@@ -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,
};
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(-)