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<a...@redhat.com>
---
  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<a...@redhat.com>
+ *
+ * This work is licensed under the terms of the GNU GPL, version 2.  See
+ * the COPYING file in the top-level directory.
+ *
+ */
+
+#include<linux/module.h>
+#include<linux/kernel.h>
+#include<linux/random.h>
+#include<asm/kvm_host.h>
+#include<asm/kvm_emulate.h>
+#include<asm/i387.h>

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)

--
To unsubscribe from this list: send the line "unsubscribe kvm" in
the body of a message to majord...@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html

Reply via email to