From mboxrd@z Thu Jan 1 00:00:00 1970 From: Lucas Meneghel Rodrigues Subject: Re: [PATCH 3/3] KVM: x86 emulator: fuzz tester Date: Thu, 25 Aug 2011 19:17:11 -0300 Message-ID: <4E56C9E7.9030206@redhat.com> References: <1314020469-30882-1-git-send-email-avi@redhat.com> <1314020469-30882-4-git-send-email-avi@redhat.com> Mime-Version: 1.0 Content-Type: text/plain; charset=ISO-8859-1; format=flowed Content-Transfer-Encoding: 7bit Cc: Marcelo Tosatti , kvm@vger.kernel.org To: Avi Kivity Return-path: Received: from mx1.redhat.com ([209.132.183.28]:1815 "EHLO mx1.redhat.com" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S1755611Ab1HYWRO (ORCPT ); Thu, 25 Aug 2011 18:17:14 -0400 Received: from int-mx10.intmail.prod.int.phx2.redhat.com (int-mx10.intmail.prod.int.phx2.redhat.com [10.5.11.23]) by mx1.redhat.com (8.14.4/8.14.4) with ESMTP id p7PMHD2q022606 (version=TLSv1/SSLv3 cipher=DHE-RSA-AES256-SHA bits=256 verify=OK) for ; Thu, 25 Aug 2011 18:17:14 -0400 In-Reply-To: <1314020469-30882-4-git-send-email-avi@redhat.com> Sender: kvm-owner@vger.kernel.org List-ID: On 08/22/2011 10:41 AM, Avi Kivity wrote: > The x86 emulator is directly exposed to guest code; therefore it is part > of the directly exposed attack surface. To reduce the risk of > vulnerabilities, this patch adds a fuzz test that runs random instructions > through the emulator. A vulnerability will usually result in an oops. > > One way to run the test is via KVM itself: > > qemu -enable-kvm -smp 4 -serial stdio -kernel bzImage \ > -append 'console=ttyS0 test_emulator.iterations=1000000000' > > this requires that the test module be built into the kernel. > > Signed-off-by: Avi Kivity > --- > arch/x86/Kbuild | 1 + > arch/x86/kvm/Kconfig | 11 + > arch/x86/kvm/Makefile | 1 + > arch/x86/kvm/test-emulator.c | 533 ++++++++++++++++++++++++++++++++++++++++++ > 4 files changed, 546 insertions(+), 0 deletions(-) > create mode 100644 arch/x86/kvm/test-emulator.c > > diff --git a/arch/x86/Kbuild b/arch/x86/Kbuild > index 0e9dec6..0d80e6f 100644 > --- a/arch/x86/Kbuild > +++ b/arch/x86/Kbuild > @@ -1,5 +1,6 @@ > > obj-$(CONFIG_KVM) += kvm/ > +obj-$(CONFIG_KVM_EMULATOR_TEST) += kvm/ > > # Xen paravirtualization support > obj-$(CONFIG_XEN) += xen/ > diff --git a/arch/x86/kvm/Kconfig b/arch/x86/kvm/Kconfig > index ff5790d..9ffc30a 100644 > --- a/arch/x86/kvm/Kconfig > +++ b/arch/x86/kvm/Kconfig > @@ -76,6 +76,17 @@ config KVM_MMU_AUDIT > This option adds a R/W kVM module parameter 'mmu_audit', which allows > audit KVM MMU at runtime. > > +config KVM_EMULATOR_TEST > + tristate "KVM emulator self test" > + depends on KVM > + ---help--- > + Build test code that checks the x86 emulator during boot or module > + insertion. If built as a module, it will be called test-emulator.ko. > + > + The emulator test will run for as many iterations as are specified by > + the emulator_test.iterations parameter; all processors will be > + utilized. When the test is complete, results are reported in dmesg. > + > # OK, it's a little counter-intuitive to do this, but it puts it neatly under > # the virtualization menu. > source drivers/vhost/Kconfig > diff --git a/arch/x86/kvm/Makefile b/arch/x86/kvm/Makefile > index f15501f..fc4a9e2 100644 > --- a/arch/x86/kvm/Makefile > +++ b/arch/x86/kvm/Makefile > @@ -19,3 +19,4 @@ kvm-amd-y += svm.o > obj-$(CONFIG_KVM) += kvm.o > obj-$(CONFIG_KVM_INTEL) += kvm-intel.o > obj-$(CONFIG_KVM_AMD) += kvm-amd.o > +obj-$(CONFIG_KVM_EMULATOR_TEST) += test-emulator.o > diff --git a/arch/x86/kvm/test-emulator.c b/arch/x86/kvm/test-emulator.c > new file mode 100644 > index 0000000..1e3a22f > --- /dev/null > +++ b/arch/x86/kvm/test-emulator.c > @@ -0,0 +1,533 @@ > +/* > + * x86 instruction emulator test > + * > + * Copyright 2011 Red Hat, Inc. and/or its affiliates. > + * > + * Authors: > + * Avi Kivity > + * > + * This work is licensed under the terms of the GNU GPL, version 2. See > + * the COPYING file in the top-level directory. > + * > + */ > + > +#include > +#include > +#include > +#include > +#include > +#include I still haven't gone through all the code, but it's a good idea to put a MODULE_LICENSE("GPL") macro around here, so the build system doesn't complain about it: WARNING: modpost: missing MODULE_LICENSE() in arch/x86/kvm/test-emulator.o see include/linux/module.h for more information > +static ulong iterations = 0; > +module_param(iterations, ulong, S_IRUGO); > + > +struct test_context { > + struct work_struct work; > + struct completion completion; > + struct x86_emulate_ctxt ctxt; > + struct test_context *next; > + bool failed; > + u8 insn[15]; > + bool insn_base_valid; > + ulong insn_base; > + struct test_seg { > + u16 selector; > + struct desc_struct desc; > + u32 base3; > + bool valid; > + } segs[8]; > + ulong iterations; > + ulong completed; > + ulong decoded; > + ulong emulated; > + ulong nofault; > + ulong failures; > +}; > + > +static u64 random64(void) > +{ > + return random32() | ((u64)random32()<< 32); > +} > + > +static ulong randlong(void) > +{ > + if (sizeof(ulong) == sizeof(u32)) > + return random32(); > + else > + return random64(); > +} > + > +static struct test_context *to_test(struct x86_emulate_ctxt *ctxt) > +{ > + return container_of(ctxt, struct test_context, ctxt); > +} > + > +static void fail(struct x86_emulate_ctxt *ctxt, const char *msg, ...) > + __attribute__((format(printf, 2, 3))); > + > +static void fail(struct x86_emulate_ctxt *ctxt, const char *msg, ...) > +{ > + va_list args; > + char s[200]; > + > + va_start(args, msg); > + vsnprintf(s, sizeof(s), msg, args); > + va_end(args); > + printk("emulator test failure: %s\n", s); > + to_test(ctxt)->failed = true; > +} > + > +static int test_fill_exception(struct x86_exception *ex) > +{ > + if (random32() % 4 == 0) { > + if (ex) { > + ex->vector = random32(); > + ex->error_code_valid = random32(); > + ex->error_code = random32(); > + ex->nested_page_fault = random32(); > + ex->address = random64(); > + } > + return X86EMUL_PROPAGATE_FAULT; > + } > + return X86EMUL_CONTINUE; > +} > + > +static int rand_error(void) > +{ > + switch (random32() % 8) { > + case 0: return X86EMUL_UNHANDLEABLE; > + case 1: return X86EMUL_IO_NEEDED; > + default: return X86EMUL_CONTINUE; > + } > +} > + > +static int test_read(struct x86_emulate_ctxt *ctxt, > + unsigned long addr, void *val, > + unsigned int bytes, > + struct x86_exception *fault) > +{ > + unsigned i; > + > + if (bytes> 32 || bytes == 0) > + fail(ctxt, "read %x bytes", bytes); > + > + for (i = 0; i< bytes; ++i) > + *(u8 *)(val + i) = random32(); > + > + return test_fill_exception(fault); > +} > + > +static int test_write(struct x86_emulate_ctxt *ctxt, > + unsigned long addr, const void *val, unsigned int bytes, > + struct x86_exception *fault) > +{ > + if (bytes> 32 || bytes == 0) > + fail(ctxt, "write %x bytes", bytes); > + > + return test_fill_exception(fault); > +} > + > +static int test_fetch(struct x86_emulate_ctxt *ctxt, > + unsigned long addr, void *val, > + unsigned int bytes, > + struct x86_exception *fault) > +{ > + struct test_context *test = to_test(ctxt); > + > + if (bytes> 15 || bytes == 0) > + fail(ctxt, "fetch %x bytes", bytes); > + > + if (!test->insn_base_valid) { > + test->insn_base_valid = true; > + test->insn_base = addr; > + } > + addr -= test->insn_base; > + if (addr>= 15 || addr + bytes> 15) > + fail(ctxt, "fetch %x from %lx vs %lx", > + bytes, addr + test->insn_base, test->insn_base); > + else > + memcpy(val, test->insn + addr, bytes); > + > + return test_fill_exception(fault); > +} > + > +static int test_cmpxchg(struct x86_emulate_ctxt *ctxt, > + unsigned long addr, > + const void *old, > + const void *new, > + unsigned int bytes, > + struct x86_exception *fault) > +{ > + if (bytes> 16 || bytes == 0 || hweight32(bytes) != 1) > + fail(ctxt, "cmpxchg %x bytes", bytes); > + > + return test_fill_exception(fault); > +} > + > +static void test_invlpg(struct x86_emulate_ctxt *ctxt, ulong addr) > +{ > +} > + > +static int test_pio_in(struct x86_emulate_ctxt *ctxt, > + int size, unsigned short port, void *val, > + unsigned int count) > +{ > + if ((size != 1&& size != 2&& size != 4) > + || (count == 0 || count * size> 4096)) > + fail(ctxt, "pio_in_emulated: size %x count %x\n", size, count); > + > + return rand_error(); > +} > + > +static int test_pio_out(struct x86_emulate_ctxt *ctxt, > + int size, unsigned short port, const void *val, > + unsigned int count) > +{ > + if ((size != 1&& size != 2&& size != 4) > + || (count == 0 || count * size> 4096)) > + fail(ctxt, "pio_out_emulated: size %x count %x\n", size, count); > + > + return rand_error(); > +} > + > +static bool test_get_segment(struct x86_emulate_ctxt *ctxt, u16 *selector, > + struct desc_struct *desc, u32 *base3, int seg) > +{ > + struct test_context *test = to_test(ctxt); > + struct test_seg *s =&test->segs[seg]; > + > + if (seg< 0 || seg> 7) > + fail(ctxt, "bad segment %d\n", seg); > + > + if (!s->valid) { > + s->valid = true; > + s->selector = random32(); > + s->desc.a = random32(); > + s->desc.b = random32(); > + s->base3 = random32(); > + } > + > + *selector = s->selector; > + desc->a = s->desc.a; > + desc->b = s->desc.b; > + if (base3) > + *base3 = s->base3; > + > + return random32()& 1; > +} > + > +static void test_set_segment(struct x86_emulate_ctxt *ctxt, u16 selector, > + struct desc_struct *desc, u32 base3, int seg) > +{ > + if (seg< 0 || seg> 5) > + fail(ctxt, "bad segment %d\n", seg); > +} > + > +static ulong segment_base(struct x86_emulate_ctxt *ctxt, > + struct desc_struct *d, u32 base3) > +{ > + unsigned long v; > + > + v = get_desc_base(d); > + if (ctxt->mode == X86EMUL_MODE_PROT64 > + && d->s == 0&& (d->type == 2 || d->type == 9 || d->type == 11)) > + v |= (u64)base3<< 32; > + return v; > +} > + > +static unsigned long test_get_cached_segment_base(struct x86_emulate_ctxt *ctxt, > + int seg) > +{ > + u16 selector; > + struct desc_struct desc; > + u32 base3; > + > + test_get_segment(ctxt,&selector,&desc,&base3, seg); > + return segment_base(ctxt,&desc, base3); > +} > + > +static void test_get_desc_table(struct x86_emulate_ctxt *ctxt, > + struct desc_ptr *dt) > +{ > + dt->size = random32(); > + dt->address = randlong(); > +} > + > +static void test_set_desc_table(struct x86_emulate_ctxt *ctxt, > + struct desc_ptr *dt) > +{ > +} > + > +static bool valid_cr[] = { > + [0] = true, [2] = true, [3] = true, [4] = true, [8] = true, > +}; > + > +static void check_cr(struct x86_emulate_ctxt *ctxt, int cr) > +{ > + if (cr< 0 || cr> ARRAY_SIZE(valid_cr) || !valid_cr[cr]) > + fail(ctxt, "bad cr %d\n", cr); > +} > + > +static ulong test_get_cr(struct x86_emulate_ctxt *ctxt, int cr) > +{ > + check_cr(ctxt, cr); > + return randlong(); > +} > + > +static int test_set_cr(struct x86_emulate_ctxt *ctxt, int cr, ulong val) > +{ > + check_cr(ctxt, cr); > + return random32()& 1; > +} > + > +static int test_cpl(struct x86_emulate_ctxt *ctxt) > +{ > + return random32()& 3; > +} > + > +static void check_dr(struct x86_emulate_ctxt *ctxt, int dr) > +{ > + if (dr< 0 || dr> 7) > + fail(ctxt, "bad dr %d\n", dr); > +} > + > +static int test_get_dr(struct x86_emulate_ctxt *ctxt, int dr, ulong *dest) > +{ > + check_dr(ctxt, dr); > + *dest = randlong(); > + return random32()& 1; > +} > + > +static int test_set_dr(struct x86_emulate_ctxt *ctxt, int dr, ulong value) > +{ > + check_dr(ctxt, dr); > + return random32()& 1; > +} > + > +static int test_set_msr(struct x86_emulate_ctxt *ctxt, u32 msr_index, u64 data) > +{ > + return random32()& 1; > +} > + > +static int test_get_msr(struct x86_emulate_ctxt *ctxt, u32 msr_index, > + u64 *pdata) > +{ > + *pdata = random64(); > + return random32()& 1; > +} > + > +static void test_halt(struct x86_emulate_ctxt *ctxt) > +{ > +} > + > +static void test_wbinvd(struct x86_emulate_ctxt *ctxt) > +{ > +} > + > +static int test_fix_hypercall(struct x86_emulate_ctxt *ctxt) > +{ > + return rand_error(); > +} > + > +static void test_get_fpu(struct x86_emulate_ctxt *ctxt) > +{ > + kernel_fpu_begin(); > + /* FIXME: randomize state? */ > +} > + > +static void test_put_fpu(struct x86_emulate_ctxt *ctxt) > +{ > + kernel_fpu_end(); > +} > + > +static int test_intercept(struct x86_emulate_ctxt *ctxt, > + struct x86_instruction_info *info, > + enum x86_intercept_stage stage) > +{ > + return X86EMUL_CONTINUE; > +} > + > +static struct x86_emulate_ops test_ops = { > + .read_std = test_read, > + .write_std = test_write, > + .fetch = test_fetch, > + .read_emulated = test_read, > + .write_emulated = test_write, > + .cmpxchg_emulated = test_cmpxchg, > + .invlpg = test_invlpg, > + .pio_in_emulated = test_pio_in, > + .pio_out_emulated = test_pio_out, > + .get_segment = test_get_segment, > + .set_segment = test_set_segment, > + .get_cached_segment_base = test_get_cached_segment_base, > + .get_gdt = test_get_desc_table, > + .get_idt = test_get_desc_table, > + .set_gdt = test_set_desc_table, > + .set_idt = test_set_desc_table, > + .get_cr = test_get_cr, > + .set_cr = test_set_cr, > + .cpl = test_cpl, > + .get_dr = test_get_dr, > + .set_dr = test_set_dr, > + .set_msr = test_set_msr, > + .get_msr = test_get_msr, > + .halt = test_halt, > + .wbinvd = test_wbinvd, > + .fix_hypercall = test_fix_hypercall, > + .get_fpu = test_get_fpu, > + .put_fpu = test_put_fpu, > + .intercept = test_intercept, > +}; > + > +static int modes[] = { > + X86EMUL_MODE_REAL, > + X86EMUL_MODE_VM86, > + X86EMUL_MODE_PROT16, > + X86EMUL_MODE_PROT32, > + X86EMUL_MODE_PROT64, > +}; > + > +static int test_emulator_one(struct test_context *test) > +{ > + struct x86_emulate_ctxt *ctxt =&test->ctxt; > + unsigned i; > + int r; > + > + test->failed = false; > + i = 0; > + if (random32()& 1) > + test->insn[i++] = 0x0f; > + for (; i< 15; ++i) > + test->insn[i++] = random32(); > + test->insn_base_valid = false; > + ctxt->ops =&test_ops; > + ctxt->eflags = randlong(); > + ctxt->eip = randlong(); > + ctxt->mode = modes[random32() % ARRAY_SIZE(modes)]; > + ctxt->guest_mode = random32() % 16 == 0; > + ctxt->perm_ok = random32() % 16 == 0; > + ctxt->only_vendor_specific_insn = random32() % 64 == 0; > + memset(&ctxt->twobyte, 0, > + (void *)&ctxt->regs - (void *)&ctxt->twobyte); > + for (i = 0; i< NR_VCPU_REGS; ++i) > + ctxt->regs[i] = randlong(); > + r = x86_decode_insn(ctxt, NULL, 0); > + if (r == EMULATION_OK) { > + ++test->decoded; > + r = x86_emulate_insn(ctxt); > + if (r == EMULATION_OK) { > + ++test->emulated; > + if (!ctxt->have_exception) > + ++test->nofault; > + } > + } > + > + ++test->completed; > + > + return test->failed ? -EINVAL : 0; > +} > + > +static const char *regnames[] = { > + [VCPU_REGS_RAX] = "rax", > + [VCPU_REGS_RBX] = "rbx", > + [VCPU_REGS_RCX] = "rcx", > + [VCPU_REGS_RDX] = "rdx", > + [VCPU_REGS_RSI] = "rsi", > + [VCPU_REGS_RDI] = "rdi", > + [VCPU_REGS_RSP] = "rsp", > + [VCPU_REGS_RBP] = "rbp", > + [VCPU_REGS_R8] = "r8", > + [VCPU_REGS_R9] = "r9", > + [VCPU_REGS_R10] = "r10", > + [VCPU_REGS_R11] = "r11", > + [VCPU_REGS_R12] = "r12", > + [VCPU_REGS_R13] = "r13", > + [VCPU_REGS_R14] = "r14", > + [VCPU_REGS_R15] = "r15", > + [VCPU_REGS_RIP] = "rip", > +}; > + > +static void dump_test_context(struct test_context *test) > +{ > + unsigned i; > + > + printk("instruction: %02x %02x %02x %02x %02x %02x %02x %02x" > + " %02x %02x %02x %02x %02x %02x %02x\n", > + test->insn[0], test->insn[1], test->insn[2], test->insn[3], > + test->insn[4], test->insn[5], test->insn[6], test->insn[7], > + test->insn[8], test->insn[9], test->insn[10], test->insn[11], > + test->insn[12], test->insn[13], test->insn[14]); > + for (i = 0; i< NR_VCPU_REGS; ++i) > + printk(" %s: %016llx\n", regnames[i], (u64)test->ctxt.regs[i]); > +} > + > +static void test_emulator_thread(struct work_struct *work) > +{ > + int i, ret; > + struct test_context *test > + = container_of(work, struct test_context, work); > + > + for (i = 0, ret = 0; i< test->iterations&& ret == 0; ++i) { > + ret = test_emulator_one(test); > + cond_resched(); > + } > + > + if (ret) { > + ++test->failures; > + printk("test failure in instruction %i\n", i); > + dump_test_context(test); > + } > + > + complete(&test->completion); > +} > + > +static __init int test_emulator(void) > +{ > + int r, cpu, remain; > + struct test_context *test = NULL, *tmp; > + ulong completed = 0, decoded = 0, emulated = 0, nofault = 0; > + ulong failures = 0; > + > + if (!iterations) > + return 0; > + > + pr_info("starting emulator test\n"); > + remain = num_online_cpus(); > + for_each_online_cpu(cpu) { > + tmp = kzalloc(sizeof(*tmp), GFP_KERNEL); > + r = -ENOMEM; > + if (!tmp) > + break; > + tmp->next = test; > + test = tmp; > + test->iterations = iterations / remain--; > + iterations -= test->iterations; > + INIT_WORK(&test->work, test_emulator_thread); > + init_completion(&test->completion); > + schedule_work_on(cpu,&test->work); > + } > + while (test) { > + wait_for_completion(&test->completion); > + completed += test->completed; > + decoded += test->decoded; > + emulated += test->emulated; > + nofault += test->nofault; > + failures += test->failures; > + tmp = test; > + test = test->next; > + kfree(tmp); > + } > + pr_info("emulator fuzz test results\n"); > + pr_info(" instructions: %12ld\n", completed); > + pr_info(" decoded: %12ld\n", decoded); > + pr_info(" emulated: %12ld\n", emulated); > + pr_info(" nofault: %12ld\n", nofault); > + pr_info(" failures: %12ld\n", failures); > + if (failures || remain) > + pr_err("emulator test: FAIL\n"); > + else > + pr_info("emulator test: PASS\n"); > + return 0; > +} > + > +module_init(test_emulator)