class: center, middle ### Secure Computer Architecture and Systems *** # Operating Systems:
Basic Practical Aspects --- # Introduction - Here we cover: - A very simplified model of how an OS runs on (equally simplified) hardware - What an OS expects from the hardware (CPU) - The security properties an OS needs to enforce and how it does so -- - We focus on **CPU** and **memory** - We'll see how the OS interfaces with devices (**I/O**) later --- # Basic OS Principles .leftcol[ - A computer is made of **hardware**: - CPU, memory, and I/O (disk and network) - Software runs on top of that hardware, and **the OS manipulates the hardware** directly - **Applications** run on top of the OS - They cannot be allowed to access most of the hardware directly, for safety/security reasons ] .rightcol[
] --- # Basic OS Principles (2) .leftcol[ - The OS provides **standardised abstractions** for applications to use the hardware safely - E.g. processes and threads for CPU/memory, filesystem for storage, sockets for network, etc. - These abstractions are accessed through a standard API: **system calls** - On Linux: `open`, `read`, `write`, `mmap`, etc. ] .rightcol[
] --- # Boot Time Basic steps (ultra-simplified): 1. Power on 2. Motherboard firmware (BIOS/UEFI) performs basic hardware initialisation and then runs the boot loader 3. Boot loader (e.g. GRUB) takes over 4. Boot loader loads the OS kernel, which starts to run 5. OS initialises more hardware and itself 6. Once ready, the OS can run applications
--- name: cpu # Execution Model on the CPU - CPU hardware: ALU + control logic + registers (among others) - **Instruction pointer** (program counter): register pointing to the instruction being executed in the code segment - Load/store instructions read/write data from/to memory --- template: cpu
--- template: cpu
--- template: cpu
--- template: cpu
--- template: cpu
--- template: cpu
--- # Execution Model on the CPU - When an application runs, the content of the registers at any given time defines the **application's CPU state** at that time -- - That state can be **saved to and restored from** memory, e.g. for a context switch:
--- # Kernel Invocation When does the kernel get invoked, i.e. **when does the CPU start executing kernel code?** -- Only on two occasions: - At boot time, after the boot loader finishes loading the kernel; and - At runtime when an **interrupt** is received by the processor -- There are two types of interrupts: - **Hardware interrupts** for I/O: a device sends an interrupt to the CPU - **Software exceptions** (e.g. division by zero): the CPU interrupts itself --- # Hardware Interrupt
--- # Hardware Interrupt
--- # Hardware Interrupt
--- # Software Exception
--- # Software Exception
--- # OS Security - Invariant: **an application must not be able to read/write the memory allocated to another application or to the kernel**
--- name: security # OS Security - OS sets up the MMU to perform virtual-to-physical address translation - Each app is given the illusion that it has access to 100% of the memory - It cannot access the memory belonging to other apps/the kernel --- template: security
--- template: security
--- template: security
--- # System Calls - **If the application cannot access kernel data/run kernel code for security reasons, how can it invoke OS services?** -- - To invoke a system call, the application executes a special instruction that traps to the kernel, like an exception: the CPU starts running the kernel, which handles the system call
--- # System Call ABI - Programmers generally do not invoke system calls directly -- - In a C program, one calls the functions `read()`, `write()`, etc. implemented by the C standard library (libc) - Libc is linked with the app and exposes a language/source-level interface: an **Application Programming Interface (API)** -- - Libc needs to invoke the kernel, which is not compiled/linked together with applications - Kernel may even be written in a different language vs. applications -- - To invoke system calls, we need an **Application Binary Interface (ABI)** - A machine-level convention stating how to invoke a system call: - What to put in what registers - What machine instruction(s) to use --- # System Call ABI - Linux uses the **System V ABI**, which states that on x86-64 a system call is invoked by an application as follows: -- 1. Place arguments in order in `%rdi`, `%rsi`, `%rdx`, `%r10`, `%r8` and `%r9` (*mostly* similar to the System V function call convention) -- 2. Place the system call number (unique integer identifier) in `%rax` -- 3. Execute the `syscall` instruction, which traps to the kernel -- 4. Before returning to user space, kernel places the return value in `%rax` -- Disassembled libc wrapper for `clock_gettime` (2 arguments): ```asm 00000000004672b0 <__clock_gettime>: #... 4672f8: mov %r12,%rsi 4672fb: mov %ebp,%edi 4672fd: mov $0xe4,%eax * 467302: syscall # ... ``` --- # Manual Syscall Invocation .leftcol[ ```asm .global _start .text _start: # write(1, message, 14) mov $1, %rax mov $1, %rdi mov $message, %rsi mov $14, %rdx syscall # exit(0) mov $60, %rax xor %rdi, %rdi syscall message: .ascii "Hello, world!\n" # ``` .codelink[
`13-os-basic-practical-aspects/syscall.s`
] ] .rightcol[ Assemble, link, and run as follows: ```bash $ as syscall.s -o syscall.o $ ld syscall.o -o syscall $ ./syscall Hello, world! ``` ] --- # Privilege Modes - **Some CPU instructions are privileged**: for security reasons, they should only be executed by the kernel: - Installing a new page table - Communicating with the hardware - Shutting down/resetting the CPU - Etc. -- - **Privilege modes** ensure that applications cannot execute them - At any time, the processor is in one of two modes: - **User mode** when applications run; or - **Supervisor mode** (kernel mode) when the kernel runs -- - A privileged instruction: - Executes successfully in kernel mode - Triggers an exception (trap to the kernel) when executed in user mode --- # Privilege Modes Privilege modes have different names depending on the architecture; on x86 they are called **protection rings**, e.g. for x86-32:
Rings 1 and 2 are largely vestigial on x86-64: mainstream OSes only use ring 0 (supervisor mode) and ring 3 (user mode) --- # User/Kernel World Switches - On x86-64, when the `syscall` instruction is executed: - The CPU switches from user to supervisor mode - Then jumps to a predefined piece of code: **system call handler** (CFI) - The kernel starts to run and handles the system call - Once done, the kernel executes the `sysret` instruction - Switches to user mode - Returns to the instruction following `syscall`
- World switches are **costly**: hundreds to thousands of cycles --- # Wrapping Up - The OS abstracts the hardware for applications, for ease of use but also for **safety/security reasons**: - Applications cannot access each other's memory or the kernel's memory - Applications cannot execute privileged operations, e.g. shutting down the computer, installing a new page table, etc. - Achieved with help from the CPU hardware: **MMU (virtual memory), privilege modes** - The only way for an application to invoke kernel services is through a safe, controlled interface: **system calls**