|
| 1 | +# SYZOS Technical Documentation |
| 2 | + |
| 3 | +## 1. System Overview |
| 4 | + |
| 5 | +### Concept |
| 6 | +SYZOS is not a traditional operating system but an **immutable C library** designed to run as a Guest (L1) within a KVM virtual machine. Its primary purpose is to expose an easy-to-fuzz API to the Host (L0) fuzzer (`syzkaller`), allowing for state-aware interactions that are difficult to achieve with raw instruction fuzzing. |
| 7 | + |
| 8 | +In this architecture, the **Host (syz-executor)** acts as the orchestrator, while the **Guest (SYZOS)** acts as the execution engine for a pre-defined sequence of commands. |
| 9 | + |
| 10 | +### Execution Flow |
| 11 | +SYZOS leverages syzkaller's standard execution model, where the fuzzer generates a sequence of syscalls (a `syzlang` program) to be executed by the host executor. For SYZOS, this program constructs the VM and defines the guest's internal logic via pseudo-syscalls. |
| 12 | + |
| 13 | +1. **VM Creation:** The fuzzer calls standard KVM ioctls (e.g., `openat`, `KVM_CREATE_VM`) to create the VM container. |
| 14 | +2. **Environment Setup (`syz_kvm_setup_syzos_vm`):** This pseudo-syscall automates the complex setup of Guest memory, ensuring the VM has valid code and stack regions. |
| 15 | +3. **VCPU & Program Loading (`syz_kvm_add_vcpu`, see 3.2):** |
| 16 | + * Instead of a bare `KVM_CREATE_VCPU`, the fuzzer calls `syz_kvm_add_vcpu` that creates a new VCPU in the VM and initializes its state. |
| 17 | + * This call takes the **entire sequence of SYZOS commands** as an argument. This sequence effectively becomes the "program" the guest will execute. |
| 18 | + * **Concurrency:** SYZOS supports up to 4 separate VCPUs sharing the same address space, allowing the fuzzer to schedule concurrent guest operations. |
| 19 | +4. **Execution (`KVM_RUN`):** The fuzzer triggers execution via standard `KVM_RUN` calls. The Guest executes its pre-loaded commands step-by-step. |
| 20 | + * **Yielding:** When the Guest needs to perform an action that requires Host intervention (e.g., a transition during Nested Virtualization), it yields to L0 via `UEXIT`. |
| 21 | + * **Resumption:** If the program contains multiple `KVM_RUN` calls, they are used to resume the Guest until the pre-loaded program completes. |
| 22 | + |
| 23 | +### Design Philosophy |
| 24 | +* **Logical Mutation:** Instead of fuzzing raw assembly bytes, SYZOS exposes high-level primitives to the fuzzer. The fuzzer mutates the arguments of the SYZOS commands. |
| 25 | +* **State Validity:** By implementing setup sequences in C, SYZOS ensures that complex structures like IRQ tables or Page Tables are valid enough to reach deep kernel code paths. |
| 26 | + |
| 27 | +--- |
| 28 | + |
| 29 | +## 2. Memory Layout & ABI (Communication Interface) |
| 30 | + |
| 31 | +The Host and Guest communicate via a shared memory protocol. The Host writes commands and arguments into specific physical memory addresses, which the Guest maps and reads. |
| 32 | + |
| 33 | +### Communication Interface |
| 34 | +* **Command Channel:** A dedicated memory region where the Host writes the commands and their arguments. |
| 35 | +* **Result Channel:** Mechanism for the Guest to report status back to the Host, piggybacked on the `UEXIT` mechanism. |
| 36 | +* **Scratch Space:** Mutable memory used by the Guest to generate dynamic code blobs or store temporary data needed for operations like `MSR` writes. |
| 37 | + |
| 38 | +### ARM64 Memory Map |
| 39 | +The ARM64 implementation relies on a static physical memory layout to ensure the Host knows exactly where to place data. |
| 40 | + |
| 41 | +| Physical Address | Description | Usage | |
| 42 | +| :--- | :--- | :--- | |
| 43 | +| `0x08000000` | GIC v3 Distributor | Interaction with Generic Interrupt Controller | |
| 44 | +| `0x080a0000` | GIC v3 Redistributor | Per-CPU Interrupt Controller interface | |
| 45 | +| `0xdddd0000` - `0xeeee0000` | Read-only / Command Page | Host writes SYZOS commands here. Also used to trigger page faults for `UEXIT` | |
| 46 | +| `0xeeee8000` - `0xeeef0000` | Code / Scratch Space | Where SYZOS resides. Also used for generated code (e.g., MSR trampolines) | |
| 47 | +| `0xffff1000` | EL1 Stack | Stack space for the SYZOS Guest execution | |
| 48 | + |
| 49 | +### x86 Memory Map |
| 50 | +While the exact addresses may vary by implementation, the x86 layout follows similar principles: |
| 51 | +* **Guest Code:** Allocated via `KVM_SET_USER_MEMORY_REGION` (typically 1 page). |
| 52 | +* **Page Tables:** Setup by the Host to allow virtual-to-physical translation required for long mode. |
| 53 | +* **IDT (Interrupt Descriptor Table):** Setup by the Host to handle exceptions within the Guest. |
| 54 | + |
| 55 | +--- |
| 56 | + |
| 57 | +## 3. Host-Side Implementation (`syz-executor`) |
| 58 | + |
| 59 | +The Host side is responsible for the heavy lifting of VM initialization. This is achieved through "pseudo-syscalls" - functions implemented in `syz-executor` that look like syscalls to the fuzzer but perform complex setup logic. |
| 60 | + |
| 61 | +### 3.1 VM Initialization: `syz_kvm_setup_syzos_vm()` |
| 62 | +This pseudo-syscall creates the VM and prepares the environment. |
| 63 | +* **Memory Allocation:** Calls `ioctl(vmfd, KVM_SET_USER_MEMORY_REGION, &memreg)` multiple times to map the Guest physical memory slots (Code, Stack, MMIO). |
| 64 | +* **Image Loading:** Copies the compiled SYZOS C library binary into the allocated Guest Code region. |
| 65 | + |
| 66 | +### 3.2 VCPU Initialization: `syz_kvm_add_vcpu()` |
| 67 | +This function adds a virtual CPU to the VM and configures its initial state to jump into the SYZOS entry point. |
| 68 | +* **Program Loading:** It parses the `syzlang`-generated argument structure, which contains the sequence of SYZOS commands, and copies them into the Guest's Command Page. |
| 69 | +* **Context Setup:** |
| 70 | + * **x86:** Sets up SREGS (Segments, Page Tables) and IDT. |
| 71 | + * **ARM64:** Sets PC to the entry point and SP to the stack. |
| 72 | + |
| 73 | +### 3.3 The Handshake Mechanism (`UEXIT`) |
| 74 | +The core synchronization primitive is the `UEXIT`. |
| 75 | +* **Trigger:** The Guest reads from a specific unmapped or read-only address (e.g., inside `0xdddd0000` on ARM) or executes a specific instruction sequence. |
| 76 | +* **Detection:** `KVM_RUN` returns on the Host. The Host may check `kvm_run->exit_reason`. |
| 77 | +* **Handling:** |
| 78 | + * If the exit indicates a Page Fault (EPT violation) at the specific `UEXIT` address, the Host treats this as a voluntary yield. |
| 79 | + * The Host reads the exit qualification or register state to retrieve the "return argument" passed by the Guest. |
| 80 | + |
| 81 | +--- |
| 82 | + |
| 83 | +## 4. Guest-Side Implementation (SYZOS Library) |
| 84 | + |
| 85 | +### Source Organization & `GUEST_CODE` |
| 86 | +SYZOS guest handlers are defined directly in architecture-specific executor headers (e.g., `executor/common_kvm_amd64_syzos.h`). |
| 87 | +* **The `GUEST_CODE` Macro:** Functions intended to run inside the guest are marked with `GUEST_CODE` (e.g., `GUEST_CODE static void guest_handle_...`). This instructs the compiler/linker to place these functions in a specific section that `syz-executor` copies into the Guest's physical memory. |
| 88 | +* **Header-Based Implementation:** The entire SYZOS codebase is contained within header files included by the executor. This architecture is necessitated by `syz-prog2c`, a tool that converts `syzlang` reproducers into standalone C programs. By concatenating these headers (via `#include` expansion), `syz-prog2c` can produce a single, build-system-independent C source file that compiles anywhere without external dependencies. |
| 89 | + |
| 90 | +### The Dispatch Loop (`guest_main`) |
| 91 | +The entry point `guest_main` iterates through the command buffer that was populated by `syz_kvm_add_vcpu`. |
| 92 | +* **Command Routing (If/Else Chain):** The routing is strictly implemented as a series of `if/else if` statements rather than a `switch`. The reason for this is that a `switch` statement can be optimized by compilers into a jump table stored in the executable's `.rodata` section. Since the global data sections are not mapped into the Guest address space, accessing a jump table would cause an immediate Page Fault. |
| 93 | +* **Argument Parsing:** Commands are cast to specific structures (e.g., `struct api_call_5*`) to access arguments safely. |
| 94 | +* **Execution:** The handler performs the logic and the loop advances to the next command in the buffer. |
| 95 | + |
| 96 | +### Core Primitives |
| 97 | +* **`SYZOS_API_UEXIT`:** - Triggers a specific exception that the Host recognizes as a "yield". It passes a return value (1 argument) back to the Host to signal success/failure or data. |
| 98 | +* **`SYZOS_API_CODE`:** - Executes a raw blob of machine code supplied by the Host. This can be used to emit exact instruction sequences not covered by high-level APIs. |
| 99 | + |
| 100 | +--- |
| 101 | + |
| 102 | +## 5. Platform Specifics |
| 103 | + |
| 104 | +### x86 (Intel & AMD) |
| 105 | + |
| 106 | +#### Privileged Operations |
| 107 | +SYZOS exposes specific APIs to fuzz privileged x86 instructions: |
| 108 | +* **`SYZOS_API_CPUID`:** Executes the `CPUID` instruction. |
| 109 | +* **`SYZOS_API_WRMSR` / `SYZOS_API_RDMSR`:** Reads/Writes Model Specific Registers. |
| 110 | +* **`SYZOS_API_WR_CRN` / `SYZOS_API_WR_DRN`:** Writes to Control Registers and Debug Registers. |
| 111 | +* **`SYZOS_API_IN_DX` / `SYZOS_API_OUT_DX`:** Executes I/O port operations. |
| 112 | + |
| 113 | +#### Nested Virtualization (NV) Engine |
| 114 | +SYZOS acts as a lightweight L1 hypervisor to fuzz L2 guests, abstracting the architectural differences between Intel VMX and AMD SVM. It provides a uniform API for the VM lifecycle while offering architecture-specific commands for state mutation. |
| 115 | + |
| 116 | +##### VM Lifecycle & Execution |
| 117 | +The following primitives control the nested guest's existence and execution flow: |
| 118 | + |
| 119 | +* **`SYZOS_API_ENABLE_NESTED`:** Enables the virtualization extensions (VMXON on Intel, EFER.SVME on AMD). |
| 120 | +* **`SYZOS_API_NESTED_CREATE_VM`:** Initializes the necessary control structures (VMCS for Intel, VMCB for AMD) and sets up Nested Page Tables. |
| 121 | +* **`SYZOS_API_NESTED_LOAD_CODE`:** Injects a sequence of instructions into the L2 guest's memory, defining what code the nested machine will execute. |
| 122 | +* **`SYZOS_API_NESTED_VMLAUNCH`:** Performs the initial VM Entry, transferring control to the L2 guest. |
| 123 | +* **`SYZOS_API_NESTED_VMRESUME`:** Resumes execution of the L2 guest after it has exited back to L1. |
| 124 | + |
| 125 | +##### State Mutation (Architecture Specific) |
| 126 | +To stress the host's handling of invalid or edge-case states, SYZOS allows direct mutation of the hardware control structures. This is done by applying the "set/unset/flip" mask logic: `new_val = (old_val & ~unset_mask) | set_mask ^ flip_mask`. |
| 127 | +The SYZOS commands are **`SYZOS_API_NESTED_INTEL_VMWRITE_MASK`** (mutates the VMCS fields on Intel) and **`SYZOS_API_NESTED_AMD_VMCB_WRITE_MASK`** (VMCB on AMD). |
| 128 | + |
| 129 | +--- |
| 130 | + |
| 131 | +### ARM64 |
| 132 | + |
| 133 | +#### Device Emulation |
| 134 | +A significant portion of ARM64 KVM code is device emulation. SYZOS provides specialized APIs to fuzz these complex interactions. |
| 135 | +* **GICv3 & ITS:** |
| 136 | + * **`SYZOS_API_IRQ_SETUP`:** Sets up the VGICv3 distributor and installs the guest IRQ table. |
| 137 | + * **`SYZOS_API_ITS_SETUP`:** Allocates translation tables and configures the Interrupt Translation Service (ITS) base. |
| 138 | + * **`SYZOS_API_ITS_SEND_CMD`:** Injects structured GIC commands (e.g., `MAPD`, `MOVI`) into the command queue. |
| 139 | + |
| 140 | +#### Hypervisor Interface |
| 141 | +SYZOS targets the boundary between the Guest and EL2/Firmware. |
| 142 | +* **Hypercalls:** |
| 143 | + * **`SYZOS_API_HVC`:** Executes `hvc #0` with fuzzer-controlled parameters in registers `x0-x5`. |
| 144 | + * **`SYZOS_API_SMC`:** Executes `smc #0` (Secure Monitor Call) with parameters in `x0-x5`. |
| 145 | + |
| 146 | +--- |
| 147 | + |
| 148 | +## 7. Developer Guide: How to Add a New Command |
| 149 | + |
| 150 | +This guide details the process of adding a new SYZOS command, using `SYZOS_API_NESTED_AMD_VMCB_WRITE_MASK` as a reference case. |
| 151 | + |
| 152 | +### Step 1: Define API ID and Handler Prototype |
| 153 | +Modify the architecture-specific executor header (e.g., `executor/common_kvm_amd64_syzos.h`) to register the new command. |
| 154 | + |
| 155 | +1. **Add the Enum ID:** Add a new entry to the `syzos_api_id` enum. |
| 156 | + ```c |
| 157 | + typedef enum { |
| 158 | + // ... |
| 159 | + SYZOS_API_NESTED_AMD_VMCB_WRITE_MASK = 380, // New ID |
| 160 | + SYZOS_API_STOP, |
| 161 | + } syzos_api_id; |
| 162 | + ``` |
| 163 | +2. **Declare the Handler:** Add a forward declaration using the `GUEST_CODE` macro. |
| 164 | + ```c |
| 165 | + GUEST_CODE static void guest_handle_nested_amd_vmcb_write_mask(struct api_call_5* cmd, uint64 cpu_id); |
| 166 | + ``` |
| 167 | + |
| 168 | +### Step 2: Implement Guest Logic and Dispatch |
| 169 | +In the same file (or corresponding source), implement the guest logic. |
| 170 | + |
| 171 | +1. **Add Dispatch Case:** Update `guest_main`. |
| 172 | + ```c |
| 173 | + } else if (call == SYZOS_API_NESTED_AMD_VMCB_WRITE_MASK) { |
| 174 | + guest_handle_nested_amd_vmcb_write_mask((struct api_call_5*)cmd, cpu); |
| 175 | + } |
| 176 | + ``` |
| 177 | +2. **Implement Handler:** Write the function logic. Strict guest-safe code restrictions apply. |
| 178 | + ```c |
| 179 | + GUEST_CODE static noinline void |
| 180 | + guest_handle_nested_amd_vmcb_write_mask(struct api_call_5* cmd, uint64 cpu_id) |
| 181 | + { |
| 182 | + if (get_cpu_vendor() != CPU_VENDOR_AMD) return; |
| 183 | + // ... parse args and perform logic ... |
| 184 | + vmcb_write64(vmcb_addr, offset, new_value); |
| 185 | + } |
| 186 | + ``` |
| 187 | + |
| 188 | +### Step 3: Define syzlang Description |
| 189 | +Expose the new command to `syzkaller` in the description file (e.g., `sys/linux/dev_kvm_amd64.txt`). |
| 190 | + |
| 191 | +1. **Define Structures:** Define any necessary constants or structures. |
| 192 | +2. **Map Command ID:** Add the command to the `syzos_api_call` union. **Crucial:** The ID (e.g., `380`) must match the enum in the C header. |
| 193 | + ``` |
| 194 | + syzos_api_call$x86 [ |
| 195 | + nested_amd_vmcb_write_mask syzos_api$x86[380, syzos_api_nested_amd_vmcb_write_mask] |
| 196 | + ] [varlen] |
| 197 | + ``` |
| 198 | +
|
| 199 | +--- |
| 200 | +
|
| 201 | +## 8. Validation & Regression Testing |
| 202 | +
|
| 203 | +The system includes a regression testing framework located in `sys/linux/test/`. New commands must include a test case to verify they trigger the expected Hypervisor behavior. |
| 204 | +
|
| 205 | +### Test File Structure |
| 206 | +Tests are `syzlang` programs with special assertions. |
| 207 | +* **Header:** Requires metadata, e.g., `# requires: arch=amd64 -threaded`. |
| 208 | +* **Setup:** Standard boilerplate creates a VM and enters SYZOS. |
| 209 | +* **Logic:** The test configures the guest to perform a specific action (e.g., executing `HLT` in a nested L2 guest). |
| 210 | +
|
| 211 | +### Assertions |
| 212 | +Tests use specialized pseudo-syscalls to assert the VM's exit state: |
| 213 | +* **`syz_kvm_assert_syzos_uexit$x86(fd, code)`:** Asserts that the guest voluntarily yielded with a specific `UEXIT` code (e.g., `0xe2e20001`). |
| 214 | +* **`syz_kvm_assert_syzos_kvm_exit$x86(fd, exit_reason)`:** Asserts that the guest triggered a standard KVM exit (e.g., `0x5` for `KVM_EXIT_HLT`) that was trapped by L0. |
0 commit comments