aboutsummaryrefslogtreecommitdiffstats
path: root/executor
diff options
context:
space:
mode:
Diffstat (limited to 'executor')
-rw-r--r--executor/common_kvm_ppc64.h187
-rw-r--r--executor/gen_linux_ppc64le.go7
-rw-r--r--executor/kvm_gen.cc4
-rw-r--r--executor/kvm_ppc64le.S39
-rw-r--r--executor/kvm_ppc64le.S.h4
-rw-r--r--executor/test_linux.h27
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, &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, &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, &regs))
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, &regs))
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
}