diff options
| -rw-r--r-- | executor/common_kvm_arm64.h | 18 | ||||
| -rw-r--r-- | executor/common_kvm_arm64_syzos.h | 66 | ||||
| -rw-r--r-- | executor/kvm.h | 4 | ||||
| -rw-r--r-- | sys/linux/dev_kvm.txt | 24 |
4 files changed, 98 insertions, 14 deletions
diff --git a/executor/common_kvm_arm64.h b/executor/common_kvm_arm64.h index ba02e244b..e6bb2b665 100644 --- a/executor/common_kvm_arm64.h +++ b/executor/common_kvm_arm64.h @@ -9,6 +9,7 @@ #include "kvm.h" // Register encodings from https://docs.kernel.org/virt/kvm/api.html. +#define KVM_ARM64_REGS_X0 0x6030000000100000UL #define KVM_ARM64_REGS_PC 0x6030000000100040UL #define KVM_ARM64_REGS_SP_EL1 0x6030000000100044UL @@ -60,13 +61,10 @@ static struct addr_size alloc_guest_mem(struct addr_size* free, size_t 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 -} +struct api_fn { + int index; + void* fn; +}; // 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) @@ -108,6 +106,7 @@ static volatile long syz_kvm_setup_cpu(volatile long a0, volatile long a1, volat // Guest physical memory layout: // 0x00000000 - unused pages + // 0xdddd0000 - unmapped region to trigger a page faults for uexits etc. (1 page) // 0xeeee0000 - user code (1 page) // 0xeeee8000 - executor guest code (4 pages) // 0xffff1000 - EL1 stack (1 page) @@ -119,12 +118,11 @@ static volatile long syz_kvm_setup_cpu(volatile long a0, volatile long a1, volat vm_set_user_memory_region(vmfd, slot++, KVM_MEM_READONLY, ARM64_ADDR_EXECUTOR_CODE, host_text.size, (uintptr_t)host_text.addr); 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); @@ -143,6 +141,8 @@ static volatile long syz_kvm_setup_cpu(volatile long a0, volatile long a1, volat // PC points to the relative offset of guest_main() within the guest code. vcpu_set_reg(cpufd, KVM_ARM64_REGS_PC, ARM64_ADDR_EXECUTOR_CODE + ((uint64)guest_main - (uint64)&__start_guest)); vcpu_set_reg(cpufd, KVM_ARM64_REGS_SP_EL1, ARM64_ADDR_EL1_STACK_BOTTOM + page_size - 128); + // Pass parameters to guest_main(). + vcpu_set_reg(cpufd, KVM_ARM64_REGS_X0, text_size); return 0; } diff --git a/executor/common_kvm_arm64_syzos.h b/executor/common_kvm_arm64_syzos.h index b9edf0069..022378b34 100644 --- a/executor/common_kvm_arm64_syzos.h +++ b/executor/common_kvm_arm64_syzos.h @@ -11,10 +11,70 @@ // Start/end of the guest section. extern char *__start_guest, *__stop_guest; +typedef enum { + SYZOS_API_UEXIT, + SYZOS_API_CODE, + SYZOS_API_STOP, // Must be the last one +} syzos_api_id; + +struct api_call_header { + uint64 call; + uint64 size; +}; + +struct api_call_uexit { + struct api_call_header header; + uint64 exit_code; +}; + +struct api_call_code { + struct api_call_header header; + uint32 insns[]; +}; + +void guest_uexit(uint64 exit_code); +void guest_execute_code(uint32* insns, uint64 size); + // Main guest function that performs necessary setup and passes the control to the user-provided // payload. -GUEST_CODE static void guest_main() +GUEST_CODE static void guest_main(uint64 size) +{ + uint64 addr = ARM64_ADDR_USER_CODE; + + while (size >= sizeof(struct api_call_header)) { + struct api_call_header* cmd = (struct api_call_header*)addr; + if (cmd->call >= SYZOS_API_STOP) + return; + if (cmd->size > size) + return; + switch (cmd->call) { + case SYZOS_API_UEXIT: { + struct api_call_uexit* ucmd = (struct api_call_uexit*)cmd; + guest_uexit(ucmd->exit_code); + break; + } + case SYZOS_API_CODE: { + struct api_call_code* ccmd = (struct api_call_code*)cmd; + guest_execute_code(ccmd->insns, cmd->size - sizeof(struct api_call_header)); + break; + } + } + addr += cmd->size; + size -= cmd->size; + }; +} + +GUEST_CODE void guest_execute_code(uint32* insns, uint64 size) +{ + volatile void (*fn)() = (volatile void (*)())insns; + fn(); +} + +// Perform a userspace exit that can be handled by the host. +// The host returns from ioctl(KVM_RUN) with kvm_run.exit_reason=KVM_EXIT_MMIO, +// and can handle the call depending on the data passed as exit code. +GUEST_CODE void guest_uexit(uint64 exit_code) { - void (*guest_payload)() = (void (*)())ARM64_ADDR_USER_CODE; - guest_payload(); + volatile uint64* ptr = (volatile uint64*)ARM64_ADDR_UEXIT; + *ptr = exit_code; } diff --git a/executor/kvm.h b/executor/kvm.h index 49a493818..1afbcd40e 100644 --- a/executor/kvm.h +++ b/executor/kvm.h @@ -76,6 +76,10 @@ #define NEXT_INSN $0xbadc0de #define PREFIX_SIZE 0xba1d +// Write to this page to trigger a page fault and stop KVM_RUN. +#define ARM64_ADDR_EXIT 0xdddd0000 +// Dedicated address within the exit page for the uexit command. +#define ARM64_ADDR_UEXIT (ARM64_ADDR_EXIT + 256) #define ARM64_ADDR_USER_CODE 0xeeee0000 #define ARM64_ADDR_EXECUTOR_CODE 0xeeee8000 #define ARM64_ADDR_EL1_STACK_BOTTOM 0xffff1000 diff --git a/sys/linux/dev_kvm.txt b/sys/linux/dev_kvm.txt index bcc4edec8..98554e303 100644 --- a/sys/linux/dev_kvm.txt +++ b/sys/linux/dev_kvm.txt @@ -232,12 +232,32 @@ kvm_text_x86_64 { size len[text, intptr] } +# Unlike on other architectures, ARM64 text is a sequence of commands, each starting with +# the call number and the command length. kvm_text_arm64 { typ const[0, intptr] - text ptr[in, text[arm64]] - size len[text, intptr] + text ptr[in, array[syzos_api_call, 1:32]] + size bytesize[text, int64] +} + +syzos_api_uexit { + call const[0, int64] + size bytesize[parent, int64] + exit_code intptr } +syzos_api_code { + call const[1, int64] + size bytesize[parent, int64] + insns text[arm64] + ret const[0xd65f03c0, int32] +} [packed] + +syzos_api_call [ + uexit syzos_api_uexit + code syzos_api_code +] [varlen] + kvm_text_ppc64 { typ const[0, intptr] text ptr[in, text[ppc64]] |
