| // Copyright 2017 syzkaller project authors. All rights reserved. |
| // Use of this source code is governed by Apache 2 LICENSE that can be found in the LICENSE file. |
| |
| // This file is shared between executor and csource package. |
| |
| // Implementation of syz_kvm_setup_cpu pseudo-syscall. |
| #include <sys/mman.h> |
| |
| #include "common_kvm.h" |
| #include "kvm.h" |
| |
| #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" |
| #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 |
| #define KVM_ARM64_REGS_TPIDR_EL1 0x603000000013c684 |
| |
| struct kvm_text { |
| uintptr_t typ; |
| const void* text; |
| uintptr_t size; |
| }; |
| |
| struct kvm_opt { |
| uint64 typ; |
| uint64 val; |
| }; |
| #endif |
| |
| #if SYZ_EXECUTOR || __NR_syz_kvm_setup_cpu || __NR_syz_kvm_setup_syzos_vm |
| 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; |
| } |
| |
| // 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); |
| } |
| |
| #define ADRP_OPCODE 0x90000000 |
| #define ADRP_OPCODE_MASK 0x9f000000 |
| |
| // Code loading SyzOS into guest memory does not handle data relocations (see |
| // https://github.com/google/syzkaller/issues/5565), so SyzOS will crash soon after encountering an |
| // ADRP instruction. Detect these instructions to catch regressions early. |
| // The most common reason for using data relocaions is accessing global variables and constants. |
| // Sometimes the compiler may choose to emit a read-only constant to zero-initialize a structure |
| // or to generate a jump table for a switch statement. |
| static void validate_guest_code(void* mem, size_t size) |
| { |
| uint32* insns = (uint32*)mem; |
| for (size_t i = 0; i < size / 4; i++) { |
| if ((insns[i] & ADRP_OPCODE_MASK) == ADRP_OPCODE) |
| fail("ADRP instruction detected in SyzOS, exiting"); |
| } |
| } |
| |
| static void install_syzos_code(void* host_mem, size_t mem_size) |
| { |
| size_t size = (char*)&__stop_guest - (char*)&__start_guest; |
| if (size > mem_size) |
| fail("SyzOS size exceeds guest memory"); |
| memcpy(host_mem, &__start_guest, size); |
| validate_guest_code(host_mem, size); |
| } |
| |
| static void setup_vm(int vmfd, void* host_mem, void** text_slot) |
| { |
| // Guest physical memory layout (must be in sync with executor/kvm.h): |
| // 0x00000000 - unused pages |
| // 0x08000000 - GICv3 distributor region (MMIO, no memory allocated) |
| // 0x080a0000 - GICv3 redistributor region (MMIO, no memory allocated) |
| // 0xdddd0000 - unmapped region to trigger a page faults for uexits etc. (1 page) |
| // 0xdddd1000 - writable region with KVM_MEM_LOG_DIRTY_PAGES to fuzz dirty ring (2 pages) |
| // 0xeeee0000 - user code (4 pages) |
| // 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 = 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 * KVM_PAGE_SIZE); |
| install_syzos_code(host_text.addr, host_text.size); |
| 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, 2 * KVM_PAGE_SIZE); |
| vm_set_user_memory_region(vmfd, slot++, KVM_MEM_LOG_DIRTY_PAGES, ARM64_ADDR_DIRTY_PAGES, next.size, (uintptr_t)next.addr); |
| |
| next = alloc_guest_mem(&allocator, KVM_MAX_VCPU * KVM_PAGE_SIZE); |
| vm_set_user_memory_region(vmfd, slot++, KVM_MEM_READONLY, ARM64_ADDR_USER_CODE, next.size, (uintptr_t)next.addr); |
| if (text_slot) |
| *text_slot = next.addr; |
| |
| next = alloc_guest_mem(&allocator, KVM_PAGE_SIZE); |
| vm_set_user_memory_region(vmfd, slot++, 0, ARM64_ADDR_EL1_STACK_BOTTOM, next.size, (uintptr_t)next.addr); |
| |
| next = alloc_guest_mem(&allocator, KVM_PAGE_SIZE); |
| vm_set_user_memory_region(vmfd, slot++, 0, ARM64_ADDR_SCRATCH_CODE, next.size, (uintptr_t)next.addr); |
| |
| // Allocate memory for the ITS tables: 64K for the device table, collection table, command queue, property table, |
| // plus 64K * 4 CPUs for the pending tables, and 64K * 16 devices for the ITT tables. |
| int its_size = SZ_64K * (4 + 4 + 16); |
| next = alloc_guest_mem(&allocator, its_size); |
| vm_set_user_memory_region(vmfd, slot++, 0, ARM64_ADDR_ITS_TABLES, 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); |
| } |
| #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) |
| { |
| // 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 + KVM_PAGE_SIZE - 128); |
| // Store the CPU ID in TPIDR_EL1. |
| vcpu_set_reg(cpufd, KVM_ARM64_REGS_TPIDR_EL1, cpu_id); |
| // Pass parameters to guest_main(). |
| vcpu_set_reg(cpufd, KVM_ARM64_REGS_X0, text_size); |
| vcpu_set_reg(cpufd, KVM_ARM64_REGS_X1, cpu_id); |
| } |
| |
| static void install_user_code(int cpufd, void* user_text_slot, 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)); |
| memcpy(target, text, text_size); |
| 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) |
| { |
| const int vmfd = a0; |
| const int cpufd = a1; |
| void* const host_mem = (void*)a2; |
| const struct kvm_text* const text_array_ptr = (struct kvm_text*)a3; |
| const uintptr_t text_count = a4; |
| const uintptr_t flags = a5; |
| const struct kvm_opt* const opt_array_ptr = (struct kvm_opt*)a6; |
| uintptr_t opt_count = a7; |
| |
| (void)flags; |
| (void)opt_count; |
| |
| (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; |
| size_t text_size = text_array_ptr[0].size; |
| (void)text_type; |
| |
| void* user_text_slot = NULL; |
| setup_vm(vmfd, host_mem, &user_text_slot); |
| 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); |
| return 0; |
| } |
| #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 |
| |
| 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->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) { |
| errno = EINVAL; |
| return -1; |
| } |
| if (vm->next_cpu_id == KVM_MAX_VCPU) { |
| errno = ENOMEM; |
| 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) |
| { |
| struct kvm_device_attr kvmattr = { |
| .flags = 0, |
| .group = group, |
| .attr = attr, |
| .addr = (uintptr_t)val, |
| }; |
| |
| return ioctl(dev_fd, KVM_SET_DEVICE_ATTR, &kvmattr); |
| } |
| |
| static int kvm_create_device(int vm_fd, int type) |
| { |
| struct kvm_create_device create_dev = { |
| .type = (uint32)type, |
| .fd = (uint32)-1, |
| .flags = 0, |
| }; |
| |
| if (ioctl(vm_fd, KVM_CREATE_DEVICE, &create_dev) != -1) |
| return create_dev.fd; |
| else |
| return -1; |
| } |
| |
| #define REDIST_REGION_ATTR_ADDR(count, base, flags, index) \ |
| (((uint64)(count) << 52) | \ |
| ((uint64)((base) >> 16) << 16) | \ |
| ((uint64)(flags) << 12) | \ |
| index) |
| |
| // Set up the VGICv3 interrupt controller. |
| // syz_kvm_vgic_v3_setup(fd fd_kvmvm, ncpus flags[kvm_num_cpus], nirqs flags[kvm_num_irqs]) |
| static long syz_kvm_vgic_v3_setup(volatile long a0, volatile long a1, volatile long a2) |
| { |
| const int vm_fd = a0; |
| const int nr_vcpus = a1; |
| const int want_nr_irq = a2; |
| |
| int vgic_fd = kvm_create_device(vm_fd, KVM_DEV_TYPE_ARM_VGIC_V3); |
| if (vgic_fd == -1) |
| return -1; |
| |
| uint32 nr_irq = want_nr_irq; |
| int ret = kvm_set_device_attr(vgic_fd, KVM_DEV_ARM_VGIC_GRP_NR_IRQS, 0, &nr_irq); |
| if (ret == -1) { |
| close(vgic_fd); |
| return -1; |
| } |
| |
| uint64 gicd_base_gpa = ARM64_ADDR_GICD_BASE; |
| ret = kvm_set_device_attr(vgic_fd, KVM_DEV_ARM_VGIC_GRP_ADDR, KVM_VGIC_V3_ADDR_TYPE_DIST, &gicd_base_gpa); |
| if (ret == -1) { |
| close(vgic_fd); |
| return -1; |
| } |
| uint64 redist_attr = REDIST_REGION_ATTR_ADDR(nr_vcpus, ARM64_ADDR_GICR_BASE, 0, 0); |
| ret = kvm_set_device_attr(vgic_fd, KVM_DEV_ARM_VGIC_GRP_ADDR, KVM_VGIC_V3_ADDR_TYPE_REDIST_REGION, &redist_attr); |
| if (ret == -1) { |
| close(vgic_fd); |
| return -1; |
| } |
| |
| ret = kvm_set_device_attr(vgic_fd, KVM_DEV_ARM_VGIC_GRP_CTRL, KVM_DEV_ARM_VGIC_CTRL_INIT, NULL); |
| if (ret == -1) { |
| close(vgic_fd); |
| return -1; |
| } |
| |
| return vgic_fd; |
| } |
| #endif |
| |
| #if SYZ_EXECUTOR || __NR_syz_kvm_assert_syzos_uexit |
| static long syz_kvm_assert_syzos_uexit(volatile long a0, volatile long a1) |
| { |
| struct kvm_run* run = (struct kvm_run*)a0; |
| uint64 expect = a1; |
| |
| if (!run || (run->exit_reason != KVM_EXIT_MMIO) || (run->mmio.phys_addr != ARM64_ADDR_UEXIT)) { |
| errno = EINVAL; |
| return -1; |
| } |
| |
| if ((((uint64*)(run->mmio.data))[0]) != expect) { |
| errno = EDOM; |
| return -1; |
| } |
| return 0; |
| } |
| #endif |
| |
| #if SYZ_EXECUTOR || __NR_syz_kvm_assert_reg |
| static long syz_kvm_assert_reg(volatile long a0, volatile long a1, volatile long a2) |
| { |
| int vcpu_fd = (int)a0; |
| uint64 id = (uint64)a1; |
| uint64 expect = a2, val = 0; |
| |
| struct kvm_one_reg reg = {.id = id, .addr = (uint64)&val}; |
| int ret = ioctl(vcpu_fd, KVM_GET_ONE_REG, ®); |
| if (ret) |
| return ret; |
| if (val != expect) { |
| errno = EDOM; |
| return -1; |
| } |
| return 0; |
| } |
| #endif |