nullplan wrote:
If it was possible to perform a far call from ring 0 to ring 3, where would the return address be stored? If on the kernel stack, then it is unaccessible to the user code that would execute the return instruction. If on the user stack, then user code could change the return pointer and execute whatever it wants as ring 0.
Let's take a step back: In order to run at ring 3, you need to change CS. How can you do that?
1. Far call
2. Far jump
3. Far return
4. Interrupt return
For the first three, you immediately get #GP if target DPL is different from CPL. Call could use a call gate but you probably did not want to install a new descriptor into your GDT just for a single use. Also, if the target is a nonconforming segment, target DPL must be less than or equal to CPL, so that would be violated here.
If you're using hardware task switching, you could jump to the task gate. But you are probably not using task gates, and shouldn't, since nobody uses that mechanism, so it is ill-tested. That leaves the interrupt return as the only way to set CS to a segment with a larger DPL than CPL. And it allows you to set EFLAGS, SS, ESP, and EIP at the same time.
Of course, you could also use SYSEXIT or SYSRET if you so choose.
Don't feel bad though, since returning from an interrupt that never happened is what you do on most architectures to enter userspace. PPC, for instance, does not allow you to enter Problem State without activating the MMU at the same time, and the only way that works is by executing the RFI instruction. RFI stands for "return from interrupt".
Thanks! That makes a lot of sense. What are SYSEXIT and SYSRET, by the way?