Privilege, Address Spaces, and Kernel Boundaries
LESSON
Privilege, Address Spaces, and Kernel Boundaries
Outcomes
Distinguish the protection provided by CPU privilege from the protection provided by an address space.
Predict whether a user instruction can access a page from its permissions and mapping.
Explain why a kernel must validate data received across its user–kernel boundary.
Core idea: An operating system protects itself by making ordinary code run with fewer powers and by giving that code a different, permission-checked view of memory.
In the previous lesson, a small kernel reached kernel_main on a RISC-V machine. Now imagine it launches a program named calc. The program only needs to read numbers, add them, and print a result. A tempting first implementation is to place calc in memory, jump to its first instruction, and let it call kernel functions directly.
That shortcut makes calc part of the trusted kernel. A bug such as buffer[i] = 0 with a bad i can overwrite the scheduler, a filesystem cache, or the code that controls a device. A malicious program can do the same deliberately. The kernel needs a boundary that survives mistakes in the program on the other side.
This lesson builds the vocabulary and the machine contract for that boundary. The next lesson follows an actual crossing through a trap; the lesson after that builds the page tables in more detail. Here, keep one question in view: when calc executes an instruction, who decides what it may do and what memory it may name?
Core Insight
Protection has two related but different dimensions:
- Privilege mode answers which kinds of operations may this instruction request? A user program must not reconfigure page translation, directly program an interrupt controller, or install a trap handler.
- Address-space mapping answers which virtual addresses resolve to which physical pages, with which read, write, and execute permissions? A user program must not turn a pointer into an alias for kernel data merely by choosing a large number.
For a conventional RISC-V Unix-like system, application code runs in U-mode and kernel code normally runs in S-mode. Firmware may use the more privileged M-mode during early setup, but it is not the everyday execution environment for calc. An operation forbidden in the current mode raises an exception rather than quietly succeeding. That is a hardware rule, not a convention that applications are expected to obey.
An address space is the other half of the contract. A page table translates a virtual address chosen by software into a physical page and attaches permissions to the mapping. On RISC-V, the satp register selects the active translation context when virtual memory is enabled. A page-table entry can make a page readable, writable, executable, and available to U-mode. The exact bit layouts and translation walk come in lesson 004; the important idea now is that the processor consults this structure on memory access instead of trusting the address embedded in an instruction.
Neither mechanism replaces the other. U-mode alone prevents certain privileged instructions, but a user program could still corrupt the kernel if its address space mapped kernel memory as user-writable. Separate mappings alone are not enough either: without a privilege distinction, application code could change the mapping machinery or touch hardware control registers. The boundary is the combination.
A Boundary Is a Machine Contract
Think of the kernel as defining this contract for every application:
| Concern | Application side | Kernel side |
|---|---|---|
| Execution authority | Runs in U-mode; privileged operations are refused. | Runs in a privileged mode and owns privileged configuration. |
| Memory view | Sees its code, stack, heap, and explicitly mapped shared pages. | Sees kernel code and data, plus controlled access to process memory. |
| Entry to services | Makes a request through a defined gateway. | Decides whether the request is valid and performs the operation. |
| Failure | A forbidden instruction or unmapped access cannot become a successful arbitrary write. | Records, handles, or terminates the failing activity according to policy. |
The word boundary is deliberately broader than “a system-call instruction.” A system call is one intentional crossing. A page fault, an illegal instruction, and an interrupt are other events that force the kernel to regain control. Lesson 003 explains their mechanics. For now, the architectural promise is simpler: user code cannot select its own privilege level or simply write through a kernel pointer.
The boundary also tells you what the kernel must not assume. A pointer supplied by calc is not a trustworthy kernel pointer. Its address may be unmapped, may refer to a read-only page, may lie at the edge of the user allocation, or may change meaning when the process changes its mappings. The kernel must treat it as untrusted input and check or copy it through a controlled path.
Worked Scenario: Protecting calc from the Kernel—and the Kernel from calc
Suppose a loader has placed these physical pages in RAM:
| Physical page | Contents | Intended owner |
|---|---|---|
0x80200000 |
calc instructions |
calc |
0x80201000 |
calc stack and input buffer |
calc |
0x80300000 |
kernel text and read-only tables | kernel |
0x80310000 |
kernel data and kernel stack | kernel |
| device MMIO range | UART and interrupt-controller registers | kernel |
Physical placement is not protection. If calc could issue arbitrary physical loads and stores, the fact that the kernel occupies a different range would be merely a map for the attacker. The kernel therefore creates a user page table before entering the program.
One simplified user view might be:
| Virtual range | Maps to | Permissions | Meaning |
|---|---|---|---|
0x00000000–0x00000fff |
0x80200000 |
read, execute, U-mode | program text |
0x00001000–0x00001fff |
0x80201000 |
read, write, U-mode | stack and buffer |
0x00002000 onward |
no mapping yet | none | invalid until allocated |
| kernel and device ranges | absent or not U-mode accessible | none to user | protected state |
Now follow two operations.
Input: calc fetches its next instruction at virtual address 0x00000040 while in U-mode.
Intermediate transitions: The processor translates that virtual address through the active page table. It finds the first mapping, observes that execution and U-mode access are allowed, and fetches bytes from physical page 0x80200000.
Output: The instruction runs. calc gets to execute its own code, not kernel code, because the mapping says so.
Next, a faulty version of calc executes a store to a virtual address chosen to resemble a kernel address: 0x80310020.
Naive design: If every program ran with the kernel mapping and full authority, that store could alter a kernel queue pointer. The bug would be an integrity failure in the operating system, not merely a crashed application.
Intermediate transitions in the protected design: The user page table has no U-mode-writable mapping for that address. Translation or its permission check fails. The processor does not perform the store. Instead it raises an exception and transfers control along the kernel's prearranged trap path.
Output: The kernel can diagnose the invalid access and usually terminate calc; its queue pointer remains unchanged. The exact registers saved and the code that handles the exception are the subject of the next lesson. The key result here is negative but essential: a user instruction did not become a successful write to protected state.
This is why a virtual address is a request, not an entitlement. A pointer value has no authority by itself. Its meaning depends on the current address space, the access type, and the current privilege mode.
The Deliberate Doorway
calc still needs useful services. It wants to write 42\n to the console, allocate memory, and eventually exit. The design cannot be “user code may touch every kernel structure it needs.” Instead the kernel exposes narrow operations such as write, sbrk, and exit.
At a high level, write(fd, buffer, length) crosses this doorway like this:
- U-mode code puts a request number and arguments in the agreed locations, then executes the architecture's request instruction.
- Hardware switches control to privileged kernel code through a handler selected by the kernel.
- The kernel identifies the request, checks
fdandlength, and treatsbufferas a user-space reference rather than dereferencing it blindly. - The kernel copies bytes safely or validates access while it uses them, writes the device through its privileged driver, and returns a result.
- Control eventually resumes in U-mode with the application address space active again.
Do not confuse a narrow interface with automatic safety. If the kernel accepts a length of four gigabytes without checking it, or copies from an invalid user address, the kernel can still contain a bug. Privilege makes the kernel the policy-enforcing side; it does not make kernel code correct. Lessons on system calls, copy boundaries, and faults will make that observation operational.
Check: calc has a valid user page containing bytes at 0x1000, but passes 0x1000 as a buffer to write along with a length of -1 interpreted as a huge unsigned value. Is the page mapping alone enough to make the request safe?
Think first, then reveal.
Answer: No. The first byte may be valid, but the requested range can exceed the mapping, overflow arithmetic, or consume unreasonable resources. The kernel must validate the whole request and its bounds, not merely observe that the starting pointer looks plausible.
Two Designs for the Kernel Mapping
The diagram above made kernel pages absent from the user view. Real kernels choose among several layouts.
One design places a supervisor-only kernel mapping in every process page table. Entering the kernel can then retain the same translation root, which simplifies and can speed up crossings. User code still cannot access the supervisor-only pages when the permissions are correct. This is the broad-sharing design.
Another design uses a mostly separate kernel page table and a minimal transition arrangement. It reduces the kernel mapping exposed while a process is active, but changing address spaces during an entry or return adds machinery and can affect translation-cache behavior. This is the narrower-mapping design.
Neither layout removes the need for privilege checks, page permissions, and careful copying of user data. The right choice depends on the architecture, performance goals, mitigation strategy, and how much implementation complexity the kernel can sustain.
The trade-off is not “secure versus fast” in a single number. More shared mapping can make transitions simpler but leaves more privileged address-space structure present. More separation can reduce that presence but increases state-management work and opportunities to make a transition bug. Good systems design states which boundary is enforced by hardware, which is enforced by kernel code, and which cost is paid at each crossing.
Common Confusions
“U-mode means an application cannot access memory.” It means the application cannot access pages that its active page table and permissions do not allow. Its own code, stack, heap, and deliberate shared mappings remain usable.
“A process gets a private physical memory chip.” Usually it gets a private virtual view. The kernel may map different virtual pages to different physical pages, share read-only code, or deliberately share a memory region. The page tables and permissions describe the isolation.
“Kernel mode can trust a user pointer because it has more power.” The opposite is the useful rule: kernel mode has the power to dereference the pointer, so it must apply the checks. The pointer originated outside the trusted boundary.
“A page table alone protects the kernel.” It controls memory translation and permissions, but privileged instructions and hardware configuration require a privilege boundary too. Protection is composed, not delegated to one feature.
Practice: Draw a Small Contract
Design a page-level contract for a new calc process. It has executable program text, read-only constants, a read-write stack, and a kernel-owned console driver. Make a four-row table that gives each item an owner, a virtual mapping decision, and read/write/execute permissions.
Then answer two questions before looking at an implementation:
- Which item must be accessible from U-mode for the program to do arithmetic?
- If the program requests console output, which item should it not map directly, and what controlled operation should replace that mapping?
A strong answer makes text executable but not writable, makes the stack writable but not executable, leaves the console registers under kernel control, and uses a write-style request to reach the driver. If your diagram allows the application to alter kernel code or device registers directly, identify the missing permission or privilege rule.
Check: A user process can read a page because it is marked U-mode readable. Does that imply the kernel may retain a pointer to that page indefinitely and use it later without further thought?
Think first, then reveal.
Answer: No. The process may exit, unmap or replace the page, or change the bytes after the first check. The kernel needs a lifetime and synchronization policy—often copying data at the boundary or pinning and validating memory for a precisely defined interval.
Resources
- [REFERENCE] RISC-V Privileged Architecture — Focus: U-mode, S-mode, and why forbidden operations trap to a more privileged environment.
- [REFERENCE] RISC-V Supervisor-Level ISA — Focus:
satp, virtual-memory translation, and page-table permission checks. - [BOOK] xv6 RISC-V textbook — Focus: Chapter 3's process page tables and the kernel/user boundary in a small Unix-like system.
Key Takeaways
- CPU privilege controls which operations code may request; address spaces control which memory references resolve and with which permissions.
- A virtual address is not authority. The active translation context and page permissions decide whether the access can proceed.
- System calls and faults are controlled crossings of the user–kernel boundary, not direct calls into arbitrary kernel memory.
- Kernel code must validate user-provided values and memory references even though it executes with greater privilege.
- Mapping layout is a design choice with performance, complexity, and exposure trade-offs; it never eliminates the need for the other protection mechanism.