To understand this important concept better, let's take the popular x86 architecture as a real example. Right from the i386 onward, the Intel processor supports four privilege levels or rings: Ring 0, Ring 1, Ring 2, and Ring 3. On the Intel CPU's, this is how the levels work:
Let's visualize this Figure 1 in the form of a Table 2: x86 privilege or ring levels:
Privilege or ring level | Privilege | Purpose |
Ring 0 | Highest | OS code runs here |
Ring 1 | < ring 0 | <Unused> |
Ring 2 | < ring 1 | <Unused> |
Ring 3 | Lowest | Application code runs here (userland) |
The ARM (32-bit) processor has seven modes of execution; of these, six are privileged, and only one is the non-privileged mode. On ARM, generically, the equivalent to Intel's Ring 0 is Supervisor (SVC) mode, and the equivalent to Intel's Ring 3 is User mode.
For interested readers, there are more links in the Further reading section on the GitHub repository.
The following diagram clearly shows of all modern OSes (Linux, Unix, Windows, and macOS) running on an x86 processor exploit processor-privilege levels:
Importantly, the processor ISA assigns every machine instruction with a privilege level or levels at which they are allowed to be executed. A machine instruction that is allowed to execute at the user privilege level automatically implies it can also be executed at the Supervisor privilege level. This distinguishing between what can and cannot be done at what mode also applies to register access.
To use the Intel terminology, the Current Privilege Level (CPL) is the privilege level at which the processor is currently executing code.
For example, that on a given processor shown as follows:
- The foo1 machine instruction has an allowed privilege level of Supervisor (or Ring 0 for x86)
- The foo2 machine instruction has an allowed privilege level of User (or Ring 3 for x86)
So, for a running application that executes these machine instructions, the following table emerges:
Machine instruction | Allowed-at mode | CPL (current privilege level) | Works? |
foo1 | Supervisor (0) | 0 | Yes |
3 | No | ||
foo2 | User (3) | 0 | Yes |
3 | Yes |
When one runs an application on, say, Linux, the application runs as a process (more on this later). But what privilege (or mode or ring) level does the application code run at? Refer to the preceding table: User Mode (Ring 3 on x86).
Aha! So now we see. The preceding code example, getreg_rcx.c, worked because it attempted to access the content of the general-purpose RCX register, which is allowed in User Mode (Ring 3, as well as at the other levels, of course)!
But the code of getreg_cr0.c failed; it crashed, because it attempted to access the content of the CR0 control register, which is disallowed in User Mode (Ring 3), and allowed only at the Ring 0 privilege! Only OS or kernel code can access the control registers. This holds true for several other sensitive assembly-language instructions as well. This approach makes a lot of sense.
Technically, it crashed because the processor raised a General Protection Fault (GPF).