diff options
| author | Alexander Potapenko <glider@google.com> | 2024-07-18 13:35:15 +0200 |
|---|---|---|
| committer | Alexander Potapenko <glider@google.com> | 2024-07-29 15:29:47 +0000 |
| commit | 3fac346ac6e2c0adadc6a268582fc50fc07f16f2 (patch) | |
| tree | 0a3d9397dc069cac1c058b67c17b0eb30d5ca18c | |
| parent | 98966d2a88e63c2ae4ffbce0c15b18a4cfef0991 (diff) | |
executor: arm64: more flexible physical page allocation
Refactor phys page allocation in syz_kvm_setup_cpu$arm64 to prepare for
more address ranges.
Load user-supplied code at ARM64_ADDR_USER_CODE and allocate EL1 stack
at ARM64_ADDR_EL1_STACK_BOTTOM.
| -rw-r--r-- | executor/common_kvm_arm64.h | 89 | ||||
| -rw-r--r-- | executor/kvm.h | 3 |
2 files changed, 78 insertions, 14 deletions
diff --git a/executor/common_kvm_arm64.h b/executor/common_kvm_arm64.h index 6369cdd81..b4de3cfcf 100644 --- a/executor/common_kvm_arm64.h +++ b/executor/common_kvm_arm64.h @@ -5,6 +5,12 @@ // Implementation of syz_kvm_setup_cpu pseudo-syscall. +#include "kvm.h" + +// Register encodings from https://docs.kernel.org/virt/kvm/api.html. +#define KVM_ARM64_REGS_PC 0x6030000000100040UL +#define KVM_ARM64_REGS_SP_EL1 0x6030000000100044UL + struct kvm_text { uintptr_t typ; const void* text; @@ -16,6 +22,51 @@ struct kvm_opt { uint64 val; }; +// 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, ®); +} + +struct addr_size { + void* addr; + size_t size; +}; + +static struct addr_size alloc_guest_mem(struct addr_size* free, size_t size) +{ + struct addr_size ret = {.addr = NULL, .size = 0}; + + if (free->size < size) + return ret; + ret.addr = free->addr; + ret.size = size; + free->addr = (void*)((char*)free->addr + size); + free->size -= size; + return ret; +} + +static void fill_with_ret(void* addr, int size) +{ + uint32* insn = (uint32*)addr; + + for (int i = 0; i < size / 4; i++) + insn[i] = 0xd65f03c0; // RET +} + // 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) { @@ -32,13 +83,12 @@ static volatile long syz_kvm_setup_cpu(volatile long a0, volatile long a1, volat (void)opt_count; const uintptr_t page_size = 4 << 10; - const uintptr_t guest_mem = 0; const uintptr_t guest_mem_size = 24 * page_size; (void)text_count; // fuzzer can spoof count and we need just 1 text, so ignore text_count int text_type = text_array_ptr[0].typ; const void* text = text_array_ptr[0].text; - int text_size = text_array_ptr[0].size; + size_t text_size = text_array_ptr[0].size; (void)text_type; (void)opt_array_ptr; @@ -55,15 +105,26 @@ static volatile long syz_kvm_setup_cpu(volatile long a0, volatile long a1, volat } } - for (uintptr_t i = 0; i < guest_mem_size / page_size; i++) { - struct kvm_userspace_memory_region memreg; - memreg.slot = i; - memreg.flags = 0; // can be KVM_MEM_LOG_DIRTY_PAGES | KVM_MEM_READONLY - memreg.guest_phys_addr = guest_mem + i * page_size; - memreg.memory_size = page_size; - memreg.userspace_addr = (uintptr_t)host_mem + i * page_size; - ioctl(vmfd, KVM_SET_USER_MEMORY_REGION, &memreg); - } + // Guest physical memory layout: + // 0x00000000 - unused pages + // 0xeeee0000 - user code (1 page) + // 0xffff1000 - EL1 stack (1 page) + struct addr_size allocator = {.addr = host_mem, .size = guest_mem_size}; + int slot = 0; // Slot numbers do not matter, they just have to be different. + + struct addr_size next = alloc_guest_mem(&allocator, page_size); + // Fill the guest code page with RET instructions to be on the safe side. + fill_with_ret(next.addr, next.size); + if (text_size > next.size) + text_size = next.size; + memcpy(next.addr, text, text_size); + vm_set_user_memory_region(vmfd, slot++, KVM_MEM_READONLY, ARM64_ADDR_USER_CODE, next.size, (uintptr_t)next.addr); + next = alloc_guest_mem(&allocator, page_size); + vm_set_user_memory_region(vmfd, slot++, 0, ARM64_ADDR_EL1_STACK_BOTTOM, next.size, (uintptr_t)next.addr); + + // Map the remaining pages at address 0. + next = alloc_guest_mem(&allocator, allocator.size); + vm_set_user_memory_region(vmfd, slot++, 0, 0, next.size, (uintptr_t)next.addr); struct kvm_vcpu_init init; // Queries KVM for preferred CPU target type. @@ -72,9 +133,9 @@ static volatile long syz_kvm_setup_cpu(volatile long a0, volatile long a1, volat // Use the modified struct kvm_vcpu_init to initialize the virtual CPU. ioctl(cpufd, KVM_ARM_VCPU_INIT, &init); - if (text_size > 1000) - text_size = 1000; - memcpy(host_mem, text, text_size); + // Set up CPU registers. + vcpu_set_reg(cpufd, KVM_ARM64_REGS_PC, ARM64_ADDR_USER_CODE); + vcpu_set_reg(cpufd, KVM_ARM64_REGS_SP_EL1, ARM64_ADDR_EL1_STACK_BOTTOM + page_size - 128); return 0; } diff --git a/executor/kvm.h b/executor/kvm.h index 18b88d1f9..217ce24ff 100644 --- a/executor/kvm.h +++ b/executor/kvm.h @@ -75,3 +75,6 @@ #define NEXT_INSN $0xbadc0de #define PREFIX_SIZE 0xba1d + +#define ARM64_ADDR_USER_CODE 0xeeee0000 +#define ARM64_ADDR_EL1_STACK_BOTTOM 0xffff1000 |
