Week 6.a CS6640 02/09 2026 https://naizhengtan.github.io/26spring/ □ 1. CPU privilege levels □ 2. Memory protection, the problem □ 3. Protection schemes □ (A) Segmentaion (x86) □ (B) PMP (RISC-V) □ (C) Paging (brief) □ 4. Meltdown & its consequences --- 1. Privilege levels [draw figure] hw, kernel, apps M/S/U-Mode Q: motivation: why do people want privilege levels? -- resource multiplexing -- security purpose -- isolating faults -- providing abstractions -- what else? CPU privilege levels define what "you" can and cannot do: * controlling over system operations * accessing system resources Q: running the same piece of code in unprivileged and privileged mode, what will be the difference? - recall what a program is. It has: instructions, registers, memory. Three differences: - privileged instructions, - touching privileged registers (CSRs), - accessing privileged memory (will cover next as memory protection) Protection mechanics: exceptions [what we learned last time] How to switch privilege levels? - interrupt - exception - ecall - mret Q: How could I know what privilege level the current CPU is running in? """ RISC-V deliberately doesn’t make it easy for code to discover what mode it is running it because this is a virtualisation hole. As a general principle, code should be designed for and implicitly know what mode it will run in. Applications code should assume it is in U mode. The operating system should assume it is in S mode (it might in fact be virtualised and running in U mode, with things U mode can’t do trapped and emulated by the hypervisor). """ [from https://forums.sifive.com/t/how-to-determine-the-current-execution-privilege-mode/2823] - A brief note on Virtual Machine and Trap-and-emulate Virtual Machines and privilege. In a virtual machine, the guest OS does not execute at the highest hardware privilege level. The hypervisor occupies the most privileged mode and exposes a virtual privilege model to the guest. The guest believes it runs in kernel mode, but hardware-enforced privilege checks redirect sensitive operations to the hypervisor. Trap-and-emulate and observability. Trap-and-emulate mediates privileged operations. When a program executes a sensitive instruction, the hardware traps to the hypervisor, which emulates the effect. If emulation is correct, architectural state visible to the program matches the specification. In principle, the program should not observe whether it runs on real hardware or under a hypervisor, nor infer its true hardware privilege level. 2. Memory protection, the problem Q: Motivation? why do we need memory protection? * avoid a process access kernel's memory * avoid a process access another process's memory * avoid a process accidentally modifying its own memory (bugs) * what else? - the fundamental security problem: access control subj --[access]--> obj for us, CPU --[op]--> memory Concretely, subj: instructions + context (privilege levels and others) op: r/w/x obj: memory address Q: is this the only way to formulate memory protection? A: No. See below encryption-as-isolation. Q: Why "op" only has r/w/x? This is defined by memory abstraction: -- data cells, identified by addresses -- ops: read/write -- instructions are part of the memory Now, memory protection is a yes/no question: allow [instruction+ctx] --[r/w/x]-->[a set of memory addresses]? Q: what are invalid accesses? -- invalid subj (e.g., user program reading kernel memory) -- invalid op (e.g., execute 0xdeadbeef) -- invalid obj (e.g., writing to a readonly memory) a straightforward solution: associate a ACL to obj => for each memory obj, attach a ACL of which subj can do what op Multiple questions: Q1: what are memory objs? (memory granularity) Q2: who is the subj? (how to define subj) Q3: where to store the ACL? 3. Protection mechanism There are multiple ways to enforce memory protection. Below are several approaches proposed and used in practice. (A) segmentation (from x86) - segfault example: In Linux or alike: """ int main() { int *ptr = (int *)0xdeadbeef; *ptr = 1; } """ You will see something like: "zsh: segmentation fault ./segfault" Q: Why it is called segmentation fault? - x86 memory segmentation, a brief history * Segmentation was introduced on the Intel 8086 in 1978 as a way to allow programs to address more than 64KB of memory. // real mode // 16bit => 20bit // address = (base << 4) | offset * The Intel 80286 introduced a second version of segmentation in 1982 that added support for memory protection. * x86-32 (starting from 80386, 32-bit, 1985) How it works: * registers---CS, DS, SS, ES (FS and GS)---contain "selectors" * selectors point to GDT/LDT (global/local descriptor table) which has "descriptors" * descriptor contains base address, limit, access right * finally, the CPU will combine the requested address with the base address memory protection (checks): - check access right - check privilege levels: max(CPL, RPL) <= DPL // in x86, lower is more privileged segment selector (RPL) segment descriptor (DPL) current privilege level (CPL) [show how Linux uses segment] * The x86-64 architecture, introduced in 2003, has largely dropped support for segmentation in 64-bit mode. - How does segmentation answer the previous questions? Q1: what is the granularity of memory obj? A: defined by segment's base and limit, a dynamic region of memory. Q2: how to define the subj? A: defined by instruction Q3: where to store the ACL? A: registers (selectors), memory (descriptors in LDT/GDT) (B) PMP---Physical Memory Protection (RISC-V) * PMP configuration registers (pmpcfg0–pmpcfg15), 16 of them * PMP address register (pmpaddr0–pmpaddr63), 64 of them Mapping: * each pmpaddr is associated with one pmpcfg; * one pmpcfg maps to four pmpaddr reigsters - How does PMP answer the previous questions? [see handout] Q1: what is the granularity of memory obj? A: defined by PMP address registers; dynamically defined Q2: how to define the subj? A: defined by instruction Q3: where to store the ACL? A: PMP registers (pmpcfg0–pmpcfg15, pmpaddr0–pmpaddr63) (C) paging (brief) Paging is the mostly widely used approach. Paging is combined with page-table virtual memory today. (will cover in the next class) - super brief history * designed for swapping in/out memory (1962) [T Kilburn, R B Payne, D J Howarth, The Atlas Supervisor http://www.chilton-computing.org.uk/acl/technology/atlas/p019.htm] * later, adding memory protections - Paging is the fundamental source of isolation for almost all today's systems - How does paging answer the previous questions? Q1: what is the granularity of memory obj? A: a page (4KB), a fixed size Q2: how to define the subj? A: defined by (1) instruction, (2) privileged levels, and (3) root of the page table (cr3 in x86; satp in RISC-V) Q3: where to store the ACL? A: memory (page table entries) 4. Meltdown & its consequences * Meltdown allows malicious user code to read kernel memory, despite page protections. surprising and disturbing one of a number of recent "micro-architectural" attacks exploiting hidden implementation details of CPUs fixable, but people fear an open-ended supply of related surprises * will skip the technical details * consequences: what OS used to do: put itself in the address space of user processes' but isolate itself by using privilege levels (set "user-cannot-read" bit on page table entries) now, a bummer: user can violate isolation and access kernel memory a fix (simplified): run OS in its own memory [something deep here;] consequences: performance [see paper, An Analysis of Performance Evolution of Linux's Core Operations, SOSP'19 https://dl.acm.org/doi/10.1145/3341301.3359640]