diff options
| author | Alexander Potapenko <glider@google.com> | 2024-09-24 15:23:14 +0200 |
|---|---|---|
| committer | Alexander Potapenko <glider@google.com> | 2024-09-25 09:05:57 +0000 |
| commit | 4b1eded1f91812d576538f106b57352d25a6b484 (patch) | |
| tree | 24d63abea11136d185dc9eba0a0ab5eb93f20046 /executor | |
| parent | 7c9588a40bd882410049d1e772de2452934a7eaf (diff) | |
executor: arm64: sys/linux: implement syz_kvm_setup_syzos_vm and syz_kvm_add_vcpu
The old syz_kvm_setup_cpu() API mixed together VM and VCPU setup, making it
harder to create and fuzz two VCPUs in the same VM.
Introduce two new pseudo-syscalls, syz_kvm_setup_syzos_vm() and syz_kvm_add_vcpu(),
that will simplify this task.
syz_kvm_setup_syzos_vm() takes a VM file descriptor, performs VM setup
(allocates guest memory and installs SYZOS code into it) and returns a
new kvm_syz_vm resource, which is in fact a pointer to `struct kvm_syz_vm`
encapsulating VM-specific data in the C code.
syz_kvm_add_vcpu() takes the VM ID denoted by kvm_syz_vm and creates a
new VCPU within that VM with a proper CPU number. It then stores the
fuzzer-supplied SYZOS API sequence into the corresponding part (indexed by
CPU number) of the VM memory slot, and sets up the CPU registers to interpret
that sequence.
The new pseudo-syscall let the fuzzer create independent CPUs that run different
code sequences without interfering with each other.
Diffstat (limited to 'executor')
| -rw-r--r-- | executor/common_kvm_arm64.h | 171 | ||||
| -rw-r--r-- | executor/common_kvm_arm64_syzos.h | 4 | ||||
| -rw-r--r-- | executor/common_linux.h | 14 |
3 files changed, 135 insertions, 54 deletions
diff --git a/executor/common_kvm_arm64.h b/executor/common_kvm_arm64.h index b90c0c48c..ea1305ed7 100644 --- a/executor/common_kvm_arm64.h +++ b/executor/common_kvm_arm64.h @@ -4,18 +4,26 @@ // This file is shared between executor and csource package. // Implementation of syz_kvm_setup_cpu pseudo-syscall. +#include <sys/mman.h> #include "kvm.h" -#if SYZ_EXECUTOR || __NR_syz_kvm_setup_cpu +#if SYZ_EXECUTOR || __NR_syz_kvm_setup_cpu || __NR_syz_kvm_add_vcpu || __NR_syz_kvm_setup_syzos_vm +#include "common_kvm_arm64_syzos.h" + +#define SYZ_KVM_MAX_VCPU 4 +#define SYZ_KVM_PAGE_SIZE (4 << 10) +#define SYZ_KVM_GUEST_MEM_SIZE (24 * SYZ_KVM_PAGE_SIZE) + +#endif +#if SYZ_EXECUTOR || __NR_syz_kvm_setup_cpu || __NR_syz_kvm_add_vcpu // Register encodings from https://docs.kernel.org/virt/kvm/api.html. #define KVM_ARM64_REGS_X0 0x6030000000100000UL #define KVM_ARM64_REGS_X1 0x6030000000100002UL #define KVM_ARM64_REGS_PC 0x6030000000100040UL #define KVM_ARM64_REGS_SP_EL1 0x6030000000100044UL -#include "common_kvm_arm64_syzos.h" struct kvm_text { uintptr_t typ; const void* text; @@ -26,26 +34,9 @@ struct kvm_opt { uint64 typ; uint64 val; }; +#endif -// Call KVM_SET_USER_MEMORY_REGION for the given pages. -static void vm_set_user_memory_region(int vmfd, uint32 slot, uint32 flags, uint64 guest_phys_addr, uint64 memory_size, uint64 userspace_addr) -{ - struct kvm_userspace_memory_region memreg; - memreg.slot = slot; - memreg.flags = flags; - memreg.guest_phys_addr = guest_phys_addr; - memreg.memory_size = memory_size; - memreg.userspace_addr = userspace_addr; - ioctl(vmfd, KVM_SET_USER_MEMORY_REGION, &memreg); -} - -// Set the value of the specified register. -static void vcpu_set_reg(int vcpu_fd, uint64 id, uint64 val) -{ - struct kvm_one_reg reg = {.id = id, .addr = (uint64)&val}; - ioctl(vcpu_fd, KVM_SET_ONE_REG, ®); -} - +#if SYZ_EXECUTOR || __NR_syz_kvm_setup_cpu || __NR_syz_kvm_setup_syzos_vm struct addr_size { void* addr; size_t size; @@ -64,18 +55,20 @@ static struct addr_size alloc_guest_mem(struct addr_size* free, size_t size) return ret; } -struct api_fn { - int index; - void* fn; -}; - -#define SYZ_KVM_MAX_VCPU 4 -#define SYZ_KVM_PAGE_SIZE (4 << 10) +// Call KVM_SET_USER_MEMORY_REGION for the given pages. +static void vm_set_user_memory_region(int vmfd, uint32 slot, uint32 flags, uint64 guest_phys_addr, uint64 memory_size, uint64 userspace_addr) +{ + struct kvm_userspace_memory_region memreg; + memreg.slot = slot; + memreg.flags = flags; + memreg.guest_phys_addr = guest_phys_addr; + memreg.memory_size = memory_size; + memreg.userspace_addr = userspace_addr; + ioctl(vmfd, KVM_SET_USER_MEMORY_REGION, &memreg); +} static void setup_vm(int vmfd, void* host_mem, void** text_slot) { - const uintptr_t guest_mem_size = 24 * SYZ_KVM_PAGE_SIZE; - // Guest physical memory layout (must be in sync with executor/kvm.h): // 0x00000000 - unused pages // 0x08000000 - GICv3 distributor region (MMIO, no memory allocated) @@ -86,7 +79,7 @@ static void setup_vm(int vmfd, void* host_mem, void** text_slot) // 0xeeee8000 - executor guest code (4 pages) // 0xeeef0000 - scratch memory for code generated at runtime (1 page) // 0xffff1000 - EL1 stack (1 page) - struct addr_size allocator = {.addr = host_mem, .size = guest_mem_size}; + struct addr_size allocator = {.addr = host_mem, .size = SYZ_KVM_GUEST_MEM_SIZE}; int slot = 0; // Slot numbers do not matter, they just have to be different. struct addr_size host_text = alloc_guest_mem(&allocator, 4 * SYZ_KVM_PAGE_SIZE); @@ -111,6 +104,15 @@ static void setup_vm(int vmfd, void* host_mem, void** text_slot) next = alloc_guest_mem(&allocator, allocator.size); vm_set_user_memory_region(vmfd, slot++, 0, 0, next.size, (uintptr_t)next.addr); } +#endif + +#if SYZ_EXECUTOR || __NR_syz_kvm_setup_cpu || __NR_syz_kvm_add_vcpu +// Set the value of the specified register. +static void vcpu_set_reg(int vcpu_fd, uint64 id, uint64 val) +{ + struct kvm_one_reg reg = {.id = id, .addr = (uint64)&val}; + ioctl(vcpu_fd, KVM_SET_ONE_REG, ®); +} // Set up CPU registers. static void reset_cpu_regs(int cpufd, int cpu_id, size_t text_size) @@ -136,6 +138,32 @@ static void install_user_code(int cpufd, void* user_text_slot, int cpu_id, const reset_cpu_regs(cpufd, cpu_id, text_size); } +static void setup_cpu_with_opts(int vmfd, int cpufd, const struct kvm_opt* opt, int opt_count) +{ + uint32 features = 0; + if (opt_count > 1) + opt_count = 1; + for (int i = 0; i < opt_count; i++) { + uint64 typ = opt[i].typ; + uint64 val = opt[i].val; + switch (typ) { + case 1: + features = val; + break; + } + } + + struct kvm_vcpu_init init; + // Queries KVM for preferred CPU target type. + ioctl(vmfd, KVM_ARM_PREFERRED_TARGET, &init); + init.features[0] = features; + // Use the modified struct kvm_vcpu_init to initialize the virtual CPU. + ioctl(cpufd, KVM_ARM_VCPU_INIT, &init); +} + +#endif + +#if SYZ_EXECUTOR || __NR_syz_kvm_setup_cpu // syz_kvm_setup_cpu(fd fd_kvmvm, cpufd fd_kvmcpu, usermem vma[24], text ptr[in, array[kvm_text, 1]], ntext len[text], flags flags[kvm_setup_flags], opts ptr[in, array[kvm_setup_opt, 0:2]], nopt len[opts]) static volatile long syz_kvm_setup_cpu(volatile long a0, volatile long a1, volatile long a2, volatile long a3, volatile long a4, volatile long a5, volatile long a6, volatile long a7) { @@ -156,30 +184,10 @@ static volatile long syz_kvm_setup_cpu(volatile long a0, volatile long a1, volat const void* text = text_array_ptr[0].text; size_t text_size = text_array_ptr[0].size; (void)text_type; - (void)opt_array_ptr; - - uint32 features = 0; - if (opt_count > 1) - opt_count = 1; - for (uintptr_t i = 0; i < opt_count; i++) { - uint64 typ = opt_array_ptr[i].typ; - uint64 val = opt_array_ptr[i].val; - switch (typ) { - case 1: - features = val; - break; - } - } void* user_text_slot = NULL; setup_vm(vmfd, host_mem, &user_text_slot); - - struct kvm_vcpu_init init; - // Queries KVM for preferred CPU target type. - ioctl(vmfd, KVM_ARM_PREFERRED_TARGET, &init); - init.features[0] = features; - // Use the modified struct kvm_vcpu_init to initialize the virtual CPU. - ioctl(cpufd, KVM_ARM_VCPU_INIT, &init); + setup_cpu_with_opts(vmfd, cpufd, opt_array_ptr, opt_count); // Assume CPU is 0. install_user_code(cpufd, user_text_slot, 0, text, text_size); @@ -187,6 +195,65 @@ static volatile long syz_kvm_setup_cpu(volatile long a0, volatile long a1, volat } #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; +}; +#endif + +#if SYZ_EXECUTOR || __NR_syz_kvm_setup_syzos_vm + +// Allocate a page using a syscall, as we may not have malloc(). +// This page will be leaked, its address will be used as an opaque resource ID. +static void* leaky_alloc_page() +{ + return mmap(NULL, 0x1000, PROT_READ | PROT_WRITE, MAP_ANON | MAP_PRIVATE, -1, 0); +} + +static long syz_kvm_setup_syzos_vm(volatile long a0) +{ + const int vmfd = a0; + + struct kvm_syz_vm* ret = (struct kvm_syz_vm*)leaky_alloc_page(); + if ((long)ret == -1) + return -1; + + void* user_text_slot = NULL; + void* host_mem = mmap(NULL, SYZ_KVM_GUEST_MEM_SIZE, PROT_READ | PROT_WRITE, MAP_ANON | MAP_PRIVATE, -1, 0); + setup_vm(vmfd, host_mem, &user_text_slot); + ret->vmfd = vmfd; + ret->next_cpu_id = 0; + ret->user_text = user_text_slot; + return (long)ret; +} +#endif + +#if SYZ_EXECUTOR || __NR_syz_kvm_add_vcpu +static long syz_kvm_add_vcpu(volatile long a0, volatile long a1, volatile long a2, volatile long a3) +{ + struct kvm_syz_vm* vm = (struct kvm_syz_vm*)a0; + struct kvm_text* utext = (struct kvm_text*)a1; + const void* text = utext->text; + size_t text_size = utext->size; + const struct kvm_opt* const opt_array_ptr = (struct kvm_opt*)a2; + uintptr_t opt_count = a3; + + if (vm->next_cpu_id == SYZ_KVM_MAX_VCPU) + return -1; + int cpu_id = vm->next_cpu_id; + int cpufd = ioctl(vm->vmfd, KVM_CREATE_VCPU, cpu_id); + if (cpufd == -1) + return -1; + // Only increment next_cpu_id if CPU creation succeeded. + vm->next_cpu_id++; + setup_cpu_with_opts(vm->vmfd, cpufd, opt_array_ptr, opt_count); + install_user_code(cpufd, vm->user_text, cpu_id, text, text_size); + return cpufd; +} +#endif + #if SYZ_EXECUTOR || __NR_syz_kvm_vgic_v3_setup static int kvm_set_device_attr(int dev_fd, uint32 group, uint64 attr, void* val) { diff --git a/executor/common_kvm_arm64_syzos.h b/executor/common_kvm_arm64_syzos.h index ebfed175c..a22c0651c 100644 --- a/executor/common_kvm_arm64_syzos.h +++ b/executor/common_kvm_arm64_syzos.h @@ -79,7 +79,9 @@ typedef enum { // Main guest function that performs necessary setup and passes the control to the user-provided // payload. -GUEST_CODE static void guest_main(uint64 size, uint64 cpu) +__attribute__((used)) +GUEST_CODE static void +guest_main(uint64 size, uint64 cpu) { uint64 addr = ARM64_ADDR_USER_CODE + cpu * 0x1000; diff --git a/executor/common_linux.h b/executor/common_linux.h index 30d29cb05..c93727058 100644 --- a/executor/common_linux.h +++ b/executor/common_linux.h @@ -3186,7 +3186,7 @@ error_clear_loop: } #endif -#if SYZ_EXECUTOR || __NR_syz_kvm_setup_cpu || __NR_syz_kvm_vgic_v3_setup +#if SYZ_EXECUTOR || __NR_syz_kvm_setup_cpu || __NR_syz_kvm_vgic_v3_setup || __NR_syz_kvm_setup_syzos_vm || __NR_syz_kvm_add_vcpu // KVM is not yet supported on RISC-V #if !GOARCH_riscv64 && !GOARCH_arm #include <errno.h> @@ -3215,6 +3215,18 @@ static long syz_kvm_vgic_v3_setup(volatile long a0, volatile long a1, volatile l return 0; } #endif +#if !GOARCH_arm64 && (SYZ_EXECUTOR || __NR_syz_kvm_add_vcpu) +static long syz_kvm_add_vcpu(volatile long a0, volatile long a1, volatile long a2, volatile long a3) +{ + return 0; +} +#endif +#if !GOARCH_arm64 && (SYZ_EXECUTOR || __NR_syz_kvm_setup_syzos_vm) +static long syz_kvm_setup_syzos_vm(volatile long a0) +{ + return 0; +} +#endif #endif #endif |
