@@ -35,6 +35,7 @@ struct heki {
extern struct heki heki;
extern bool heki_enabled;
+extern bool heki_enforcing;
void heki_early_init(void);
void heki_late_init(void);
new file mode 100644
@@ -0,0 +1,9 @@
+CONFIG_HEKI=y
+CONFIG_HEKI_KUNIT_TEST=m
+CONFIG_HEKI_MENU=y
+CONFIG_HIGH_RES_TIMERS=y
+CONFIG_HYPERVISOR_GUEST=y
+CONFIG_KUNIT=y
+CONFIG_KVM=y
+CONFIG_KVM_GUEST=y
+CONFIG_PARAVIRT=y
@@ -28,4 +28,16 @@ config HEKI
This feature is helpful in maintaining guest virtual machine security
even after the guest kernel has been compromised.
+config HEKI_KUNIT_TEST
+ tristate "KUnit tests for Heki" if !KUNIT_ALL_TESTS
+ depends on KUNIT
+ depends on X86
+ default KUNIT_ALL_TESTS
+ help
+ Build KUnit tests for Landlock.
+
+ See the KUnit documentation in Documentation/dev-tools/kunit
+
+ If you are unsure how to answer this question, answer N.
+
endif
@@ -1,3 +1,4 @@
# SPDX-License-Identifier: GPL-2.0-only
obj-$(CONFIG_HEKI) += main.o
+obj-$(CONFIG_HEKI_KUNIT_TEST) += heki-test.o
new file mode 100644
@@ -0,0 +1,114 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Hypervisor Enforced Kernel Integrity (Heki) - Tests
+ *
+ * Copyright © 2023-2024 Microsoft Corporation
+ */
+
+#include <asm/asm.h>
+#include <asm/processor.h>
+#include <asm/special_insns.h>
+#include <kunit/test.h>
+#include <linux/heki.h>
+
+/* Returns true on error (i.e. GP fault), false otherwise. */
+static __always_inline bool set_cr4(unsigned long value)
+{
+ int err = 0;
+
+ might_sleep();
+ /* clang-format off */
+ asm volatile("1: mov %[value],%%cr4 \n"
+ "2: \n"
+ _ASM_EXTABLE_TYPE_REG(1b, 2b, EX_TYPE_ONE_REG, %[err])
+ : [value] "+r" (value), [err] "+r" (err)
+ :);
+ /* clang-format on */
+ return err;
+}
+
+/* Control register pinning tests with SMEP check. */
+static void test_cr_disable_smep(struct kunit *const test)
+{
+ bool is_vme_set;
+
+ /* SMEP should be initially enabled. */
+ KUNIT_ASSERT_TRUE(test, __read_cr4() & X86_CR4_SMEP);
+
+ /*
+ * Trying to disable SMEP, bypassing kernel self-protection by not
+ * using cr4_clear_bits(X86_CR4_SMEP), and checking GP fault.
+ */
+ KUNIT_EXPECT_TRUE(test, set_cr4(__read_cr4() & ~X86_CR4_SMEP));
+
+ /* SMEP should still be enabled. */
+ KUNIT_EXPECT_TRUE(test, __read_cr4() & X86_CR4_SMEP);
+
+ /* Re-enabling SMEP doesn't throw a GP fault. */
+ KUNIT_EXPECT_FALSE(test, set_cr4(__read_cr4() | X86_CR4_SMEP));
+ KUNIT_EXPECT_TRUE(test, __read_cr4() & X86_CR4_SMEP);
+
+ /* We are allowed to set and unset VME. */
+ is_vme_set = __read_cr4() & X86_CR4_VME;
+ KUNIT_EXPECT_FALSE(test, set_cr4(__read_cr4() | X86_CR4_VME));
+ KUNIT_EXPECT_TRUE(test, __read_cr4() & X86_CR4_VME);
+
+ KUNIT_EXPECT_FALSE(test, set_cr4(__read_cr4() & ~X86_CR4_VME));
+ KUNIT_EXPECT_FALSE(test, __read_cr4() & X86_CR4_VME);
+
+ KUNIT_EXPECT_FALSE(test, set_cr4(__read_cr4() | X86_CR4_VME));
+ KUNIT_EXPECT_TRUE(test, __read_cr4() & X86_CR4_VME);
+
+ /* SMEP and VME changes should be consistent when setting both. */
+ KUNIT_EXPECT_TRUE(test, set_cr4(__read_cr4() &
+ ~(X86_CR4_SMEP | X86_CR4_VME)));
+ KUNIT_EXPECT_TRUE(test, __read_cr4() & X86_CR4_SMEP);
+ KUNIT_EXPECT_TRUE(test, __read_cr4() & X86_CR4_VME);
+
+ /* Unset VME. */
+ KUNIT_EXPECT_FALSE(test, set_cr4(__read_cr4() & ~X86_CR4_VME));
+ KUNIT_EXPECT_FALSE(test, __read_cr4() & X86_CR4_VME);
+
+ /* SMEP and VME changes should be consistent when only setting SMEP. */
+ KUNIT_EXPECT_TRUE(test, set_cr4(__read_cr4() &
+ ~(X86_CR4_SMEP | X86_CR4_VME)));
+ KUNIT_EXPECT_TRUE(test, __read_cr4() & X86_CR4_SMEP);
+ KUNIT_EXPECT_FALSE(test, __read_cr4() & X86_CR4_VME);
+
+ /* Restores VME. */
+ if (is_vme_set)
+ KUNIT_EXPECT_FALSE(test, set_cr4(__read_cr4() | X86_CR4_VME));
+ else
+ KUNIT_EXPECT_FALSE(test, set_cr4(__read_cr4() & ~X86_CR4_VME));
+}
+
+/* clang-format off */
+static struct kunit_case test_cases_x86[] = {
+ KUNIT_CASE(test_cr_disable_smep),
+ {}
+};
+/* clang-format on */
+
+static int test_init(struct kunit *test)
+{
+#ifdef CONFIG_HEKI
+ if (heki_enforcing)
+ return 0;
+#else /* CONFIG_HEKI */
+ kunit_skip(test, "Heki is not enforced");
+#endif /* CONFIG_HEKI */
+
+ return 0;
+}
+
+static struct kunit_suite test_suite_x86 = {
+ .name = "heki_x86",
+ .init = test_init,
+ .test_cases = test_cases_x86,
+};
+
+kunit_test_suite(test_suite_x86);
+
+MODULE_IMPORT_NS(HEKI_KUNIT_TEST);
+MODULE_LICENSE("GPL");
+MODULE_DESCRIPTION("Tests for Hypervisor Enforced Kernel Integrity (Heki)");
@@ -11,6 +11,12 @@
#include "common.h"
bool heki_enabled __ro_after_init = true;
+
+#if IS_ENABLED(CONFIG_KUNIT)
+bool heki_enforcing = false;
+EXPORT_SYMBOL_NS_GPL(heki_enforcing, HEKI_KUNIT_TEST);
+#endif /* IS_ENABLED(CONFIG_KUNIT) */
+
struct heki heki;
/*
@@ -47,6 +53,10 @@ void heki_late_init(void)
return;
pr_warn("Control registers locked\n");
+
+#if IS_ENABLED(CONFIG_KUNIT)
+ heki_enforcing = true;
+#endif /* IS_ENABLED(CONFIG_KUNIT) */
}
static int __init heki_parse_config(char *str)
The new CONFIG_HEKI_KUNIT_TEST option enables to run tests in a a kernel module. The minimal required configuration is listed in the virt/heki-test/.kunitconfig file. test_cr_disable_smep checks control-register pinning by trying to disable SMEP. This test should then failed on a non-protected kernel, and only succeed with a kernel protected by Heki. This test doesn't rely on native_write_cr4() because of the cr4_pinned_mask hardening, which means that this *test* module loads a valid kernel code to arbitrary change CR4. This simulate an attack scenario where an attaker would use ROP to directly jump to the related cr4 instruction. As for any KUnit test, the kernel is tainted with TAINT_TEST when the test is executed. It is interesting to create new KUnit tests instead of extending KVM's Kselftests because Heki is design to be hypervisor-agnostic, it relies on a set of hypercalls (for KVM or others), and we also want to test kernel's configuration (actual pinned CR). However, new KVM's Kselftests would be useful to test KVM's interface with the host. When using Qemu, we need to pass the following arguments: -cpu host -enable-kvm For now, it is not possible to run these tests as built-in but we are working on that [1]. If tests are built-in anyway, they will just be skipped because Heki would not be enabled. Run Heki tests with: insmod heki-test.ko KTAP version 1 1..1 KTAP version 1 # Subtest: heki_x86 # module: heki_test 1..1 ok 1 test_cr_disable_smep ok 1 heki_x86 Link: https://lore.kernel.org/r/20240229170409.365386-2-mic@digikod.net [1] Signed-off-by: Mickaël Salaün <mic@digikod.net> Link: https://lore.kernel.org/r/20240503131910.307630-6-mic@digikod.net --- Changes since v2: * Make tests standalone (e.g. don't depends on CONFIG_HEKI). * Enable to create a test kernel module. * Don't rely on private kernel symbols. * Handle GP fault for CR-pinning test case. * Rename option to CONFIG_HEKI_KUNIT_TEST. * Add the list of required kernel options. * Move tests to virt/heki-test/ [FIXME] * Only keep CR pinning test. * Restore previous state (with SMEP enabled). * Add a Kconfig menu for Heki and update the description. * Skip tests if Heki is not protecting the running kernel. Changes since v1: * Move all tests to virt/heki/tests.c --- include/linux/heki.h | 1 + virt/heki/.kunitconfig | 9 ++++ virt/heki/Kconfig | 12 +++++ virt/heki/Makefile | 1 + virt/heki/heki-test.c | 114 +++++++++++++++++++++++++++++++++++++++++ virt/heki/main.c | 10 ++++ 6 files changed, 147 insertions(+) create mode 100644 virt/heki/.kunitconfig create mode 100644 virt/heki/heki-test.c