Skip to content

Commit

Permalink
executor/common_kvm_ppc64: fix KVM support
Browse files Browse the repository at this point in the history
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 <[email protected]>
  • Loading branch information
Alexey Kardashevskiy authored and dvyukov committed Jul 19, 2021
1 parent 5044cb7 commit e00224d
Show file tree
Hide file tree
Showing 10 changed files with 437 additions and 105 deletions.
1 change: 1 addition & 0 deletions .gitattributes
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
executor/kvm_amd64.S.h linguist-generated
executor/kvm_ppc64le.S.h linguist-generated
187 changes: 175 additions & 12 deletions executor/common_kvm_ppc64.h
Original file line number Diff line number Diff line change
Expand Up @@ -5,41 +5,131 @@

// 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;
const void* 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;
const int cpufd = a1;
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;
Expand All @@ -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
Expand All @@ -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;
}
7 changes: 7 additions & 0 deletions executor/gen_linux_ppc64le.go
Original file line number Diff line number Diff line change
@@ -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
4 changes: 4 additions & 0 deletions executor/kvm_gen.cc
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
39 changes: 39 additions & 0 deletions executor/kvm_ppc64le.S
Original file line number Diff line number Diff line change
@@ -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:
4 changes: 4 additions & 0 deletions executor/kvm_ppc64le.S.h

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

27 changes: 27 additions & 0 deletions executor/test_linux.h
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down Expand Up @@ -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";
Expand Down Expand Up @@ -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
}
1 change: 1 addition & 0 deletions pkg/csource/gen.go
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ func main() {
"android/android_seccomp.h",
"kvm.h",
"kvm_amd64.S.h",
"kvm_ppc64le.S.h",
}
data = replaceIncludes(executorFilenames, "../../executor/", data)
androidFilenames := []string{
Expand Down
Loading

0 comments on commit e00224d

Please sign in to comment.