diff mbox series

[kvm-unit-tests,v3,2/4] x86/access: CR0.WP toggling write to r/o data test

Message ID 20230403105618.41118-3-minipli@grsecurity.net (mailing list archive)
State New, archived
Headers show
Series Tests for CR0.WP=0/1 r/o write access | expand

Commit Message

Mathias Krause April 3, 2023, 10:56 a.m. UTC
KUT has tests that verify a supervisor write access to an r/o page is
successful when CR0.WP=0, but lacks a test that explicitly verifies that
the same access faults after setting CR0.WP=1 without flushing any
associated TLB entries, either explicitly (INVLPG) or implicitly (write
to CR3). Add such a test.

Signed-off-by: Mathias Krause <minipli@grsecurity.net>
---
For v3 I moved the guts to a helper but omitted the "as expected" part
from the printf() as it not only keeps the generated output below 80
chars but also is kinda superfluous when it triggers.

Here's an example failure run (with the full series applied):

: run
: ....................................................................................................................................................................................................................................................................................................
: test pte.a pte.p pde.a pde.p write fep: FAIL: unexpected fault
: Dump mapping: address: 0xffff923042007000
: ------L4 I292: 2100027
: ------L3 I193: 2101027
: ------L2 I16: 2102021
: ------L1 I7: 2000061
: do_cr0_wp_access: emulated supervisor write with CR0.WP=0 did not SUCCEED
: 
: test pte.a pte.p pde.a pde.p write cr0.wp fep: FAIL: unexpected access
: Dump mapping: address: 0xffff923042007000
: ------L4 I292: 2100027
: ------L3 I193: 2101027
: ------L2 I16: 2102021
: ------L1 I7: 2000061
: do_cr0_wp_access: emulated supervisor write with CR0.WP=1 did not FAULT
: 
: 19169286 tests, 1 failures
: FAIL access

As can be seen, ac_test_check() already prints the failure reason, no
need to mention it again.

 x86/access.c | 61 ++++++++++++++++++++++++++++++++++++++++++++++++++--
 1 file changed, 59 insertions(+), 2 deletions(-)
diff mbox series

Patch

diff --git a/x86/access.c b/x86/access.c
index 203353a3f74f..a01278451b96 100644
--- a/x86/access.c
+++ b/x86/access.c
@@ -575,9 +575,10 @@  fault:
 		at->expected_error &= ~PFERR_FETCH_MASK;
 }
 
-static void ac_set_expected_status(ac_test_t *at)
+static void __ac_set_expected_status(ac_test_t *at, bool flush)
 {
-	invlpg(at->virt);
+	if (flush)
+		invlpg(at->virt);
 
 	if (at->ptep)
 		at->expected_pte = *at->ptep;
@@ -599,6 +600,11 @@  static void ac_set_expected_status(ac_test_t *at)
 	ac_emulate_access(at, at->flags);
 }
 
+static void ac_set_expected_status(ac_test_t *at)
+{
+	__ac_set_expected_status(at, true);
+}
+
 static pt_element_t ac_get_pt(ac_test_t *at, int i, pt_element_t *ptep)
 {
 	pt_element_t pte;
@@ -1061,6 +1067,56 @@  err:
 	return 0;
 }
 
+#define TOGGLE_CR0_WP_TEST_BASE_FLAGS \
+	(AC_PDE_PRESENT_MASK | AC_PDE_ACCESSED_MASK | \
+	 AC_PTE_PRESENT_MASK | AC_PTE_ACCESSED_MASK | \
+	 AC_ACCESS_WRITE_MASK)
+
+static int do_cr0_wp_access(ac_test_t *at, int flags)
+{
+	const bool cr0_wp = !!(flags & AC_CPU_CR0_WP_MASK);
+
+	at->flags = TOGGLE_CR0_WP_TEST_BASE_FLAGS | flags;
+	__ac_set_expected_status(at, false);
+
+	/*
+	 * Under VMX the guest might own the CR0.WP bit, requiring KVM to
+	 * manually keep track of it where needed, e.g. in the guest page
+	 * table walker.
+	 *
+	 * Load CR0.WP with the inverse value of what will be used during
+	 * the access test and toggle EFER.NX to coerce KVM into rebuilding
+	 * the current MMU context based on the soon-to-be-stale CR0.WP.
+	 */
+	set_cr0_wp(!cr0_wp);
+	set_efer_nx(1);
+	set_efer_nx(0);
+
+	if (!ac_test_do_access(at)) {
+		printf("%s: supervisor write with CR0.WP=%d did not %s\n",
+		       __FUNCTION__, cr0_wp, cr0_wp ? "FAULT" : "SUCCEED");
+
+		return 1;
+	}
+
+	return 0;
+}
+
+static int check_toggle_cr0_wp(ac_pt_env_t *pt_env)
+{
+	ac_test_t at;
+	int err = 0;
+
+	ac_test_init(&at, 0xffff923042007000ul, pt_env);
+	at.flags = TOGGLE_CR0_WP_TEST_BASE_FLAGS;
+	ac_test_setup_ptes(&at);
+
+	err += do_cr0_wp_access(&at, 0);
+	err += do_cr0_wp_access(&at, AC_CPU_CR0_WP_MASK);
+
+	return err == 0;
+}
+
 static int check_effective_sp_permissions(ac_pt_env_t *pt_env)
 {
 	unsigned long ptr1 = 0xffff923480000000;
@@ -1150,6 +1206,7 @@  const ac_test_fn ac_test_cases[] =
 	check_pfec_on_prefetch_pte,
 	check_large_pte_dirty_for_nowp,
 	check_smep_andnot_wp,
+	check_toggle_cr0_wp,
 	check_effective_sp_permissions,
 };