diff options
| author | Alexander Potapenko <glider@google.com> | 2025-09-30 16:12:17 +0200 |
|---|---|---|
| committer | Alexander Potapenko <glider@google.com> | 2025-10-17 06:51:20 +0000 |
| commit | 2606b77513573de02745daefe9de925c9494cfff (patch) | |
| tree | 4e4d4856049ef76553068e598ad46d053fdb87d5 /executor | |
| parent | 6f255ae2c60c113dc8eeae689561e439791cc502 (diff) | |
executor: refactor x86 SYZOS setup
Pass around struct kvm_syzos_vm instead of one-off pointers to
various guest memory ranges.
Diffstat (limited to 'executor')
| -rw-r--r-- | executor/common_kvm_amd64.h | 129 | ||||
| -rw-r--r-- | executor/kvm.h | 3 |
2 files changed, 67 insertions, 65 deletions
diff --git a/executor/common_kvm_amd64.h b/executor/common_kvm_amd64.h index cdf36b714..44e209650 100644 --- a/executor/common_kvm_amd64.h +++ b/executor/common_kvm_amd64.h @@ -202,12 +202,14 @@ static void setup_64bit_idt(struct kvm_sregs* sregs, char* host_mem, uintptr_t g } #endif -#if SYZ_EXECUTOR || __NR_syz_kvm_setup_cpu || __NR_syz_kvm_setup_syzos_vm +#if SYZ_EXECUTOR || __NR_syz_kvm_setup_syzos_vm || __NR_syz_kvm_add_vcpu // Flags for mem_region #define MEM_REGION_FLAG_USER_CODE (1 << 0) #define MEM_REGION_FLAG_DIRTY_LOG (1 << 1) #define MEM_REGION_FLAG_READONLY (1 << 2) #define MEM_REGION_FLAG_EXECUTOR_CODE (1 << 3) +#define MEM_REGION_FLAG_GPA0 (1 << 5) +#define MEM_REGION_FLAG_NO_HOST_MEM (1 << 6) struct mem_region { uint64 gpa; @@ -215,18 +217,40 @@ struct mem_region { uint32 flags; }; +// SYZOS guest virtual memory layout (must be in sync with executor/kvm.h): static const struct mem_region syzos_mem_regions[] = { - {X86_SYZOS_ADDR_ZERO, 20, 0}, + // AMD64 data structures (20 pages starting at GPA 0x0, see kvm.h). + {X86_SYZOS_ADDR_ZERO, 20, MEM_REGION_FLAG_GPA0}, + // SMRAM memory. {X86_SYZOS_ADDR_SMRAM, 10, 0}, + // Unmapped region to trigger a page faults for uexits etc. + {X86_SYZOS_ADDR_EXIT, 1, MEM_REGION_FLAG_NO_HOST_MEM}, + // Writable region with KVM_MEM_LOG_DIRTY_PAGES to fuzz dirty ring. {X86_SYZOS_ADDR_DIRTY_PAGES, 2, MEM_REGION_FLAG_DIRTY_LOG}, + // SYZOS user code (generated by the fuzzer). {X86_SYZOS_ADDR_USER_CODE, KVM_MAX_VCPU, MEM_REGION_FLAG_READONLY | MEM_REGION_FLAG_USER_CODE}, + // Executor guest code. {X86_SYZOS_ADDR_EXECUTOR_CODE, 4, MEM_REGION_FLAG_READONLY | MEM_REGION_FLAG_EXECUTOR_CODE}, + // Scratch memory for code generated at runtime. {X86_SYZOS_ADDR_SCRATCH_CODE, 1, 0}, + // CPU stack. {X86_SYZOS_ADDR_STACK_BOTTOM, 1, 0}, + // IOAPIC memory. {X86_SYZOS_ADDR_IOAPIC, 1, 0}, }; #endif +#if SYZ_EXECUTOR || __NR_syz_kvm_setup_syzos_vm || __NR_syz_kvm_setup_cpu || __NR_syz_kvm_add_vcpu +struct kvm_syz_vm { + int vmfd; + int next_cpu_id; + void* host_mem; + size_t total_pages; + void* user_text; + void* gpa0_mem; +}; +#endif + #if SYZ_EXECUTOR || __NR_syz_kvm_add_vcpu // See https://wiki.osdev.org/Interrupt_Descriptor_Table#Gate_Descriptor_2. struct idt_entry_64 { @@ -251,12 +275,12 @@ static inline void error_in_executor_fn_guest_addr() DEFINE_GUEST_FN_TO_GPA_FN(executor_fn_guest_addr, X86_SYZOS_ADDR_EXECUTOR_CODE, error_in_executor_fn_guest_addr()); #define X86_NUM_IDT_ENTRIES 256 -static void syzos_setup_idt(struct kvm_sregs* sregs, char* host_mem) +static void syzos_setup_idt(struct kvm_syz_vm* vm, struct kvm_sregs* sregs) { sregs->idt.base = X86_SYZOS_ADDR_VAR_IDT; sregs->idt.limit = (X86_NUM_IDT_ENTRIES * sizeof(struct idt_entry_64)) - 1; volatile struct idt_entry_64* idt = - (volatile struct idt_entry_64*)(host_mem + sregs->idt.base); + (volatile struct idt_entry_64*)((uint64)vm->host_mem + sregs->idt.base); uint64 handler_addr = executor_fn_guest_addr((uintptr_t)dummy_null_handler); for (int i = 0; i < X86_NUM_IDT_ENTRIES; i++) { idt[i].offset_low = (uint16)(handler_addr & 0xFFFF); @@ -286,19 +310,19 @@ struct kvm_opt { }; #endif -#if SYZ_EXECUTOR || __NR_syz_kvm_setup_cpu || __NR_syz_kvm_add_vcpu +#if SYZ_EXECUTOR || __NR_syz_kvm_add_vcpu #define PAGE_MASK GENMASK_ULL(51, 12) -static void map_4k_page(void* host_mem, uint64 gpa) +static void map_4k_page(uint64 host_mem, uint64 gpa) { - uint64* pml4 = (uint64*)((uint64)host_mem + X86_SYZOS_ADDR_PML4); + uint64* pml4 = (uint64*)(host_mem + X86_SYZOS_ADDR_PML4); // PML4 Entry (Level 4). uint64 pml4_idx = (gpa >> 39) & 0x1FF; // We assume all GPAs are < 512GB, so pml4_idx is always 0 - link it to the PDPT. if (pml4[pml4_idx] == 0) pml4[pml4_idx] = X86_PDE64_PRESENT | X86_PDE64_RW | (X86_SYZOS_ADDR_PDP & PAGE_MASK); - uint64* pdpt = (uint64*)((uint64)host_mem + (pml4[0] & PAGE_MASK)); + uint64* pdpt = (uint64*)(host_mem + (pml4[0] & PAGE_MASK)); // PDPT Entry (Level 3). uint64 pdpt_idx = (gpa >> 30) & 0x1FF; @@ -318,7 +342,7 @@ static void map_4k_page(void* host_mem, uint64 gpa) if (*pd_addr_ptr == 0) *pd_addr_ptr = X86_PDE64_PRESENT | X86_PDE64_RW | (pd_phys_addr & PAGE_MASK); - uint64* pd = (uint64*)((uint64)host_mem + (*pd_addr_ptr & PAGE_MASK)); + uint64* pd = (uint64*)(host_mem + (*pd_addr_ptr & PAGE_MASK)); // PD Entry (Level 2). uint64 pd_idx = (gpa >> 21) & 0x1FF; @@ -338,7 +362,7 @@ static void map_4k_page(void* host_mem, uint64 gpa) if (*pt_addr_ptr == 0) *pt_addr_ptr = X86_PDE64_PRESENT | X86_PDE64_RW | (pt_phys_addr & PAGE_MASK); - uint64* pt = (uint64*)((uint64)host_mem + (*pt_addr_ptr & PAGE_MASK)); + uint64* pt = (uint64*)(host_mem + (*pt_addr_ptr & PAGE_MASK)); // PT Entry (Level 1). uint64 pt_idx = (gpa >> 12) & 0x1FF; @@ -348,7 +372,7 @@ static void map_4k_page(void* host_mem, uint64 gpa) pt[pt_idx] = (gpa & PAGE_MASK) | X86_PDE64_PRESENT | X86_PDE64_RW; } -static int map_4k_region(void* host_mem, uint64 gpa_start, int num_pages) +static int map_4k_region(uint64 host_mem, uint64 gpa_start, int num_pages) { for (int i = 0; i < num_pages; i++) map_4k_page(host_mem, gpa_start + (i * KVM_PAGE_SIZE)); @@ -357,27 +381,25 @@ static int map_4k_region(void* host_mem, uint64 gpa_start, int num_pages) // We assume a 4-level page table, in the future we could add support for // n-level if needed. -// Assuming host_mem is mapped at GPA 0x0. -static void setup_pg_table(void* host_mem) +static void setup_pg_table(struct kvm_syz_vm* vm) { - int total = 1024; + int total = vm->total_pages; + // Page tables are located in the first memory region starting at 0x0. + uint64 host_mem = (uint64)vm->gpa0_mem; // Zero-out all page table memory. // This includes the 4 levels (PML4, PDPT, PDs) and 3 PTs - memset((void*)((uint64)host_mem + X86_SYZOS_ADDR_PML4), 0, KVM_PAGE_SIZE); - memset((void*)((uint64)host_mem + X86_SYZOS_ADDR_PDP), 0, KVM_PAGE_SIZE); - memset((void*)((uint64)host_mem + X86_SYZOS_ADDR_PD), 0, KVM_PAGE_SIZE); - memset((void*)((uint64)host_mem + X86_SYZOS_ADDR_PD_IOAPIC), 0, KVM_PAGE_SIZE); - memset((void*)((uint64)host_mem + X86_SYZOS_ADDR_PT_LOW_MEM), 0, KVM_PAGE_SIZE); - memset((void*)((uint64)host_mem + X86_SYZOS_ADDR_PT_UNUSED_MEM), 0, 2 * KVM_PAGE_SIZE); - memset((void*)((uint64)host_mem + X86_SYZOS_ADDR_PT_IOAPIC), 0, KVM_PAGE_SIZE); + memset((void*)(host_mem + X86_SYZOS_ADDR_PML4), 0, KVM_PAGE_SIZE); + memset((void*)(host_mem + X86_SYZOS_ADDR_PDP), 0, KVM_PAGE_SIZE); + memset((void*)(host_mem + X86_SYZOS_ADDR_PD), 0, KVM_PAGE_SIZE); + memset((void*)(host_mem + X86_SYZOS_ADDR_PD_IOAPIC), 0, KVM_PAGE_SIZE); + memset((void*)(host_mem + X86_SYZOS_ADDR_PT_LOW_MEM), 0, KVM_PAGE_SIZE); + memset((void*)(host_mem + X86_SYZOS_ADDR_PT_UNUSED_MEM), 0, 2 * KVM_PAGE_SIZE); + memset((void*)(host_mem + X86_SYZOS_ADDR_PT_IOAPIC), 0, KVM_PAGE_SIZE); // Map all the regions defined in setup_vm() for (size_t i = 0; i < sizeof(syzos_mem_regions) / sizeof(syzos_mem_regions[0]); i++) total -= map_4k_region(host_mem, syzos_mem_regions[i].gpa, syzos_mem_regions[i].pages); map_4k_region(host_mem, X86_SYZOS_ADDR_UNUSED, total); - // Mapping for MMIO region should be present even though there is no - // corresponding physical memory. - map_4k_region(host_mem, X86_SYZOS_ADDR_EXIT, 1); } // A 64-bit GDT entry for a code or data segment. @@ -419,14 +441,14 @@ static void setup_gdt_64(struct gdt_entry* gdt) // This only sets up a 64-bit VCPU. // TODO: Should add support for other modes. -static void setup_gdt_ldt_pg(int cpufd, void* host_mem) +static void setup_gdt_ldt_pg(struct kvm_syz_vm* vm, int cpufd) { struct kvm_sregs sregs; ioctl(cpufd, KVM_GET_SREGS, &sregs); sregs.gdt.base = X86_SYZOS_ADDR_GDT; sregs.gdt.limit = 3 * sizeof(struct gdt_entry) - 1; - struct gdt_entry* gdt = (struct gdt_entry*)((uint64)host_mem + sregs.gdt.base); + struct gdt_entry* gdt = (struct gdt_entry*)((uint64)vm->host_mem + sregs.gdt.base); struct kvm_segment seg_cs64; memset(&seg_cs64, 0, sizeof(seg_cs64)); @@ -459,8 +481,8 @@ static void setup_gdt_ldt_pg(int cpufd, void* host_mem) setup_gdt_64(gdt); - syzos_setup_idt(&sregs, (char*)host_mem); - setup_pg_table(host_mem); + syzos_setup_idt(vm, &sregs); + setup_pg_table(vm); sregs.cr0 = X86_CR0_PE | X86_CR0_NE | X86_CR0_PG; sregs.cr4 |= X86_CR4_PAE | X86_CR4_OSFXSR; @@ -1052,17 +1074,15 @@ static void reset_cpu_regs(int cpufd, int cpu_id, size_t text_size) ioctl(cpufd, KVM_SET_REGS, ®s); } -static void install_user_code(int cpufd, void* user_text_slot, int cpu_id, const void* text, size_t text_size, void* host_mem) +static void install_user_code(struct kvm_syz_vm* vm, int cpufd, int cpu_id, const void* text, size_t text_size) { if ((cpu_id < 0) || (cpu_id >= KVM_MAX_VCPU)) return; - if (!user_text_slot) - return; if (text_size > KVM_PAGE_SIZE) text_size = KVM_PAGE_SIZE; - void* target = (void*)((uint64)user_text_slot + (KVM_PAGE_SIZE * cpu_id)); + void* target = (void*)((uint64)vm->user_text + (KVM_PAGE_SIZE * cpu_id)); memcpy(target, text, text_size); - setup_gdt_ldt_pg(cpufd, host_mem); + setup_gdt_ldt_pg(vm, cpufd); setup_cpuid(cpufd); reset_cpu_regs(cpufd, cpu_id, text_size); } @@ -1107,32 +1127,25 @@ static void install_syzos_code(void* host_mem, size_t mem_size) memcpy(host_mem, &__start_guest, size); } -static void setup_vm(int vmfd, void* host_mem, void** text_slot) +static void setup_vm(int vmfd, struct kvm_syz_vm* vm) { - // Guest virtual memory layout (must be in sync with executor/kvm.h): - // 0x00000000 - AMD64 data structures (20 pages, see kvm.h) - // 0x00030000 - SMRAM (10 pages) - // 0x00040000 - unmapped region to trigger a page faults for uexits etc. (1 page) - // 0x00041000 - writable region with KVM_MEM_LOG_DIRTY_PAGES to fuzz dirty ring (2 pages) - // 0x00050000 - user code (4 pages) - // 0x00054000 - executor guest code (4 pages) - // 0000058000 - scratch memory for code generated at runtime (1 page) - // 0xfec00000 - IOAPIC (1 page) - struct addr_size allocator = {.addr = host_mem, .size = KVM_GUEST_MEM_SIZE}; + struct addr_size allocator = {.addr = vm->host_mem, .size = vm->total_pages * KVM_PAGE_SIZE}; int slot = 0; // Slot numbers do not matter, they just have to be different. for (size_t i = 0; i < sizeof(syzos_mem_regions) / sizeof(syzos_mem_regions[0]); i++) { const struct mem_region* r = &syzos_mem_regions[i]; + if (r->flags & MEM_REGION_FLAG_NO_HOST_MEM) + continue; struct addr_size next = alloc_guest_mem(&allocator, r->pages * KVM_PAGE_SIZE); uint32 flags = 0; if (r->flags & MEM_REGION_FLAG_DIRTY_LOG) flags |= KVM_MEM_LOG_DIRTY_PAGES; if (r->flags & MEM_REGION_FLAG_READONLY) flags |= KVM_MEM_READONLY; - if (r->flags & MEM_REGION_FLAG_USER_CODE) { - if (text_slot) - *text_slot = next.addr; - } + if (r->flags & MEM_REGION_FLAG_USER_CODE) + vm->user_text = next.addr; + if (r->flags & MEM_REGION_FLAG_GPA0) + vm->gpa0_mem = next.addr; if (r->flags & MEM_REGION_FLAG_EXECUTOR_CODE) install_syzos_code(next.addr, next.size); vm_set_user_memory_region(vmfd, slot++, flags, r->gpa, next.size, (uintptr_t)next.addr); @@ -1144,29 +1157,17 @@ static void setup_vm(int vmfd, void* host_mem, void** text_slot) } #endif -#if SYZ_EXECUTOR || __NR_syz_kvm_setup_syzos_vm || __NR_syz_kvm_add_vcpu -struct kvm_syz_vm { - int vmfd; - int next_cpu_id; - void* user_text; - void* host_mem; -}; -#endif - #if SYZ_EXECUTOR || __NR_syz_kvm_setup_syzos_vm static long syz_kvm_setup_syzos_vm(volatile long a0, volatile long a1) { const int vmfd = a0; void* host_mem = (void*)a1; - - void* user_text_slot = NULL; struct kvm_syz_vm* ret = (struct kvm_syz_vm*)host_mem; - host_mem = (void*)((uint64)host_mem + KVM_PAGE_SIZE); - setup_vm(vmfd, host_mem, &user_text_slot); + ret->host_mem = (void*)((uint64)host_mem + KVM_PAGE_SIZE); + ret->total_pages = KVM_GUEST_PAGES - 1; + setup_vm(vmfd, ret); ret->vmfd = vmfd; ret->next_cpu_id = 0; - ret->user_text = user_text_slot; - ret->host_mem = host_mem; return (long)ret; } #endif @@ -1193,7 +1194,7 @@ static long syz_kvm_add_vcpu(volatile long a0, volatile long a1) return -1; // Only increment next_cpu_id if CPU creation succeeded. vm->next_cpu_id++; - install_user_code(cpufd, vm->user_text, cpu_id, text, text_size, vm->host_mem); + install_user_code(vm, cpufd, cpu_id, text, text_size); return cpufd; } #endif diff --git a/executor/kvm.h b/executor/kvm.h index 57d8aa490..8e5dc62be 100644 --- a/executor/kvm.h +++ b/executor/kvm.h @@ -174,7 +174,8 @@ #define KVM_MAX_VCPU 4 #define KVM_PAGE_SIZE (1 << 12) -#define KVM_GUEST_MEM_SIZE (1024 * KVM_PAGE_SIZE) +#define KVM_GUEST_PAGES 1024 +#define KVM_GUEST_MEM_SIZE (KVM_GUEST_PAGES * KVM_PAGE_SIZE) #define SZ_4K 0x00001000 #define SZ_64K 0x00010000 #define GENMASK_ULL(h, l) \ |
