From: Mathias Krause <minipli@xxxxxxxxxxxxxx> 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@xxxxxxxxxxxxxx> Signed-off-by: Sean Christopherson <seanjc@xxxxxxxxxx> --- x86/access.c | 60 ++++++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 58 insertions(+), 2 deletions(-) diff --git a/x86/access.c b/x86/access.c index 203353a3..2d3d7c7b 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,55 @@ 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 +1205,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, }; -- 2.40.0.348.gf938b09366-goog