Step 1 of 5 · Reading · ~3 min
Read
System Calls & I/O
User Mode & Privilege Rings
Lesson 1 said user code can never reach ring 0 on its own terms. Here is the hardware that enforces it. Three different privilege numbers take part in every check: CPL, the privilege of the code running now; DPL, the privilege recorded on the thing being accessed (a gate or a segment descriptor); and RPL, the privilege requested in the low two bits of a selector. In this lesson your program is the CPU's protection logic for three operations: a software interrupt through a gate, an iret, and loading a data segment.
Calling the kernel: gates and DPL
A user program enters the kernel with int n. The CPU looks up gate n in the IDT (lesson 4's 8-byte descriptors) and checks, in order:
- The gate is present (P bit). If not, it raises
#NP(vector 11). - For a software
int, CPL <= gate DPL (numerically). A ring-3 program can only use gates whose DPL is 3. Otherwise#GP(vector 13). - The gate type is an interrupt gate (
0xE) or trap gate (0xF). - The target code segment, named by the gate's selector, must be at the same or more privileged level than the caller. An interrupt can never transfer control to less privileged code.
The error code pushed with these faults names the IDT entry: vector * 8 + 2 (bit 1 says "IDT"). So int 14 from user mode, where the page-fault gate has DPL 0, produces #GP with error code 0x72. Linux makes only a few gates DPL 3: breakpoint (3), overflow (4) and int 0x80. The user cannot fake a page fault by writing int 14, which is the point. Hardware interrupts ignore the DPL check, since the CPU, not the program, raised them.
What the CPU does on success
If the privilege level changes (3 to 0) the CPU switches stacks: user code cannot be trusted to have a usable stack pointer. It loads ESP from the TSS field esp0, the kernel stack the OS registered for this task, and pushes five words onto it: the old SS, old ESP, EFLAGS, old CS, return EIP. Then it loads CS:EIP from the gate. If the level does not change, nothing is switched and only three words (EFLAGS, CS, EIP) are pushed on the current stack. An interrupt gate also clears the IF flag (interrupts off); a trap gate leaves it as it was.
high address | old SS | <- pushed only when the ring changes
| old ESP |
| EFLAGS |
| old CS |
low address | return EIP | <- new ESP points here
The way back: iret
iret pops EIP, CS, EFLAGS (and ESP, SS if returning to an outer ring). The RPL of the popped CS selects the new CPL. The CPU refuses to "return" to a more privileged level than the caller has (#GP), or to a null selector. That is why the kernel must build the frame carefully before returning to a process: the CPU treats it as data.
Loading segment registers
A data segment load (mov ds, ax) is allowed only if max(CPL, RPL) <= segment DPL. Taking the larger of the two stops a kernel that is acting on behalf of a user from accidentally using its own privilege to touch kernel data: if the user supplies a selector with RPL 3, the check is made as if it were ring 3. The null selector (0) loads without a check and faults only when used.
Common mistakes
- Comparing the wrong way around. Numerically larger is less privileged.
- Applying the gate DPL check to hardware interrupts, or forgetting it for
int. - Forgetting the stack switch and the two extra words on a ring change.
- Using the segment's RPL alone, or CPL alone, instead of the maximum.
Your exercise
The starter parses the commands and prints the lines. You write the three checks.
Discussion
Ask a question, share an insight, or help someone who’s stuck.
Sign in to post a comment or reply.
Loading…