kmemcheck: add opcode self-testing at boot
authorVegard Nossum <vegard.nossum@gmail.com>
Fri, 27 Feb 2009 10:35:55 +0000 (11:35 +0100)
committerVegard Nossum <vegard.nossum@gmail.com>
Mon, 15 Jun 2009 13:49:22 +0000 (15:49 +0200)
We've had some troubles in the past with weird instructions. This
patch adds a self-test framework which can be used to verify that
a certain set of opcodes are decoded correctly. Of course, the
opcodes which are not tested can still give the wrong results.

In short, this is just a safeguard to catch unintentional changes
in the opcode decoder. It does not mean that errors can't still
occur!

[rebased for mainline inclusion]
Signed-off-by: Vegard Nossum <vegard.nossum@gmail.com>
arch/x86/mm/kmemcheck/Makefile
arch/x86/mm/kmemcheck/kmemcheck.c
arch/x86/mm/kmemcheck/selftest.c [new file with mode: 0644]
arch/x86/mm/kmemcheck/selftest.h [new file with mode: 0644]

index 4666b7a778be73e67fd3428456b7a6c0b071e75f..520b3bce4095ffafa1d563f548b66c30c4f0434e 100644 (file)
@@ -1 +1 @@
-obj-y := error.o kmemcheck.o opcode.o pte.o shadow.o
+obj-y := error.o kmemcheck.o opcode.o pte.o selftest.o shadow.o
index 6931e5115bcd1a2544e6be684915278dd12a90d2..2c55ed098654f3815586973a52446b83c3db5228 100644 (file)
 #include "error.h"
 #include "opcode.h"
 #include "pte.h"
+#include "selftest.h"
 #include "shadow.h"
 
+
 #ifdef CONFIG_KMEMCHECK_DISABLED_BY_DEFAULT
 #  define KMEMCHECK_ENABLED 0
 #endif
@@ -47,8 +49,6 @@ int kmemcheck_enabled = KMEMCHECK_ENABLED;
 
 int __init kmemcheck_init(void)
 {
-       printk(KERN_INFO "kmemcheck: Initialized\n");
-
 #ifdef CONFIG_SMP
        /*
         * Limit SMP to use a single CPU. We rely on the fact that this code
@@ -61,25 +61,18 @@ int __init kmemcheck_init(void)
        }
 #endif
 
+       if (!kmemcheck_selftest()) {
+               printk(KERN_INFO "kmemcheck: self-tests failed; disabling\n");
+               kmemcheck_enabled = 0;
+               return -EINVAL;
+       }
+
+       printk(KERN_INFO "kmemcheck: Initialized\n");
        return 0;
 }
 
 early_initcall(kmemcheck_init);
 
-#ifdef CONFIG_KMEMCHECK_DISABLED_BY_DEFAULT
-#  define KMEMCHECK_ENABLED 0
-#endif
-
-#ifdef CONFIG_KMEMCHECK_ENABLED_BY_DEFAULT
-#  define KMEMCHECK_ENABLED 1
-#endif
-
-#ifdef CONFIG_KMEMCHECK_ONESHOT_BY_DEFAULT
-#  define KMEMCHECK_ENABLED 2
-#endif
-
-int kmemcheck_enabled = KMEMCHECK_ENABLED;
-
 /*
  * We need to parse the kmemcheck= option before any memory is allocated.
  */
diff --git a/arch/x86/mm/kmemcheck/selftest.c b/arch/x86/mm/kmemcheck/selftest.c
new file mode 100644 (file)
index 0000000..036efbe
--- /dev/null
@@ -0,0 +1,69 @@
+#include <linux/kernel.h>
+
+#include "opcode.h"
+#include "selftest.h"
+
+struct selftest_opcode {
+       unsigned int expected_size;
+       const uint8_t *insn;
+       const char *desc;
+};
+
+static const struct selftest_opcode selftest_opcodes[] = {
+       /* REP MOVS */
+       {1, "\xf3\xa4",                 "rep movsb <mem8>, <mem8>"},
+       {4, "\xf3\xa5",                 "rep movsl <mem32>, <mem32>"},
+
+       /* MOVZX / MOVZXD */
+       {1, "\x66\x0f\xb6\x51\xf8",     "movzwq <mem8>, <reg16>"},
+       {1, "\x0f\xb6\x51\xf8",         "movzwq <mem8>, <reg32>"},
+
+       /* MOVSX / MOVSXD */
+       {1, "\x66\x0f\xbe\x51\xf8",     "movswq <mem8>, <reg16>"},
+       {1, "\x0f\xbe\x51\xf8",         "movswq <mem8>, <reg32>"},
+
+#ifdef CONFIG_X86_64
+       /* MOVZX / MOVZXD */
+       {1, "\x49\x0f\xb6\x51\xf8",     "movzbq <mem8>, <reg64>"},
+       {2, "\x49\x0f\xb7\x51\xf8",     "movzbq <mem16>, <reg64>"},
+
+       /* MOVSX / MOVSXD */
+       {1, "\x49\x0f\xbe\x51\xf8",     "movsbq <mem8>, <reg64>"},
+       {2, "\x49\x0f\xbf\x51\xf8",     "movsbq <mem16>, <reg64>"},
+       {4, "\x49\x63\x51\xf8",         "movslq <mem32>, <reg64>"},
+#endif
+};
+
+static bool selftest_opcode_one(const struct selftest_opcode *op)
+{
+       unsigned size;
+
+       kmemcheck_opcode_decode(op->insn, &size);
+
+       if (size == op->expected_size)
+               return true;
+
+       printk(KERN_WARNING "kmemcheck: opcode %s: expected size %d, got %d\n",
+               op->desc, op->expected_size, size);
+       return false;
+}
+
+static bool selftest_opcodes_all(void)
+{
+       bool pass = true;
+       unsigned int i;
+
+       for (i = 0; i < ARRAY_SIZE(selftest_opcodes); ++i)
+               pass = pass && selftest_opcode_one(&selftest_opcodes[i]);
+
+       return pass;
+}
+
+bool kmemcheck_selftest(void)
+{
+       bool pass = true;
+
+       pass = pass && selftest_opcodes_all();
+
+       return pass;
+}
diff --git a/arch/x86/mm/kmemcheck/selftest.h b/arch/x86/mm/kmemcheck/selftest.h
new file mode 100644 (file)
index 0000000..8fed4fe
--- /dev/null
@@ -0,0 +1,6 @@
+#ifndef ARCH_X86_MM_KMEMCHECK_SELFTEST_H
+#define ARCH_X86_MM_KMEMCHECK_SELFTEST_H
+
+bool kmemcheck_selftest(void);
+
+#endif