// https://syzkaller.appspot.com/bug?id=a1cfe3e495bd3a17055274ab69e7c5a364a7f286 // autogenerated by syzkaller (https://github.com/google/syzkaller) #define _GNU_SOURCE #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #ifndef __NR_ioctl #define __NR_ioctl 29 #endif #ifndef __NR_mmap #define __NR_mmap 222 #endif #ifndef __NR_openat #define __NR_openat 56 #endif static void sleep_ms(uint64_t ms) { usleep(ms * 1000); } static uint64_t current_time_ms(void) { struct timespec ts; if (clock_gettime(CLOCK_MONOTONIC, &ts)) exit(1); return (uint64_t)ts.tv_sec * 1000 + (uint64_t)ts.tv_nsec / 1000000; } static bool write_file(const char* file, const char* what, ...) { char buf[1024]; va_list args; va_start(args, what); vsnprintf(buf, sizeof(buf), what, args); va_end(args); buf[sizeof(buf) - 1] = 0; int len = strlen(buf); int fd = open(file, O_WRONLY | O_CLOEXEC); if (fd == -1) return false; if (write(fd, buf, len) != len) { int err = errno; close(fd); errno = err; return false; } close(fd); return true; } #define ADDR_TEXT 0x0000 #define ADDR_GDT 0x1000 #define ADDR_LDT 0x1800 #define ADDR_PML4 0x2000 #define ADDR_PDP 0x3000 #define ADDR_PD 0x4000 #define ADDR_STACK0 0x0f80 #define ADDR_VAR_HLT 0x2800 #define ADDR_VAR_SYSRET 0x2808 #define ADDR_VAR_SYSEXIT 0x2810 #define ADDR_VAR_IDT 0x3800 #define ADDR_VAR_TSS64 0x3a00 #define ADDR_VAR_TSS64_CPL3 0x3c00 #define ADDR_VAR_TSS16 0x3d00 #define ADDR_VAR_TSS16_2 0x3e00 #define ADDR_VAR_TSS16_CPL3 0x3f00 #define ADDR_VAR_TSS32 0x4800 #define ADDR_VAR_TSS32_2 0x4a00 #define ADDR_VAR_TSS32_CPL3 0x4c00 #define ADDR_VAR_TSS32_VM86 0x4e00 #define ADDR_VAR_VMXON_PTR 0x5f00 #define ADDR_VAR_VMCS_PTR 0x5f08 #define ADDR_VAR_VMEXIT_PTR 0x5f10 #define ADDR_VAR_VMWRITE_FLD 0x5f18 #define ADDR_VAR_VMWRITE_VAL 0x5f20 #define ADDR_VAR_VMXON 0x6000 #define ADDR_VAR_VMCS 0x7000 #define ADDR_VAR_VMEXIT_CODE 0x9000 #define ADDR_VAR_USER_CODE 0x9100 #define ADDR_VAR_USER_CODE2 0x9120 #define SEL_LDT (1 << 3) #define SEL_CS16 (2 << 3) #define SEL_DS16 (3 << 3) #define SEL_CS16_CPL3 ((4 << 3) + 3) #define SEL_DS16_CPL3 ((5 << 3) + 3) #define SEL_CS32 (6 << 3) #define SEL_DS32 (7 << 3) #define SEL_CS32_CPL3 ((8 << 3) + 3) #define SEL_DS32_CPL3 ((9 << 3) + 3) #define SEL_CS64 (10 << 3) #define SEL_DS64 (11 << 3) #define SEL_CS64_CPL3 ((12 << 3) + 3) #define SEL_DS64_CPL3 ((13 << 3) + 3) #define SEL_CGATE16 (14 << 3) #define SEL_TGATE16 (15 << 3) #define SEL_CGATE32 (16 << 3) #define SEL_TGATE32 (17 << 3) #define SEL_CGATE64 (18 << 3) #define SEL_CGATE64_HI (19 << 3) #define SEL_TSS16 (20 << 3) #define SEL_TSS16_2 (21 << 3) #define SEL_TSS16_CPL3 ((22 << 3) + 3) #define SEL_TSS32 (23 << 3) #define SEL_TSS32_2 (24 << 3) #define SEL_TSS32_CPL3 ((25 << 3) + 3) #define SEL_TSS32_VM86 (26 << 3) #define SEL_TSS64 (27 << 3) #define SEL_TSS64_HI (28 << 3) #define SEL_TSS64_CPL3 ((29 << 3) + 3) #define SEL_TSS64_CPL3_HI (30 << 3) #define MSR_IA32_FEATURE_CONTROL 0x3a #define MSR_IA32_VMX_BASIC 0x480 #define MSR_IA32_SMBASE 0x9e #define MSR_IA32_SYSENTER_CS 0x174 #define MSR_IA32_SYSENTER_ESP 0x175 #define MSR_IA32_SYSENTER_EIP 0x176 #define MSR_IA32_STAR 0xC0000081 #define MSR_IA32_LSTAR 0xC0000082 #define MSR_IA32_VMX_PROCBASED_CTLS2 0x48B #define NEXT_INSN $0xbadc0de #define PREFIX_SIZE 0xba1d #define ARM64_ADDR_GICD_BASE 0x08000000 #define ARM64_ADDR_GICI_BASE 0x08080000 #define ARM64_ADDR_GICR_BASE 0x080a0000 #define ARM64_ADDR_EXIT 0xdddd0000 #define ARM64_ADDR_UEXIT (ARM64_ADDR_EXIT + 256) #define ARM64_ADDR_DIRTY_PAGES 0xdddd1000 #define ARM64_ADDR_USER_CODE 0xeeee0000 #define ARM64_ADDR_EXECUTOR_CODE 0xeeee8000 #define ARM64_ADDR_SCRATCH_CODE 0xeeef0000 #define ARM64_ADDR_EL1_STACK_BOTTOM 0xffff1000 #define ADDR_TEXT 0x0000 #define ADDR_GDT 0x1000 #define ADDR_LDT 0x1800 #define ADDR_PML4 0x2000 #define ADDR_PDP 0x3000 #define ADDR_PD 0x4000 #define ADDR_STACK0 0x0f80 #define ADDR_VAR_HLT 0x2800 #define ADDR_VAR_SYSRET 0x2808 #define ADDR_VAR_SYSEXIT 0x2810 #define ADDR_VAR_IDT 0x3800 #define ADDR_VAR_TSS64 0x3a00 #define ADDR_VAR_TSS64_CPL3 0x3c00 #define ADDR_VAR_TSS16 0x3d00 #define ADDR_VAR_TSS16_2 0x3e00 #define ADDR_VAR_TSS16_CPL3 0x3f00 #define ADDR_VAR_TSS32 0x4800 #define ADDR_VAR_TSS32_2 0x4a00 #define ADDR_VAR_TSS32_CPL3 0x4c00 #define ADDR_VAR_TSS32_VM86 0x4e00 #define ADDR_VAR_VMXON_PTR 0x5f00 #define ADDR_VAR_VMCS_PTR 0x5f08 #define ADDR_VAR_VMEXIT_PTR 0x5f10 #define ADDR_VAR_VMWRITE_FLD 0x5f18 #define ADDR_VAR_VMWRITE_VAL 0x5f20 #define ADDR_VAR_VMXON 0x6000 #define ADDR_VAR_VMCS 0x7000 #define ADDR_VAR_VMEXIT_CODE 0x9000 #define ADDR_VAR_USER_CODE 0x9100 #define ADDR_VAR_USER_CODE2 0x9120 #define SEL_LDT (1 << 3) #define SEL_CS16 (2 << 3) #define SEL_DS16 (3 << 3) #define SEL_CS16_CPL3 ((4 << 3) + 3) #define SEL_DS16_CPL3 ((5 << 3) + 3) #define SEL_CS32 (6 << 3) #define SEL_DS32 (7 << 3) #define SEL_CS32_CPL3 ((8 << 3) + 3) #define SEL_DS32_CPL3 ((9 << 3) + 3) #define SEL_CS64 (10 << 3) #define SEL_DS64 (11 << 3) #define SEL_CS64_CPL3 ((12 << 3) + 3) #define SEL_DS64_CPL3 ((13 << 3) + 3) #define SEL_CGATE16 (14 << 3) #define SEL_TGATE16 (15 << 3) #define SEL_CGATE32 (16 << 3) #define SEL_TGATE32 (17 << 3) #define SEL_CGATE64 (18 << 3) #define SEL_CGATE64_HI (19 << 3) #define SEL_TSS16 (20 << 3) #define SEL_TSS16_2 (21 << 3) #define SEL_TSS16_CPL3 ((22 << 3) + 3) #define SEL_TSS32 (23 << 3) #define SEL_TSS32_2 (24 << 3) #define SEL_TSS32_CPL3 ((25 << 3) + 3) #define SEL_TSS32_VM86 (26 << 3) #define SEL_TSS64 (27 << 3) #define SEL_TSS64_HI (28 << 3) #define SEL_TSS64_CPL3 ((29 << 3) + 3) #define SEL_TSS64_CPL3_HI (30 << 3) #define MSR_IA32_FEATURE_CONTROL 0x3a #define MSR_IA32_VMX_BASIC 0x480 #define MSR_IA32_SMBASE 0x9e #define MSR_IA32_SYSENTER_CS 0x174 #define MSR_IA32_SYSENTER_ESP 0x175 #define MSR_IA32_SYSENTER_EIP 0x176 #define MSR_IA32_STAR 0xC0000081 #define MSR_IA32_LSTAR 0xC0000082 #define MSR_IA32_VMX_PROCBASED_CTLS2 0x48B #define NEXT_INSN $0xbadc0de #define PREFIX_SIZE 0xba1d #define ARM64_ADDR_GICD_BASE 0x08000000 #define ARM64_ADDR_GICI_BASE 0x08080000 #define ARM64_ADDR_GICR_BASE 0x080a0000 #define ARM64_ADDR_EXIT 0xdddd0000 #define ARM64_ADDR_UEXIT (ARM64_ADDR_EXIT + 256) #define ARM64_ADDR_DIRTY_PAGES 0xdddd1000 #define ARM64_ADDR_USER_CODE 0xeeee0000 #define ARM64_ADDR_EXECUTOR_CODE 0xeeee8000 #define ARM64_ADDR_SCRATCH_CODE 0xeeef0000 #define ARM64_ADDR_EL1_STACK_BOTTOM 0xffff1000 #define GUEST_CODE __attribute__((section("guest"))) extern char *__start_guest, *__stop_guest; typedef enum { SYZOS_API_UEXIT, SYZOS_API_CODE, SYZOS_API_MSR, SYZOS_API_SMC, SYZOS_API_HVC, SYZOS_API_IRQ_SETUP, SYZOS_API_MEMWRITE, SYZOS_API_STOP, } syzos_api_id; struct api_call_header { uint64_t call; uint64_t size; }; struct api_call_uexit { struct api_call_header header; uint64_t exit_code; }; struct api_call_2 { struct api_call_header header; uint64_t args[2]; }; struct api_call_code { struct api_call_header header; uint32_t insns[]; }; struct api_call_smccc { struct api_call_header header; uint32_t func_id; uint64_t params[5]; }; struct api_call_irq_setup { struct api_call_header header; uint32_t nr_cpus; uint32_t nr_spis; }; struct api_call_memwrite { struct api_call_header header; uint64_t base_addr; uint64_t offset; uint64_t value; uint64_t len; }; static void guest_uexit(uint64_t exit_code); static void guest_execute_code(uint32_t* insns, uint64_t size); static void guest_handle_msr(uint64_t reg, uint64_t val); static void guest_handle_smc(struct api_call_smccc* cmd); static void guest_handle_hvc(struct api_call_smccc* cmd); static void guest_handle_irq_setup(struct api_call_irq_setup* cmd); static void guest_handle_memwrite(struct api_call_memwrite* cmd); typedef enum { UEXIT_END = (uint64_t)-1, UEXIT_IRQ = (uint64_t)-2, UEXIT_ASSERT = (uint64_t)-3, } uexit_code; __attribute__((used)) GUEST_CODE static void guest_main(uint64_t size, uint64_t cpu) { uint64_t addr = ARM64_ADDR_USER_CODE + cpu * 0x1000; 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; } case SYZOS_API_MSR: { struct api_call_2* ccmd = (struct api_call_2*)cmd; guest_handle_msr(ccmd->args[0], ccmd->args[1]); break; } case SYZOS_API_SMC: { guest_handle_smc((struct api_call_smccc*)cmd); break; } case SYZOS_API_HVC: { guest_handle_hvc((struct api_call_smccc*)cmd); break; } case SYZOS_API_IRQ_SETUP: { guest_handle_irq_setup((struct api_call_irq_setup*)cmd); break; } case SYZOS_API_MEMWRITE: { guest_handle_memwrite((struct api_call_memwrite*)cmd); break; } } addr += cmd->size; size -= cmd->size; }; guest_uexit((uint64_t)-1); } GUEST_CODE static void guest_execute_code(uint32_t* insns, uint64_t size) { volatile void (*fn)() = (volatile void (*)())insns; fn(); } GUEST_CODE static void guest_uexit(uint64_t exit_code) { volatile uint64_t* ptr = (volatile uint64_t*)ARM64_ADDR_UEXIT; *ptr = exit_code; } #define MSR_REG_OPCODE 0xd5100000 GUEST_CODE static uint32_t reg_to_msr(uint64_t reg) { return MSR_REG_OPCODE | ((reg & 0xffff) << 5); } GUEST_CODE static uint32_t get_cpu_id() { uint64_t val = 0; asm volatile("mrs %0, tpidr_el1" : "=r"(val)); return (uint32_t)val; } #define MAX_CACHE_LINE_SIZE 256 __attribute__((noinline)) GUEST_CODE static void guest_handle_msr(uint64_t reg, uint64_t val) { uint32_t msr = reg_to_msr(reg); uint32_t cpu_id = get_cpu_id(); uint32_t* insn = (uint32_t*)((uint64_t)ARM64_ADDR_SCRATCH_CODE + cpu_id * MAX_CACHE_LINE_SIZE); insn[0] = msr; insn[1] = 0xd65f03c0; asm("mov x0, %[val]\nblr %[pc]\n" : : [val] "r"(val), [pc] "r"(insn) : "x0", "x30", "memory"); } GUEST_CODE static void guest_handle_smc(struct api_call_smccc* cmd) { asm volatile( "mov x0, %[func_id]\n" "mov x1, %[arg1]\n" "mov x2, %[arg2]\n" "mov x3, %[arg3]\n" "mov x4, %[arg4]\n" "mov x5, %[arg5]\n" "smc #0\n" : : [func_id] "r"((uint32_t)cmd->func_id), [arg1] "r"(cmd->params[0]), [arg2] "r"(cmd->params[1]), [arg3] "r"(cmd->params[2]), [arg4] "r"(cmd->params[3]), [arg5] "r"(cmd->params[4]) : "x0", "x1", "x2", "x3", "x4", "x5", "x6", "x7", "x8", "x9", "x10", "x11", "x12", "x13", "x14", "x15", "x16", "x17", "memory"); } GUEST_CODE static void guest_handle_hvc(struct api_call_smccc* cmd) { asm volatile( "mov x0, %[func_id]\n" "mov x1, %[arg1]\n" "mov x2, %[arg2]\n" "mov x3, %[arg3]\n" "mov x4, %[arg4]\n" "mov x5, %[arg5]\n" "hvc #0\n" : : [func_id] "r"((uint32_t)cmd->func_id), [arg1] "r"(cmd->params[0]), [arg2] "r"(cmd->params[1]), [arg3] "r"(cmd->params[2]), [arg4] "r"(cmd->params[3]), [arg5] "r"(cmd->params[4]) : "x0", "x1", "x2", "x3", "x4", "x5", "x6", "x7", "x8", "x9", "x10", "x11", "x12", "x13", "x14", "x15", "x16", "x17", "memory"); } #define GICD_CTLR 0x0000 #define GICD_IGROUPR 0x0080 #define GICD_ISENABLER 0x0100 #define GICD_ICENABLER 0x0180 #define GICD_ICACTIVER 0x0380 #define GICD_IPRIORITYR 0x0400 #define GICD_INT_DEF_PRI_X4 0xa0a0a0a0 #define GICD_CTLR_ARE_NS (1U << 4) #define GICD_CTLR_ENABLE_G1A (1U << 1) #define GICD_CTLR_ENABLE_G1 (1U << 0) #define GICD_CTLR_RWP (1U << 31) #define GICR_CTLR_RWP (1UL << 3) #define GICR_CTLR GICD_CTLR #define GICR_WAKER 0x0014 #define GICR_IGROUPR0 GICD_IGROUPR #define GICR_ICENABLER0 GICD_ICENABLER #define GICR_ICACTIVER0 GICD_ICACTIVER #define GICR_IPRIORITYR0 GICD_IPRIORITYR #define ICC_SRE_EL1_SRE (1U << 0) #define ICC_PMR_DEF_PRIO 0xff #define ICC_IGRPEN1_EL1_ENABLE (1U << 0) #define GICR_WAKER_ProcessorSleep (1U << 1) #define GICR_WAKER_ChildrenAsleep (1U << 2) #define ICC_SRE_EL1 "S3_0_C12_C12_5" #define ICC_PMR_EL1 "S3_0_C4_C6_0" #define ICC_IGRPEN1_EL1 "S3_0_C12_C12_7" #define ICC_IAR0_EL1 "S3_0_C12_C8_0" #define ICC_IAR1_EL1 "S3_0_C12_C12_0" #define ICC_EOIR0_EL1 "S3_0_C12_C8_1" #define ICC_EOIR1_EL1 "S3_0_C12_C12_1" #define ICC_DIR_EL1 "S3_0_C12_C11_1" static GUEST_CODE __always_inline void __raw_writel(uint32_t val, uint64_t addr) { asm volatile("str %w0, [%1]" : : "rZ"(val), "r"(addr)); } static GUEST_CODE __always_inline uint32_t __raw_readl(uint64_t addr) { uint32_t val; asm volatile("ldr %w0, [%1]" : "=r"(val) : "r"(addr)); return val; } #define writel_relaxed(v, c) ((void)__raw_writel((uint32_t)cpu_to_le32(v), (c))) #define readl_relaxed(c) \ ({ \ uint32_t __r = le32_to_cpu((__le32)__raw_readl(c)); \ __r; \ }) #define dmb() asm volatile("dmb sy" : : : "memory") #define writel(v, c) \ ({ \ dmb(); \ __raw_writel(v, c); \ }) #define readl(c) \ ({ \ uint32_t __v = __raw_readl(c); \ dmb(); \ __v; \ }) #define GUEST_ASSERT(val) \ do { \ if (!(val)) \ guest_uexit(UEXIT_ASSERT); \ } while (0) GUEST_CODE uint64_t read_cntvct(void) { uint64_t val; asm volatile("mrs %0, cntvct_el0" : "=r"(val)); return val; } GUEST_CODE static void guest_udelay(uint32_t us) { uint64_t ticks_per_second = 0; asm volatile("mrs %0, cntfrq_el0" : "=r"(ticks_per_second)); uint64_t start = read_cntvct(); uint64_t target = start + (us * ticks_per_second) / 1000000; while (read_cntvct() < target) { } } GUEST_CODE static void spin_while_readl(uint64_t reg, uint32_t mask) { volatile unsigned int count = 100000; while (readl(reg) & mask) { GUEST_ASSERT(count--); guest_udelay(10); } } GUEST_CODE static void gicd_wait_for_rwp() { spin_while_readl(ARM64_ADDR_GICD_BASE + GICD_CTLR, GICD_CTLR_RWP); } #define SZ_64K 0x00010000 GUEST_CODE static uint64_t gicr_base_cpu(uint32_t cpu) { return ARM64_ADDR_GICR_BASE + cpu * SZ_64K * 2; } GUEST_CODE static uint64_t sgi_base_cpu(uint32_t cpu) { return gicr_base_cpu(cpu) + SZ_64K; } GUEST_CODE static void gicr_wait_for_rwp(uint32_t cpu) { spin_while_readl(gicr_base_cpu(cpu) + GICR_CTLR, GICR_CTLR_RWP); } GUEST_CODE static void gicv3_dist_init(int nr_spis) { writel(0, ARM64_ADDR_GICD_BASE + GICD_CTLR); gicd_wait_for_rwp(); for (int i = 32; i < nr_spis + 32; i += 32) { writel(~0, ARM64_ADDR_GICD_BASE + GICD_IGROUPR + i / 8); writel(~0, ARM64_ADDR_GICD_BASE + GICD_ICACTIVER + i / 8); writel(~0, ARM64_ADDR_GICD_BASE + GICD_ICENABLER + i / 8); } for (int i = 32; i < nr_spis + 32; i += 4) { writel(GICD_INT_DEF_PRI_X4, ARM64_ADDR_GICD_BASE + GICD_IPRIORITYR + i); } gicd_wait_for_rwp(); writel(GICD_CTLR_ARE_NS | GICD_CTLR_ENABLE_G1A | GICD_CTLR_ENABLE_G1, ARM64_ADDR_GICD_BASE + GICD_CTLR); gicd_wait_for_rwp(); } GUEST_CODE void gicv3_enable_redist(uint32_t cpu) { uint64_t redist_base_cpu = gicr_base_cpu(cpu); uint32_t val = readl(redist_base_cpu + GICR_WAKER); val &= ~GICR_WAKER_ProcessorSleep; writel(val, ARM64_ADDR_GICR_BASE + GICR_WAKER); spin_while_readl(ARM64_ADDR_GICR_BASE + GICR_WAKER, GICR_WAKER_ChildrenAsleep); } GUEST_CODE void gicv3_cpu_init(uint32_t cpu) { uint64_t sgi_base = sgi_base_cpu(cpu); gicv3_enable_redist(cpu); writel(~0, sgi_base + GICR_IGROUPR0); writel(~0, sgi_base + GICR_ICACTIVER0); writel(~0, sgi_base + GICR_ICENABLER0); for (int i = 0; i < 32; i += 4) { writel(GICD_INT_DEF_PRI_X4, sgi_base + GICR_IPRIORITYR0 + i); } gicr_wait_for_rwp(cpu); uint32_t icc_sre_el1 = 0; asm volatile("mrs %0, " ICC_SRE_EL1 : : "r"(icc_sre_el1)); icc_sre_el1 |= ICC_SRE_EL1_SRE; asm volatile("msr " ICC_SRE_EL1 ", %0" : : "r"(icc_sre_el1)); uint32_t value = ICC_PMR_DEF_PRIO; asm volatile("msr " ICC_PMR_EL1 ", %0" : : "r"(value)); value = ICC_IGRPEN1_EL1_ENABLE; asm volatile("msr " ICC_IGRPEN1_EL1 ", %0" : : "r"(value)); } #define VGICV3_MIN_SPI 32 #define VGICV3_MAX_SPI 1019 GUEST_CODE static void gicv3_irq_enable(uint32_t intid) { uint32_t cpu = get_cpu_id(); writel(1 << (intid % 32), ARM64_ADDR_GICD_BASE + GICD_ISENABLER + (intid / 32) * 4); if ((intid >= VGICV3_MIN_SPI) && (intid <= VGICV3_MAX_SPI)) gicd_wait_for_rwp(); else gicr_wait_for_rwp(cpu); } GUEST_CODE static void guest_handle_irq_setup(struct api_call_irq_setup* cmd) { int nr_spis = cmd->nr_spis; if ((nr_spis > VGICV3_MAX_SPI - VGICV3_MIN_SPI) || (nr_spis < 0)) nr_spis = 32; int nr_cpus = cmd->nr_cpus; gicv3_dist_init(nr_spis); for (int i = 0; i < nr_cpus; i++) gicv3_cpu_init(i); for (int i = 0; i < nr_spis; i++) gicv3_irq_enable(VGICV3_MIN_SPI + i); asm(R"( adr x1, guest_vector_table msr vbar_el1, x1 msr daifclr, #0b1111 )" : : : "x1"); } GUEST_CODE static void guest_handle_memwrite(struct api_call_memwrite* cmd) { uint64_t dest = cmd->base_addr + cmd->offset; switch (cmd->len) { case 1: { volatile uint8_t* p = (uint8_t*)dest; *p = (uint8_t)cmd->value; break; } case 2: { volatile uint16_t* p = (uint16_t*)dest; *p = (uint16_t)cmd->value; break; } case 4: { volatile uint32_t* p = (uint32_t*)dest; *p = (uint32_t)cmd->value; break; } case 8: default: { volatile uint64_t* p = (uint64_t*)dest; *p = (uint64_t)cmd->value; break; } } } struct ex_regs { uint64_t regs[31]; uint64_t sp; uint64_t pc; uint64_t pstate; }; __attribute__((used)) GUEST_CODE static void one_irq_handler_fn() { asm volatile( R"(.global one_irq_handler one_irq_handler: # Allocate 34 * uint64_t for struct ex_regs. add sp, sp, #-16 * 17 # Store registers x0-x29 on the stack. stp x0, x1, [sp, #16 * 0] stp x2, x3, [sp, #16 * 1] stp x4, x5, [sp, #16 * 2] stp x6, x7, [sp, #16 * 3] stp x8, x9, [sp, #16 * 4] stp x10, x11, [sp, #16 * 5] stp x12, x13, [sp, #16 * 6] stp x14, x15, [sp, #16 * 7] stp x16, x17, [sp, #16 * 8] stp x18, x19, [sp, #16 * 9] stp x20, x21, [sp, #16 * 10] stp x22, x23, [sp, #16 * 11] stp x24, x25, [sp, #16 * 12] stp x26, x27, [sp, #16 * 13] stp x28, x29, [sp, #16 * 14] add x1, sp, #16 * 17 # Store x30 and SP (before allocating ex_regs). stp x30, x1, [sp, #16 * 15] # ELR_EL1 holds the PC to return to. mrs x1, elr_el1 # SPSR_EL1 is the saved PSTATE. mrs x2, spsr_el1 # Also store them to ex_regs. stp x1, x2, [sp, #16 * 16] # Call guest_irq_handler(ex_regs). mov x0, sp bl guest_irq_handler # Restore ELR_EL1 and SPSR_EL1. ldp x1, x2, [sp, #16 * 16] msr elr_el1, x1 msr spsr_el1, x2 # Restore the GP registers x0-x30 (ignoring SP). ldp x30, xzr, [sp, #16 * 15] ldp x28, x29, [sp, #16 * 14] ldp x26, x27, [sp, #16 * 13] ldp x24, x25, [sp, #16 * 12] ldp x22, x23, [sp, #16 * 11] ldp x20, x21, [sp, #16 * 10] ldp x18, x19, [sp, #16 * 9] ldp x16, x17, [sp, #16 * 8] ldp x14, x15, [sp, #16 * 7] ldp x12, x13, [sp, #16 * 6] ldp x10, x11, [sp, #16 * 5] ldp x8, x9, [sp, #16 * 4] ldp x6, x7, [sp, #16 * 3] ldp x4, x5, [sp, #16 * 2] ldp x2, x3, [sp, #16 * 1] ldp x0, x1, [sp, #16 * 0] add sp, sp, #16 * 17 # Use ERET to exit from an exception. eret)" : : : "x0", "x1", "x2", "x3", "x4", "x5", "x6", "x7", "x8", "x9", "x10", "x11", "x12", "x13", "x14", "x15", "x16", "x17", "x18", "x19", "x20", "x21", "x22", "x23", "x24", "x25", "x26", "x27", "x28", "x29", "x30", "memory"); } __attribute__((used)) GUEST_CODE static void guest_irq_handler(struct ex_regs* regs) { uint32_t iar0, iar1, irq_num = 0; asm volatile("mrs %0, " ICC_IAR0_EL1 : "=r"(iar0)); asm volatile("mrs %0, " ICC_IAR1_EL1 : "=r"(iar1)); if (iar0 != 0x3ff) { irq_num = iar0 & 0x3FF; } else if (iar1 != 0x3ff) { irq_num = iar1 & 0x3FF; } else { return; } guest_uexit(UEXIT_IRQ); if (iar0 != 0x3ff) { asm volatile("msr " ICC_EOIR0_EL1 ", %0" : : "r"(irq_num)); } else { asm volatile("msr " ICC_EOIR1_EL1 ", %0" : : "r"(irq_num)); } asm volatile("msr " ICC_DIR_EL1 ", %0" : : "r"(irq_num)); } #define IRQ_ENTRY \ ".balign 0x80\n" \ "b one_irq_handler\n" #define IRQ_ENTRY_DUMMY \ ".balign 0x80\n" \ "eret\n" __attribute__((used)) GUEST_CODE static void guest_vector_table_fn() { asm volatile( ".global guest_vector_table\n" ".balign 2048\n" "guest_vector_table:\n" IRQ_ENTRY_DUMMY IRQ_ENTRY_DUMMY IRQ_ENTRY_DUMMY IRQ_ENTRY_DUMMY IRQ_ENTRY_DUMMY IRQ_ENTRY IRQ_ENTRY_DUMMY IRQ_ENTRY_DUMMY IRQ_ENTRY_DUMMY IRQ_ENTRY_DUMMY IRQ_ENTRY_DUMMY IRQ_ENTRY_DUMMY IRQ_ENTRY_DUMMY IRQ_ENTRY_DUMMY IRQ_ENTRY_DUMMY IRQ_ENTRY_DUMMY); } #define KVM_MAX_VCPU 4 #define KVM_PAGE_SIZE (4 << 10) #define KVM_GUEST_MEM_SIZE (24 * KVM_PAGE_SIZE) #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_t typ; uint64_t val; }; 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 vm_set_user_memory_region(int vmfd, uint32_t slot, uint32_t flags, uint64_t guest_phys_addr, uint64_t memory_size, uint64_t 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) { struct addr_size allocator = {.addr = host_mem, .size = KVM_GUEST_MEM_SIZE}; int slot = 0; struct addr_size host_text = alloc_guest_mem(&allocator, 4 * KVM_PAGE_SIZE); memcpy(host_text.addr, &__start_guest, (char*)&__stop_guest - (char*)&__start_guest); 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); next = alloc_guest_mem(&allocator, allocator.size); vm_set_user_memory_region(vmfd, slot++, 0, 0, next.size, (uintptr_t)next.addr); } static void vcpu_set_reg(int vcpu_fd, uint64_t id, uint64_t val) { struct kvm_one_reg reg = {.id = id, .addr = (uint64_t)&val}; ioctl(vcpu_fd, KVM_SET_ONE_REG, ®); } static void reset_cpu_regs(int cpufd, int cpu_id, size_t text_size) { vcpu_set_reg(cpufd, KVM_ARM64_REGS_PC, ARM64_ADDR_EXECUTOR_CODE + ((uint64_t)guest_main - (uint64_t)&__start_guest)); vcpu_set_reg(cpufd, KVM_ARM64_REGS_SP_EL1, ARM64_ADDR_EL1_STACK_BOTTOM + KVM_PAGE_SIZE - 128); vcpu_set_reg(cpufd, KVM_ARM64_REGS_TPIDR_EL1, cpu_id); 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_t)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_t features = 0; if (opt_count > 1) opt_count = 1; for (int i = 0; i < opt_count; i++) { uint64_t typ = opt[i].typ; uint64_t val = opt[i].val; switch (typ) { case 1: features = val; break; } } struct kvm_vcpu_init init; ioctl(vmfd, KVM_ARM_PREFERRED_TARGET, &init); init.features[0] = features; ioctl(cpufd, KVM_ARM_VCPU_INIT, &init); } 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; 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); install_user_code(cpufd, user_text_slot, 0, text, text_size); return 0; } static void kill_and_wait(int pid, int* status) { kill(-pid, SIGKILL); kill(pid, SIGKILL); for (int i = 0; i < 100; i++) { if (waitpid(-1, status, WNOHANG | __WALL) == pid) return; usleep(1000); } DIR* dir = opendir("/sys/fs/fuse/connections"); if (dir) { for (;;) { struct dirent* ent = readdir(dir); if (!ent) break; if (strcmp(ent->d_name, ".") == 0 || strcmp(ent->d_name, "..") == 0) continue; char abort[300]; snprintf(abort, sizeof(abort), "/sys/fs/fuse/connections/%s/abort", ent->d_name); int fd = open(abort, O_WRONLY); if (fd == -1) { continue; } if (write(fd, abort, 1) < 0) { } close(fd); } closedir(dir); } else { } while (waitpid(-1, status, __WALL) != pid) { } } static void setup_test() { prctl(PR_SET_PDEATHSIG, SIGKILL, 0, 0, 0); setpgrp(); write_file("/proc/self/oom_score_adj", "1000"); } static void execute_one(void); #define WAIT_FLAGS __WALL static void loop(void) { int iter = 0; for (;; iter++) { int pid = fork(); if (pid < 0) exit(1); if (pid == 0) { setup_test(); execute_one(); exit(0); } int status = 0; uint64_t start = current_time_ms(); for (;;) { sleep_ms(10); if (waitpid(-1, &status, WNOHANG | WAIT_FLAGS) == pid) break; if (current_time_ms() - start < 15000) continue; kill_and_wait(pid, &status); break; } } } uint64_t r[3] = {0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff}; void execute_one(void) { intptr_t res = 0; if (write(1, "executing program\n", sizeof("executing program\n") - 1)) { } memcpy((void*)0x20000040, "/dev/kvm\000", 9); res = syscall(__NR_openat, /*fd=*/0ul, /*file=*/0x20000040ul, /*flags=*/0, /*mode=*/0); if (res != -1) r[0] = res; res = syscall(__NR_ioctl, /*fd=*/r[0], /*cmd=*/0xae01, /*type=*/0ul); if (res != -1) r[1] = res; res = syscall(__NR_ioctl, /*fd=*/r[1], /*cmd=*/0xae41, /*id=*/0ul); if (res != -1) r[2] = res; *(uint64_t*)0x20000080 = 0; *(uint64_t*)0x20000088 = 0; *(uint64_t*)0x20000090 = 0; syz_kvm_setup_cpu(/*fd=*/r[1], /*cpufd=*/r[2], /*usermem=*/0x20e8a000, /*text=*/0x20000080, /*ntext=*/1, /*flags=*/0, /*opts=*/0, /*nopt=*/0); syscall(__NR_ioctl, /*fd=*/r[2], /*cmd=*/0xae80, /*arg=*/0ul); *(uint8_t*)0x20000140 = 0x28; *(uint8_t*)0x20000141 = 7; *(uint8_t*)0x20000142 = 2; memset((void*)0x20000143, 0, 5); *(uint64_t*)0x20000148 = 0x694; memset((void*)0x20000150, 0, 48); syscall(__NR_ioctl, /*fd=*/r[2], /*cmd=*/0x4040aea0, /*arg=*/0x20000140ul); syscall(__NR_ioctl, /*fd=*/r[2], /*cmd=*/0xae80, /*arg=*/0ul); } int main(void) { syscall(__NR_mmap, /*addr=*/0x1ffff000ul, /*len=*/0x1000ul, /*prot=*/0ul, /*flags=MAP_FIXED|MAP_ANONYMOUS|MAP_PRIVATE*/ 0x32ul, /*fd=*/-1, /*offset=*/0ul); syscall(__NR_mmap, /*addr=*/0x20000000ul, /*len=*/0x1000000ul, /*prot=PROT_WRITE|PROT_READ|PROT_EXEC*/ 7ul, /*flags=MAP_FIXED|MAP_ANONYMOUS|MAP_PRIVATE*/ 0x32ul, /*fd=*/-1, /*offset=*/0ul); syscall(__NR_mmap, /*addr=*/0x21000000ul, /*len=*/0x1000ul, /*prot=*/0ul, /*flags=MAP_FIXED|MAP_ANONYMOUS|MAP_PRIVATE*/ 0x32ul, /*fd=*/-1, /*offset=*/0ul); const char* reason; (void)reason; loop(); return 0; }