diff options
| author | Alexander Potapenko <glider@google.com> | 2025-09-26 10:20:49 +0200 |
|---|---|---|
| committer | Alexander Potapenko <glider@google.com> | 2025-10-17 06:51:20 +0000 |
| commit | c7354acdafe9a5bdf11bafed36b695588185a198 (patch) | |
| tree | 3a4afa9ae33ddf222d77a94ec3877bcaeae029d8 /executor | |
| parent | 6ca4530067ac25a78291b176b6d3dbe6ba592d15 (diff) | |
executor: more robust x86 page table creation in SYZOS
Provide map_4k_region() to ease page table creation for different
regions.
While at it, also move the stack from 0x0 to 0x90000.
Diffstat (limited to 'executor')
| -rw-r--r-- | executor/common_kvm_amd64.h | 111 | ||||
| -rw-r--r-- | executor/kvm.h | 22 |
2 files changed, 117 insertions, 16 deletions
diff --git a/executor/common_kvm_amd64.h b/executor/common_kvm_amd64.h index 39804b4b8..4792c47a1 100644 --- a/executor/common_kvm_amd64.h +++ b/executor/common_kvm_amd64.h @@ -220,21 +220,101 @@ struct kvm_opt { #if SYZ_EXECUTOR || __NR_syz_kvm_setup_cpu || __NR_syz_kvm_add_vcpu #define PAGE_MASK GENMASK_ULL(51, 12) +static void map_4k_page(void* host_mem, uint64 gpa) +{ + uint64* pml4 = (uint64*)((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)); + + // PDPT Entry (Level 3). + uint64 pdpt_idx = (gpa >> 30) & 0x1FF; + uint64* pd_addr_ptr = &pdpt[pdpt_idx]; + uint64 pd_phys_addr = 0; + + // Determine which Page Directory (PD) to use based on the address + if (gpa >= X86_SYZOS_ADDR_IOAPIC) { + // High-memory IOAPIC region (0xfec00000). + // PDPT index will be 3 (for 0xC0000000 - 0xFFFFFFFFF). + pd_phys_addr = X86_SYZOS_ADDR_PD_IOAPIC; + } else { + // Low-memory region (< 1GB). + // PDPT index will be 0 (for 0x0 - 0x3FFFFFFF). + pd_phys_addr = X86_SYZOS_ADDR_PD; + } + 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)); + + // PD Entry (Level 2). + uint64 pd_idx = (gpa >> 21) & 0x1FF; + uint64* pt_addr_ptr = &pd[pd_idx]; + uint64 pt_phys_addr = 0; + + // Determine which Page Table (PT) to use. + if (gpa >= X86_SYZOS_ADDR_IOAPIC) { + pt_phys_addr = X86_SYZOS_ADDR_PT_IOAPIC; + } else if (gpa >= X86_SYZOS_ADDR_UNUSED) { + const uint64 unused_base_pd_idx = (X86_SYZOS_ADDR_UNUSED >> 21) & 0x1FF; + const uint64 gpa_pd_idx = (gpa >> 21) & 0x1FF; + pt_phys_addr = X86_SYZOS_ADDR_PT_UNUSED_MEM + (gpa_pd_idx - unused_base_pd_idx) * KVM_PAGE_SIZE; + } else { + pt_phys_addr = X86_SYZOS_ADDR_PT_LOW_MEM; + } + 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)); + + // PT Entry (Level 1). + uint64 pt_idx = (gpa >> 12) & 0x1FF; + + // Set the final 4KB page table entry to map the GPA + // This is an identity map: GPA -> 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) +{ + for (int i = 0; i < num_pages; i++) + map_4k_page(host_mem, gpa_start + (i * KVM_PAGE_SIZE)); + return 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) { - uint64* pml4 = (uint64*)((uint64)host_mem + X86_ADDR_PML4); - uint64* pdp = (uint64*)((uint64)host_mem + X86_ADDR_PDP); - uint64* pd = (uint64*)((uint64)host_mem + X86_ADDR_PD); - uint64* pd_ioapic = (uint64*)((uint64)host_mem + X86_ADDR_PD_IOAPIC); - - pml4[0] = X86_PDE64_PRESENT | X86_PDE64_RW | (X86_ADDR_PDP & PAGE_MASK); - pdp[0] = X86_PDE64_PRESENT | X86_PDE64_RW | (X86_ADDR_PD & PAGE_MASK); - pdp[3] = X86_PDE64_PRESENT | X86_PDE64_RW | (X86_ADDR_PD_IOAPIC & PAGE_MASK); - - pd[0] = X86_PDE64_PRESENT | X86_PDE64_RW | X86_PDE64_PS; - pd_ioapic[502] = X86_PDE64_PRESENT | X86_PDE64_RW | X86_PDE64_PS; + int total = 1024; + // 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); + + // Map all the regions defined in setup_vm() + total -= map_4k_region(host_mem, X86_SYZOS_ADDR_ZERO, 20); + total -= map_4k_region(host_mem, X86_SYZOS_ADDR_SMRAM, 10); + total -= map_4k_region(host_mem, X86_SYZOS_ADDR_DIRTY_PAGES, 2); + total -= map_4k_region(host_mem, X86_SYZOS_ADDR_USER_CODE, KVM_MAX_VCPU); + total -= map_4k_region(host_mem, X86_SYZOS_ADDR_EXECUTOR_CODE, 4); + total -= map_4k_region(host_mem, X86_SYZOS_ADDR_SCRATCH_CODE, 1); + total -= map_4k_region(host_mem, X86_SYZOS_ADDR_STACK_BOTTOM, 1); + total -= map_4k_region(host_mem, X86_SYZOS_ADDR_IOAPIC, 1); + 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); } // This only sets up a 64-bit VCPU. @@ -966,7 +1046,7 @@ static void install_syzos_code(void* host_mem, size_t mem_size) static void setup_vm(int vmfd, void* host_mem, void** text_slot) { // Guest virtual memory layout (must be in sync with executor/kvm.h): - // 0x00000000 - AMD64 data structures (10 pages, see 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) @@ -979,8 +1059,8 @@ static void setup_vm(int vmfd, void* host_mem, void** text_slot) // This *needs* to be the first allocation to avoid passing pointers // around for the gdt/ldt/page table setup. - struct addr_size next = alloc_guest_mem(&allocator, 10 * KVM_PAGE_SIZE); - vm_set_user_memory_region(vmfd, slot++, 0, 0, next.size, (uintptr_t)next.addr); + struct addr_size next = alloc_guest_mem(&allocator, 20 * KVM_PAGE_SIZE); + vm_set_user_memory_region(vmfd, slot++, 0, X86_SYZOS_ADDR_ZERO, next.size, (uintptr_t)next.addr); next = alloc_guest_mem(&allocator, 10 * KVM_PAGE_SIZE); vm_set_user_memory_region(vmfd, slot++, 0, X86_SYZOS_ADDR_SMRAM, next.size, (uintptr_t)next.addr); @@ -1001,6 +1081,9 @@ static void setup_vm(int vmfd, void* host_mem, void** text_slot) vm_set_user_memory_region(vmfd, slot++, 0, X86_SYZOS_ADDR_SCRATCH_CODE, next.size, (uintptr_t)next.addr); next = alloc_guest_mem(&allocator, KVM_PAGE_SIZE); + vm_set_user_memory_region(vmfd, slot++, 0, X86_SYZOS_ADDR_STACK_BOTTOM, next.size, (uintptr_t)next.addr); + + next = alloc_guest_mem(&allocator, KVM_PAGE_SIZE); vm_set_user_memory_region(vmfd, slot++, 0, X86_SYZOS_ADDR_IOAPIC, next.size, (uintptr_t)next.addr); // Map the remaining pages at an unused address. diff --git a/executor/kvm.h b/executor/kvm.h index 79dddc486..28391db57 100644 --- a/executor/kvm.h +++ b/executor/kvm.h @@ -34,6 +34,24 @@ #define X86_ADDR_VAR_USER_CODE2 0x9120 // x86 SYZOS definitions. +// Zero page (0x0 - 0xfff) is deliberately unused. +#define X86_SYZOS_ADDR_ZERO 0x0 +#define X86_SYZOS_ADDR_GDT 0x1000 +// PML4 for GPAs 0x0 - 0xffffffffffff. +#define X86_SYZOS_ADDR_PML4 0x2000 +// PDP for GPAs 0x0 - 0x7fffffffff. +#define X86_SYZOS_ADDR_PDP 0x3000 +// Lowmem PD for GPAs 0x0 - 0x3fffffff. +#define X86_SYZOS_ADDR_PD 0x4000 +// IOAPIC PD for GPAs 0xc0000000 - 0xffffffff. +#define X86_SYZOS_ADDR_PD_IOAPIC 0x5000 +// Lowmem PT for GPAs 0x000000 - 0x1fffff. +#define X86_SYZOS_ADDR_PT_LOW_MEM 0x6000 +// Two PTs for unused memory for GPAs 0x200000 - 0x3fffff. +#define X86_SYZOS_ADDR_PT_UNUSED_MEM 0x7000 +// IOAPIC PT for GPAs 0xfed00000 - 0xfedfffff. +#define X86_SYZOS_ADDR_PT_IOAPIC 0x9000 + #define X86_SYZOS_ADDR_SMRAM 0x30000 // Write to this page to trigger a page fault and stop KVM_RUN. #define X86_SYZOS_ADDR_EXIT 0x40000 @@ -43,8 +61,8 @@ #define X86_SYZOS_ADDR_USER_CODE 0x50000 #define X86_SYZOS_ADDR_EXECUTOR_CODE 0x54000 #define X86_SYZOS_ADDR_SCRATCH_CODE 0x58000 -#define X86_SYZOS_ADDR_STACK_BOTTOM 0x0 -#define X86_SYZOS_ADDR_STACK0 0xf80 +#define X86_SYZOS_ADDR_STACK_BOTTOM 0x90000 +#define X86_SYZOS_ADDR_STACK0 0x90f80 #define X86_SYZOS_ADDR_UNUSED 0x200000 #define X86_SYZOS_ADDR_IOAPIC 0xfec00000 |
