Explaining the Concept of Privilege Levels in Assembly Language Programming

Explain the concept of privilege levels in Assembly language programming

Early in my low-level programming journey, I tried to write a tiny assembly routine that directly accessed a hardware port, expecting it to just work like any other instruction. It didn’t — the CPU raised a protection fault and the OS killed my process instantly. That was my first real encounter with privilege levels, the hardware-enforced hierarchy that decides what a piece of running code is and isn’t allowed to do. This post walks through what privilege levels are, how they’re implemented on x86 and ARM, and why they matter even if you never write an operating system yourself.

What Are Privilege Levels?

Privilege levels (also called protection rings, or exception levels on ARM) are hardware-enforced tiers of access control built into the CPU. Code running at a higher privilege level can do things that code at a lower privilege level cannot — access arbitrary memory, execute special instructions, configure hardware, and handle interrupts directly.

This exists because an operating system needs to protect itself, and other processes, from a buggy or malicious user program. Without hardware-enforced privilege separation, any program could overwrite kernel memory, disable interrupts, or directly manipulate hardware in ways that would destabilize the entire system.

x86 Protection Rings

x86 defines four privilege rings, though in practice almost all modern general-purpose operating systems only use two of them:

RingTypical UseCommon Usage Today
Ring 0Kernel mode — full hardware accessUsed by the OS kernel
Ring 1Device drivers (historically)Rarely used in modern OSes
Ring 2Device drivers (historically)Rarely used in modern OSes
Ring 3User mode — restricted accessUsed by all normal applications
flowchart TD
    R0["Ring 0 - Kernel Mode (highest privilege)"] --> R1[Ring 1 - rarely used]
    R1 --> R2[Ring 2 - rarely used]
    R2 --> R3["Ring 3 - User Mode (lowest privilege)"]

Most modern OSes (Linux, Windows, macOS) collapse this into a simple two-tier model: Ring 0 for the kernel, Ring 3 for everything else, because the intermediate rings offered diminishing security benefit for the added complexity.

How the CPU Tracks Current Privilege Level

On x86, the Current Privilege Level (CPL) is stored in the low 2 bits of the CS (code segment) register, and it’s checked automatically by the hardware every time a privileged instruction or memory access is attempted.

; A privileged instruction - only legal at Ring 0
cli                 ; Clear Interrupt Flag (disable interrupts) - requires Ring 0
; Executing CLI from Ring 3 user-mode code causes a #GP (General Protection Fault)

ARM Exception Levels

ARM’s AArch64 architecture uses a conceptually similar but differently named system: Exception Levels (EL0–EL3).

Exception LevelTypical Use
EL0User applications (least privileged)
EL1Operating system kernel
EL2Hypervisor (virtualization)
EL3Secure Monitor / TrustZone firmware (most privileged)
flowchart TD
    EL3["EL3 - Secure Monitor / TrustZone (highest privilege)"] --> EL2["EL2 - Hypervisor"]
    EL2 --> EL1["EL1 - Operating System Kernel"]
    EL1 --> EL0["EL0 - User Applications (lowest privilege)"]

This model explicitly bakes in a virtualization tier (EL2) and a security/firmware tier (EL3), reflecting ARM’s widespread use in mobile devices, embedded security, and cloud virtualization scenarios where these extra layers are genuinely useful.

How Privilege Transitions Happen

Moving between privilege levels isn’t something you can do with an ordinary jump instruction — it requires specific, hardware-controlled mechanisms:

x86: Interrupts, Syscalls, and Task Gates

; Transition from Ring 3 to Ring 0 via syscall instruction
mov     rax, 1          ; syscall number
syscall                 ; CPU switches CS to a Ring 0 code segment, jumps to kernel handler

The SYSCALL instruction (or historically INT 0x80) is one of the very few sanctioned doorways from Ring 3 into Ring 0. The CPU automatically updates the CPL as part of this transition, and only jumps to a kernel-configured entry point — user code cannot specify an arbitrary destination address for this privilege escalation.

ARM: SVC and Exception Entry

; Transition from EL0 to EL1 via supervisor call
MOV     X8, #64          ; syscall number
SVC     #0               ; CPU switches exception level EL0 -> EL1, jumps to configured vector

Just like x86, the destination of this transition (the exception vector table) is configured by the kernel ahead of time, not chosen by the calling user-mode code.

What Privileged Instructions Actually Look Like

CategoryExample x86 InstructionsExample ARM Instructions
Interrupt controlCLI, STIMSR DAIFSet, MSR DAIFClr
Memory managementMOV CR3, reg (page table base)MSR TTBR0_EL1, reg
Privilege/exception configLGDT, LIDTMSR VBAR_EL1, reg
Halt/power managementHLTWFI (Wait For Interrupt)

Attempting any of these from an insufficiently privileged level results in a hardware fault — on x86 this is typically a General Protection Fault (#GP); on ARM it results in a synchronous exception that the OS’s exception handler must service (often terminating the offending process).

Internal Flow: What Happens on a Privilege Violation

sequenceDiagram
    participant App as User Program (Ring 3 / EL0)
    participant CPU as CPU Protection Hardware
    participant Kernel as Kernel (Ring 0 / EL1)

    App->>CPU: Attempt privileged instruction (e.g. CLI, MSR TTBR0_EL1)
    CPU->>CPU: Check current privilege level against required level
    CPU->>Kernel: Privilege violation detected - raise fault/exception
    Kernel->>Kernel: Fault handler inspects the offending instruction/context
    Kernel->>App: Typically terminates the process (e.g. SIGSEGV/SIGILL)

Practical Use Cases

  1. Operating system and kernel development: Understanding privilege transitions is fundamental to writing bootloaders, kernels, and hypervisors.
  2. Virtualization: Hypervisors rely on ARM’s EL2 or x86’s VMX root mode to intercept and manage guest OS privilege behavior transparently.
  3. Security research and exploit mitigation: Many privilege-escalation vulnerabilities are precisely about tricking the kernel into executing attacker-controlled code at a higher privilege level than intended.
  4. Embedded/TrustZone development: ARM’s EL3/TrustZone is used to isolate secure firmware (e.g., handling cryptographic keys) from the normal-world OS, even if the OS itself is compromised.

Debugging Privilege-Related Issues

$ dmesg | grep -i "general protection"
$ dmesg | grep -i "segfault"

On Linux, privilege violations from user-mode code typically surface as SIGSEGV (segmentation fault) or SIGILL (illegal instruction), logged in the kernel ring buffer. Kernel-mode privilege bugs (much rarer to debug, since they crash the whole system) require kernel debuggers like kgdb or hardware JTAG debug probes.

Comparison: x86 Rings vs. ARM Exception Levels

Aspectx86 Protection RingsARM Exception Levels
Number of levels4 defined, typically 2 used4 defined (EL0-EL3), all commonly used in modern systems
Virtualization supportSeparate VMX root/non-root mode overlayBuilt directly into the EL model via EL2
Security/firmware tierHandled via System Management Mode (SMM), somewhat separateExplicit EL3/TrustZone tier
Terminology“Rings,” Current Privilege Level (CPL)“Exception Levels,” current EL tracked in PSTATE

Common Mistakes

  • Assuming user-mode code can execute privileged instructions “if it’s careful” — the check is enforced entirely in hardware, not convention.
  • Confusing ARM’s exception levels with x86 rings one-to-one; ARM’s model explicitly separates hypervisor (EL2) and secure firmware (EL3) concepts that x86 handles differently (via VMX and SMM respectively).
  • In kernel/driver development, forgetting that certain memory-mapped registers or instructions require a specific privilege level, leading to faults that are hard to diagnose without understanding the underlying protection model.

Best Practices

  • When writing low-level or OS-adjacent assembly, always check the target architecture’s manual for which privilege level a given instruction requires.
  • Use well-documented syscall/SVC interfaces to request privileged operations rather than attempting direct privileged instructions from user mode.
  • In virtualization or security-sensitive contexts, understand exactly which exception level or ring your code is expected to run at, and design fault handlers accordingly.

FAQs

Can user-mode code ever execute privileged instructions? No — the CPU hardware checks the current privilege level before executing certain instructions and blocks the attempt with a fault if the check fails, regardless of how the instruction was reached.

Do all four x86 rings get used in modern systems? Rarely — almost all modern operating systems use only Ring 0 and Ring 3, treating Rings 1 and 2 as legacy/unused.

What’s the difference between EL2 and EL3 on ARM? EL2 is for hypervisors managing virtual machines; EL3 is for the most privileged secure firmware/monitor code, often used for TrustZone-based secure world isolation, which is a different concern from virtualization.

Summary and Key Takeaways

  • Privilege levels are hardware-enforced tiers that restrict which instructions and memory a running program can access.
  • x86 uses protection rings (0–3, with 0 and 3 dominant in practice); ARM uses Exception Levels (EL0–EL3), explicitly including hypervisor and secure firmware tiers.
  • Transitioning between privilege levels requires specific, kernel-controlled mechanisms like SYSCALL/SVC, not ordinary jumps.
  • Understanding privilege levels is essential for OS development, virtualization, security research, and diagnosing low-level faults.

References

  • Intel® 64 and IA-32 Architectures Software Developer’s Manual, Volume 3 (Protection, Rings, and Privilege chapters)
  • AMD64 Architecture Programmer’s Manual, Volume 2 (System Programming)
  • Arm® Architecture Reference Manual for A-profile architecture (Exception Levels chapter)
  • Arm TrustZone documentation (Security and EL3 overview)
Total
1
Shares

Leave a Reply

Previous Post
How are multi-byte data types represented in Assembly language

How Are Multi-Byte Data Types Represented in Assembly Language?

Next Post
Describe the function of the instruction queue in Assembly language

Describing the Function of the Instruction Queue in Assembly Language

Related Posts