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

Mathias Krause
2023-03-31T13:57:33Z

We already have tests that verify a write access to an r/o page is
successful when CR0.WP=0, but we lack a test that explicitly verifies
that the same access will fail after we set 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>
---
x86/access.c | 56 ++++++++++++++++++++++++++++++++++++++++++++++++++--
1 file changed, 54 insertions(+), 2 deletions(-)

diff --git a/x86/access.c b/x86/access.c
index 203353a3f74f..d1ec99b4fa73 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,51 @@ err:
return 0;
}

+static int check_write_cr0wp(ac_pt_env_t *pt_env)
+{
+ ac_test_t at;
+ int err = 0;
+
+ ac_test_init(&at, 0xffff923042007000ul, pt_env);
+ at.flags = AC_PDE_PRESENT_MASK | AC_PTE_PRESENT_MASK |
+ AC_PDE_ACCESSED_MASK | AC_PTE_ACCESSED_MASK |
+ AC_ACCESS_WRITE_MASK;
+ ac_test_setup_ptes(&at);
+
+ /*
+ * Under VMX the guest might own the CR0.WP bit, requiring KVM to
+ * manually keep track of its state where needed, e.g. in the guest
+ * page table walker.
+ *
+ * We load CR0.WP with the inverse value of what would be used during
+ * the access test and toggle EFER.NX to flush and rebuild the current
+ * MMU context based on that value.
+ */
+
+ set_cr0_wp(1);
+ set_efer_nx(1);
+ set_efer_nx(0);
+
+ if (!ac_test_do_access(&at)) {
+ printf("%s: CR0.WP=0 r/o write fail\n", __FUNCTION__);
+ err++;
+ }
+
+ at.flags |= AC_CPU_CR0_WP_MASK;
+ __ac_set_expected_status(&at, false);
+
+ set_cr0_wp(0);
+ set_efer_nx(1);
+ set_efer_nx(0);
+
+ if (!ac_test_do_access(&at)) {
+ printf("%s: CR0.WP=1 r/o write deny fail\n", __FUNCTION__);
+ err++;
+ }
+
+ return err == 0;
+}
+
static int check_effective_sp_permissions(ac_pt_env_t *pt_env)
{
unsigned long ptr1 = 0xffff923480000000;
@@ -1150,6 +1201,7 @@ const ac_test_fn ac_test_cases[] =
check_pfec_on_prefetch_pte,
check_large_pte_dirty_for_nowp,
check_smep_andnot_wp,
+ check_write_cr0wp,
check_effective_sp_permissions,
};

--
2.39.2