diff options
Diffstat (limited to 'executor')
| -rw-r--r-- | executor/common_kvm_ppc64.h | 187 | ||||
| -rw-r--r-- | executor/gen_linux_ppc64le.go | 7 | ||||
| -rw-r--r-- | executor/kvm_gen.cc | 4 | ||||
| -rw-r--r-- | executor/kvm_ppc64le.S | 39 | ||||
| -rw-r--r-- | executor/kvm_ppc64le.S.h | 4 | ||||
| -rw-r--r-- | executor/test_linux.h | 27 |
6 files changed, 256 insertions, 12 deletions
diff --git a/executor/common_kvm_ppc64.h b/executor/common_kvm_ppc64.h index 816c432e8..ec9e4a608 100644 --- a/executor/common_kvm_ppc64.h +++ b/executor/common_kvm_ppc64.h @@ -5,7 +5,48 @@ // Implementation of syz_kvm_setup_cpu pseudo-syscall. -#include "kvm.h" +#include "kvm_ppc64le.S.h" + +#define BOOK3S_INTERRUPT_SYSTEM_RESET 0x100 +#define BOOK3S_INTERRUPT_MACHINE_CHECK 0x200 +#define BOOK3S_INTERRUPT_DATA_STORAGE 0x300 +#define BOOK3S_INTERRUPT_DATA_SEGMENT 0x380 +#define BOOK3S_INTERRUPT_INST_STORAGE 0x400 +#define BOOK3S_INTERRUPT_INST_SEGMENT 0x480 +#define BOOK3S_INTERRUPT_EXTERNAL 0x500 +#define BOOK3S_INTERRUPT_EXTERNAL_HV 0x502 +#define BOOK3S_INTERRUPT_ALIGNMENT 0x600 +#define BOOK3S_INTERRUPT_PROGRAM 0x700 +#define BOOK3S_INTERRUPT_FP_UNAVAIL 0x800 +#define BOOK3S_INTERRUPT_DECREMENTER 0x900 +#define BOOK3S_INTERRUPT_HV_DECREMENTER 0x980 +#define BOOK3S_INTERRUPT_DOORBELL 0xa00 +#define BOOK3S_INTERRUPT_SYSCALL 0xc00 +#define BOOK3S_INTERRUPT_TRACE 0xd00 +#define BOOK3S_INTERRUPT_H_DATA_STORAGE 0xe00 +#define BOOK3S_INTERRUPT_H_INST_STORAGE 0xe20 +#define BOOK3S_INTERRUPT_H_EMUL_ASSIST 0xe40 +#define BOOK3S_INTERRUPT_HMI 0xe60 +#define BOOK3S_INTERRUPT_H_DOORBELL 0xe80 +#define BOOK3S_INTERRUPT_H_VIRT 0xea0 +#define BOOK3S_INTERRUPT_PERFMON 0xf00 +#define BOOK3S_INTERRUPT_ALTIVEC 0xf20 +#define BOOK3S_INTERRUPT_VSX 0xf40 +#define BOOK3S_INTERRUPT_FAC_UNAVAIL 0xf60 +#define BOOK3S_INTERRUPT_H_FAC_UNAVAIL 0xf80 + +#define BITS_PER_LONG 64 +#define PPC_BITLSHIFT(be) (BITS_PER_LONG - 1 - (be)) +#define PPC_BIT(bit) (1ULL << PPC_BITLSHIFT(bit)) + +#define cpu_to_be32(x) __builtin_bswap32(x) +#define LPCR_ILE PPC_BIT(38) +#ifndef KVM_REG_PPC_LPCR_64 +#define KVM_REG_PPC_LPCR_64 (KVM_REG_PPC | KVM_REG_SIZE_U64 | 0xb5) +#endif +#ifndef KVM_REG_PPC_DEC_EXPIRY +#define KVM_REG_PPC_DEC_EXPIRY (KVM_REG_PPC | KVM_REG_SIZE_U64 | 0xbe) +#endif struct kvm_text { uintptr_t typ; @@ -13,7 +54,49 @@ struct kvm_text { uintptr_t size; }; -// 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 int kvmppc_get_one_reg(int cpufd, uint64 id, void* target) +{ + struct kvm_one_reg reg = {.id = id, .addr = (uintptr_t)target}; + + return ioctl(cpufd, KVM_GET_ONE_REG, ®); +} + +static int kvmppc_set_one_reg(int cpufd, uint64 id, void* target) +{ + struct kvm_one_reg reg = {.id = id, .addr = (uintptr_t)target}; + + return ioctl(cpufd, KVM_SET_ONE_REG, ®); +} + +static int kvm_vcpu_enable_cap(int cpufd, uint32 capability) +{ + struct kvm_enable_cap cap = { + .cap = capability, + }; + return ioctl(cpufd, KVM_ENABLE_CAP, &cap); +} + +static void dump_text(const char* mem, unsigned start, unsigned cw, uint32 debug_inst_opcode) +{ +#ifdef DEBUG + printf("Text @%x: ", start); + + for (unsigned i = 0; i < cw; ++i) { + uint32 w = ((uint32*)(mem + start))[i]; + + printf(" %08x", w); + if (debug_inst_opcode && debug_inst_opcode == w) + break; + } + + printf("\n"); +#endif +} + +// Flags +#define KVM_SETUP_PPC64_LE (1 << 0) // Little endian + +// 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_ppc64], opts ptr[in, array[kvm_setup_opt, 0:2]], nopt len[opts]) static 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; @@ -21,25 +104,32 @@ static long syz_kvm_setup_cpu(volatile long a0, volatile long a1, volatile long char* const host_mem = (char*)a2; const struct kvm_text* const text_array_ptr = (struct kvm_text*)a3; const uintptr_t text_count = a4; - - const uintptr_t page_size = 16 << 10; - const uintptr_t guest_mem_size = 256 << 20; - const uintptr_t guest_mem = 0; + uintptr_t flags = a5; + const uintptr_t page_size = 0x10000; // SYZ_PAGE_SIZE + const uintptr_t guest_mem_size = 24 * page_size; // vma[24] from dev_kvm.txt + unsigned long gpa_off = 0; + uint32 debug_inst_opcode = 0; (void)text_count; // fuzzer can spoof count and we need just 1 text, so ignore text_count const void* text = 0; uintptr_t text_size = 0; + uint64 lpcr = 0; NONFAILING(text = text_array_ptr[0].text); NONFAILING(text_size = text_array_ptr[0].size); + if (kvm_vcpu_enable_cap(cpufd, KVM_CAP_PPC_PAPR)) + return -1; + 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.flags = 0; // can be KVM_MEM_LOG_DIRTY_PAGES but not KVM_MEM_READONLY + memreg.guest_phys_addr = 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); + if (ioctl(vmfd, KVM_SET_USER_MEMORY_REGION, &memreg)) { + return -1; + } } struct kvm_regs regs; @@ -49,15 +139,81 @@ static long syz_kvm_setup_cpu(volatile long a0, volatile long a1, volatile long if (ioctl(cpufd, KVM_GET_REGS, ®s)) return -1; - // PPC64LE, real mode: MSR_LE | MSR_SF - regs.msr = 1ULL | (1ULL << 63); + regs.msr = PPC_BIT(0); // MSR_SF == Sixty Four == 64bit + if (flags & KVM_SETUP_PPC64_LE) + regs.msr |= PPC_BIT(63); // Little endian + + // KVM HV on POWER is hard to force to exit, it will bounce between + // the fault handlers in KVM and the VM. Forcing all exception + // vectors to do software debug breakpoint ensures the exit from KVM. + if (kvmppc_get_one_reg(cpufd, KVM_REG_PPC_DEBUG_INST, &debug_inst_opcode)) + return -1; + +#define VEC(x) (*((uint32*)(host_mem + (x)))) + VEC(BOOK3S_INTERRUPT_SYSTEM_RESET) = debug_inst_opcode; + VEC(BOOK3S_INTERRUPT_MACHINE_CHECK) = debug_inst_opcode; + VEC(BOOK3S_INTERRUPT_DATA_STORAGE) = debug_inst_opcode; + VEC(BOOK3S_INTERRUPT_DATA_SEGMENT) = debug_inst_opcode; + VEC(BOOK3S_INTERRUPT_INST_STORAGE) = debug_inst_opcode; + VEC(BOOK3S_INTERRUPT_INST_SEGMENT) = debug_inst_opcode; + VEC(BOOK3S_INTERRUPT_EXTERNAL) = debug_inst_opcode; + VEC(BOOK3S_INTERRUPT_EXTERNAL_HV) = debug_inst_opcode; + VEC(BOOK3S_INTERRUPT_ALIGNMENT) = debug_inst_opcode; + VEC(BOOK3S_INTERRUPT_PROGRAM) = debug_inst_opcode; + VEC(BOOK3S_INTERRUPT_FP_UNAVAIL) = debug_inst_opcode; + memcpy(host_mem + BOOK3S_INTERRUPT_DECREMENTER, kvm_ppc64_recharge_dec, sizeof(kvm_ppc64_recharge_dec) - 1); + VEC(BOOK3S_INTERRUPT_DECREMENTER + sizeof(kvm_ppc64_recharge_dec) - 1) = debug_inst_opcode; + VEC(BOOK3S_INTERRUPT_HV_DECREMENTER) = debug_inst_opcode; + VEC(BOOK3S_INTERRUPT_DOORBELL) = debug_inst_opcode; + VEC(BOOK3S_INTERRUPT_SYSCALL) = debug_inst_opcode; + VEC(BOOK3S_INTERRUPT_TRACE) = debug_inst_opcode; + VEC(BOOK3S_INTERRUPT_H_DATA_STORAGE) = debug_inst_opcode; + VEC(BOOK3S_INTERRUPT_H_INST_STORAGE) = debug_inst_opcode; + VEC(BOOK3S_INTERRUPT_H_EMUL_ASSIST) = debug_inst_opcode; + VEC(BOOK3S_INTERRUPT_HMI) = debug_inst_opcode; + VEC(BOOK3S_INTERRUPT_H_DOORBELL) = debug_inst_opcode; + VEC(BOOK3S_INTERRUPT_H_VIRT) = debug_inst_opcode; + VEC(BOOK3S_INTERRUPT_PERFMON) = debug_inst_opcode; + VEC(BOOK3S_INTERRUPT_ALTIVEC) = debug_inst_opcode; + VEC(BOOK3S_INTERRUPT_VSX) = debug_inst_opcode; + VEC(BOOK3S_INTERRUPT_FAC_UNAVAIL) = debug_inst_opcode; + VEC(BOOK3S_INTERRUPT_H_FAC_UNAVAIL) = debug_inst_opcode; + + struct kvm_guest_debug dbg = {0}; + dbg.control = KVM_GUESTDBG_ENABLE | KVM_GUESTDBG_USE_SW_BP; + + if (ioctl(cpufd, KVM_SET_GUEST_DEBUG, &dbg)) + return -1; + + // Exception vector occupy 128K, including "System Call Vectored" + gpa_off = 128 << 10; + + memcpy(host_mem + gpa_off, text, text_size); + regs.pc = gpa_off; + + uintptr_t end_of_text = gpa_off + ((text_size + 3) & ~3); + memcpy(host_mem + end_of_text, &debug_inst_opcode, sizeof(debug_inst_opcode)); + + // The code generator produces little endian instructions so swap bytes here + if (!(flags & KVM_SETUP_PPC64_LE)) { + uint32* p = (uint32*)(host_mem + gpa_off); + for (unsigned long i = 0; i < text_size / sizeof(*p); ++i) + p[i] = cpu_to_be32(p[i]); - memcpy(host_mem, text, text_size); + p = (uint32*)(host_mem + BOOK3S_INTERRUPT_DECREMENTER); + for (unsigned long i = 0; i < sizeof(kvm_ppc64_recharge_dec) / sizeof(*p); ++i) + p[i] = cpu_to_be32(p[i]); + } else { + // PPC by default calls exception handlers in big endian unless ILE + lpcr |= LPCR_ILE; + } if (ioctl(cpufd, KVM_SET_SREGS, &sregs)) return -1; if (ioctl(cpufd, KVM_SET_REGS, ®s)) return -1; + if (kvmppc_set_one_reg(cpufd, KVM_REG_PPC_LPCR_64, &lpcr)) + return -1; // Hypercalls need to be enable so we enable them all here to // allow fuzzing @@ -71,5 +227,12 @@ static long syz_kvm_setup_cpu(volatile long a0, volatile long a1, volatile long ioctl(vmfd, KVM_ENABLE_CAP, &cap); } + dump_text(host_mem, regs.pc, 8, debug_inst_opcode); + dump_text(host_mem, BOOK3S_INTERRUPT_DECREMENTER, 16, debug_inst_opcode); + + uint64 decr = 0x7fffffff; + if (kvmppc_set_one_reg(cpufd, KVM_REG_PPC_DEC_EXPIRY, &decr)) + return -1; + return 0; } diff --git a/executor/gen_linux_ppc64le.go b/executor/gen_linux_ppc64le.go new file mode 100644 index 000000000..1c66d5caa --- /dev/null +++ b/executor/gen_linux_ppc64le.go @@ -0,0 +1,7 @@ +// Copyright 2021 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. + +// nolint: lll +//go:generate bash -c "gcc -DGOARCH_$GOARCH=1 kvm_gen.cc kvm_ppc64le.S -o kvm_gen && ./kvm_gen > kvm_ppc64le.S.h && rm ./kvm_gen" + +package executor diff --git a/executor/kvm_gen.cc b/executor/kvm_gen.cc index 7df6e9bd7..bfbb0e8e7 100644 --- a/executor/kvm_gen.cc +++ b/executor/kvm_gen.cc @@ -29,6 +29,10 @@ int main() PRINT(kvm_asm64_init_vm); PRINT(kvm_asm64_vm_exit); PRINT(kvm_asm64_cpl3); +#elif GOARCH_ppc64le + PRINT(kvm_ppc64_mr); + PRINT(kvm_ppc64_ld); + PRINT(kvm_ppc64_recharge_dec); #endif return 0; } diff --git a/executor/kvm_ppc64le.S b/executor/kvm_ppc64le.S new file mode 100644 index 000000000..0a177229c --- /dev/null +++ b/executor/kvm_ppc64le.S @@ -0,0 +1,39 @@ +// Copyright 2021 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. + +// kvm_gen.cc generates machine code from this file and saves it into kvm_ppc64le.S.h. + +// +build + +#include "kvm.h" + +#define LOAD64(rn,name) \ + lis rn,name##@highest; \ + ori rn,rn,name##@higher; \ + rldicr rn,rn,32,31; \ + oris rn,rn,name##@h; \ + ori rn,rn,name##@l + +.global kvm_ppc64_mr, kvm_ppc64_mr_end +kvm_ppc64_mr: + LOAD64(5, 0xbadc0de) + mr 4,5 + mr 3,4 +kvm_ppc64_mr_end: + +.global kvm_ppc64_ld, kvm_ppc64_ld_end +kvm_ppc64_ld: + LOAD64(15, 0xbadc0de) + // Last double word of vma[24] + LOAD64(25, 24 * 0x10000 - 8) + std 15, 0(25) + ld 3, 0(25) +kvm_ppc64_ld_end: + +.global kvm_ppc64_recharge_dec, kvm_ppc64_recharge_dec_end +kvm_ppc64_recharge_dec: + LOAD64(20,0x7ffffff) +#define SPRN_DEC 0x016 /* Decrement Register */ + mtspr SPRN_DEC,20 + rfid +kvm_ppc64_recharge_dec_end: diff --git a/executor/kvm_ppc64le.S.h b/executor/kvm_ppc64le.S.h new file mode 100644 index 000000000..f4014cc64 --- /dev/null +++ b/executor/kvm_ppc64le.S.h @@ -0,0 +1,4 @@ +// Code generated by executor/kvm_gen.cc. DO NOT EDIT. +const char kvm_ppc64_mr[] = "\x00\x00\xa0\x3c\x00\x00\xa5\x60\xc6\x07\xa5\x78\xad\x0b\xa5\x64\xde\xc0\xa5\x60\x78\x2b\xa4\x7c\x78\x23\x83\x7c"; +const char kvm_ppc64_ld[] = "\x00\x00\xe0\x3d\x00\x00\xef\x61\xc6\x07\xef\x79\xad\x0b\xef\x65\xde\xc0\xef\x61\x00\x00\x20\x3f\x00\x00\x39\x63\xc6\x07\x39\x7b\x17\x00\x39\x67\xf8\xff\x39\x63\x00\x00\xf9\xf9\x00\x00\x79\xe8"; +const char kvm_ppc64_recharge_dec[] = "\x00\x00\x80\x3e\x00\x00\x94\x62\xc6\x07\x94\x7a\xff\x07\x94\x66\xff\xff\x94\x62\xa6\x03\x96\x7e\x24\x00\x00\x4c"; diff --git a/executor/test_linux.h b/executor/test_linux.h index 69d647a4d..84efbdba0 100644 --- a/executor/test_linux.h +++ b/executor/test_linux.h @@ -84,6 +84,12 @@ static int test_one(int text_type, const char* text, int text_size, int flags, u dump_cpu_state(cpufd, (char*)vm_mem); return 1; } +#elif GOARCH_ppc64le + if (check_rax && regs.gpr[3] != 0xbadc0de) { + printf("wrong result: gps[3]=0x%llx\n", (long long)regs.gpr[3]); + dump_cpu_state(cpufd, (char*)vm_mem); + return 1; + } #endif munmap(vm_mem, vm_mem_size); munmap(cpu_mem, cpu_mem_size); @@ -167,6 +173,15 @@ static int test_kvm() if ((res = test_one(32, text_rsm, sizeof(text_rsm) - 1, KVM_SETUP_SMM, KVM_EXIT_HLT, false))) return res; } +#elif GOARCH_ppc64le + for (unsigned i = 0; i < (1 << 1); ++i) { + res = test_one(8, kvm_ppc64_mr, sizeof(kvm_ppc64_mr) - 1, i, KVM_EXIT_DEBUG, true); + if (res) + return res; + res = test_one(8, kvm_ppc64_ld, sizeof(kvm_ppc64_ld) - 1, i, KVM_EXIT_DEBUG, true); + if (res) + return res; + } #else // Keeping gcc happy const char text8[] = "\x66\xb8\xde\xc0\xad\x0b"; @@ -234,5 +249,17 @@ static void dump_cpu_state(int cpufd, char* vm_mem) ((long long*)vm_mem)[i], ((long long*)vm_mem)[i + 1], ((long long*)vm_mem)[i + 2], ((long long*)vm_mem)[i + 3]); } } +#elif GOARCH_ppc64 || GOARCH_ppc64le + printf("NIP %016lx\n", regs.pc); + printf("MSR %016lx\n", regs.msr); + printf("GPR00 %016lx %016lx %016lx %016lx\n", regs.gpr[0], regs.gpr[1], regs.gpr[2], regs.gpr[3]); + printf("GPR04 %016lx %016lx %016lx %016lx\n", regs.gpr[4], regs.gpr[5], regs.gpr[6], regs.gpr[7]); + printf("GPR08 %016lx %016lx %016lx %016lx\n", regs.gpr[8], regs.gpr[9], regs.gpr[10], regs.gpr[11]); + printf("GPR12 %016lx %016lx %016lx %016lx\n", regs.gpr[12], regs.gpr[13], regs.gpr[14], regs.gpr[15]); + printf("GPR16 %016lx %016lx %016lx %016lx\n", regs.gpr[16], regs.gpr[17], regs.gpr[18], regs.gpr[19]); + printf("GPR20 %016lx %016lx %016lx %016lx\n", regs.gpr[20], regs.gpr[21], regs.gpr[22], regs.gpr[23]); + printf("GPR24 %016lx %016lx %016lx %016lx\n", regs.gpr[24], regs.gpr[25], regs.gpr[26], regs.gpr[27]); + printf("GPR28 %016lx %016lx %016lx %016lx\n", regs.gpr[28], regs.gpr[29], regs.gpr[30], regs.gpr[31]); + printf(" SRR0 %016lx SRR1 %016lx\n", regs.srr0, regs.srr1); #endif } |
