diff options
| author | Alexey Kardashevskiy <aik@linux.ibm.com> | 2021-07-14 15:19:57 +1000 |
|---|---|---|
| committer | Dmitry Vyukov <dvyukov@google.com> | 2021-07-19 11:29:36 +0200 |
| commit | e00224d9ff393517c827e5e827f7638a9eaff812 (patch) | |
| tree | 67c9a5e343b45db9f5ebb252ebaee157e54bbf6e /executor | |
| parent | 5044cb7c5d2dddf1e93b895e800cd64b100a8e18 (diff) | |
executor/common_kvm_ppc64: fix KVM support
Turns out the ifuzz on powerpc did not ever properly work. This fixes
syz_kvm_setup_cpu$ppc64:
Enable the PAPR KVM capability (otherwise KVM_RUN fails right away).
Finish generated sequences with the software debug breakpoint as
there is no x86's "hlt" variant on POWER and otherwise KVM won't exit.
Add exception handlers, use the software debug breakpoint instruction
to trigger immediate exit from KVM with the only exception of
the decrementer interrupt handler (timer) to recharge the timer and
continue.
Define and use endianness selection flag (Big vs. Little endian).
Define the code generator similar to kvm_gen.cc which for now contains
2 simple tests and the decrementer interrupt handler code.
Add test cases to the executor so "bin/linux_ppc64le/syz-executor test"
can run some sensible tests. The tests copy 0xbadc0de around similar
to x86 and uses gpr[3] is a return value register (similar to EAX).
Signed-off-by: Alexey Kardashevskiy <aik@linux.ibm.com>
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 } |
