lists.openwall.net   lists  /  announce  owl-users  owl-dev  john-users  john-dev  passwdqc-users  yescrypt  popa3d-users  /  oss-security  kernel-hardening  musl  sabotage  tlsify  passwords  /  crypt-dev  xvendor  /  Bugtraq  Full-Disclosure  linux-kernel  linux-netdev  linux-ext4  linux-hardening  linux-cve-announce  PHC 
Open Source and information security mailing list archives
 
Hash Suite for Android: free password hash cracker in your pocket
[<prev] [next>] [<thread-prev] [thread-next>] [day] [month] [year] [list]
Date: Fri,  3 May 2024 15:19:10 +0200
From: Mickaël Salaün <mic@...ikod.net>
To: Borislav Petkov <bp@...en8.de>,
	Dave Hansen <dave.hansen@...ux.intel.com>,
	"H . Peter Anvin" <hpa@...or.com>,
	Ingo Molnar <mingo@...hat.com>,
	Kees Cook <keescook@...omium.org>,
	Paolo Bonzini <pbonzini@...hat.com>,
	Sean Christopherson <seanjc@...gle.com>,
	Thomas Gleixner <tglx@...utronix.de>,
	Vitaly Kuznetsov <vkuznets@...hat.com>,
	Wanpeng Li <wanpengli@...cent.com>
Cc: Mickaël Salaün <mic@...ikod.net>,
	"Edgecombe, Rick P" <rick.p.edgecombe@...el.com>,
	Alexander Graf <graf@...zon.com>,
	Angelina Vu <angelinavu@...ux.microsoft.com>,
	Anna Trikalinou <atrikalinou@...rosoft.com>,
	Chao Peng <chao.p.peng@...ux.intel.com>,
	Forrest Yuan Yu <yuanyu@...gle.com>,
	James Gowans <jgowans@...zon.com>,
	James Morris <jamorris@...ux.microsoft.com>,
	John Andersen <john.s.andersen@...el.com>,
	"Madhavan T . Venkataraman" <madvenka@...ux.microsoft.com>,
	Marian Rotariu <marian.c.rotariu@...il.com>,
	Mihai Donțu <mdontu@...defender.com>,
	Nicușor Cîțu <nicu.citu@...oud.com>,
	Thara Gopinath <tgopinath@...rosoft.com>,
	Trilok Soni <quic_tsoni@...cinc.com>,
	Wei Liu <wei.liu@...nel.org>,
	Will Deacon <will@...nel.org>,
	Yu Zhang <yu.c.zhang@...ux.intel.com>,
	Ștefan Șicleru <ssicleru@...defender.com>,
	dev@...ts.cloudhypervisor.org,
	kvm@...r.kernel.org,
	linux-hardening@...r.kernel.org,
	linux-hyperv@...r.kernel.org,
	linux-kernel@...r.kernel.org,
	linux-security-module@...r.kernel.org,
	qemu-devel@...gnu.org,
	virtualization@...ts.linux-foundation.org,
	x86@...nel.org,
	xen-devel@...ts.xenproject.org
Subject: [RFC PATCH v3 5/5] virt: Add Heki KUnit tests

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@...ikod.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

diff --git a/include/linux/heki.h b/include/linux/heki.h
index 96ccb17657e5..3294c4d583e5 100644
--- a/include/linux/heki.h
+++ b/include/linux/heki.h
@@ -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);
diff --git a/virt/heki/.kunitconfig b/virt/heki/.kunitconfig
new file mode 100644
index 000000000000..ad4454800579
--- /dev/null
+++ b/virt/heki/.kunitconfig
@@ -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
diff --git a/virt/heki/Kconfig b/virt/heki/Kconfig
index 0c764e342f48..18895a81a9af 100644
--- a/virt/heki/Kconfig
+++ b/virt/heki/Kconfig
@@ -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
diff --git a/virt/heki/Makefile b/virt/heki/Makefile
index 8b10e73a154b..7133545eb5ae 100644
--- a/virt/heki/Makefile
+++ b/virt/heki/Makefile
@@ -1,3 +1,4 @@
 # SPDX-License-Identifier: GPL-2.0-only
 
 obj-$(CONFIG_HEKI) += main.o
+obj-$(CONFIG_HEKI_KUNIT_TEST) += heki-test.o
diff --git a/virt/heki/heki-test.c b/virt/heki/heki-test.c
new file mode 100644
index 000000000000..b4e11c21ac5d
--- /dev/null
+++ b/virt/heki/heki-test.c
@@ -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)");
diff --git a/virt/heki/main.c b/virt/heki/main.c
index ef0530a03e09..dcc89befaf66 100644
--- a/virt/heki/main.c
+++ b/virt/heki/main.c
@@ -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)
-- 
2.45.0


Powered by blists - more mailing lists

Powered by Openwall GNU/*/Linux Powered by OpenVZ