@@ -13,6 +13,7 @@
#include "alloc_page.h"
#include "isr.h"
#include "apic.h"
+#include "vmalloc.h"
/* for the nested page table*/
u64 *pte[2048];
@@ -397,12 +398,14 @@ test_wanted(const char *name, char *filters[], int filter_count)
int main(int ac, char **av)
{
+ /* Omit PT_USER_MASK to allow tested host.CR4.SMEP=1. */
+ pteval_t opt_mask = 0;
int i = 0;
ac--;
av++;
- setup_vm();
+ __setup_vm(&opt_mask);
if (!this_cpu_has(X86_FEATURE_SVM)) {
printf("SVM not availble\n");
@@ -2499,6 +2499,148 @@ static void svm_guest_state_test(void)
test_vmrun_canonicalization();
}
+static void __svm_npt_rsvd_bits_test(u64 *pxe, u64 rsvd_bits, u64 efer,
+ ulong cr4, u64 guest_efer, ulong guest_cr4)
+{
+ u64 pxe_orig = *pxe;
+ int exit_reason;
+ u64 pfec;
+
+ wrmsr(MSR_EFER, efer);
+ write_cr4(cr4);
+
+ vmcb->save.efer = guest_efer;
+ vmcb->save.cr4 = guest_cr4;
+
+ *pxe |= rsvd_bits;
+
+ exit_reason = svm_vmrun();
+
+ report(exit_reason == SVM_EXIT_NPF,
+ "Wanted #NPF on rsvd bits = 0x%lx, got exit = 0x%x", rsvd_bits, exit_reason);
+
+ if (pxe == npt_get_pdpe() || pxe == npt_get_pml4e()) {
+ /*
+ * The guest's page tables will blow up on a bad PDPE/PML4E,
+ * before starting the final walk of the guest page.
+ */
+ pfec = 0x20000000full;
+ } else {
+ /* RSVD #NPF on final walk of guest page. */
+ pfec = 0x10000000dULL;
+
+ /* PFEC.FETCH=1 if NX=1 *or* SMEP=1. */
+ if ((cr4 & X86_CR4_SMEP) || (efer & EFER_NX))
+ pfec |= 0x10;
+
+ }
+
+ report(vmcb->control.exit_info_1 == pfec,
+ "Wanted PFEC = 0x%lx, got PFEC = %lx, PxE = 0x%lx. "
+ "host.NX = %u, host.SMEP = %u, guest.NX = %u, guest.SMEP = %u",
+ pfec, vmcb->control.exit_info_1, *pxe,
+ !!(efer & EFER_NX), !!(cr4 & X86_CR4_SMEP),
+ !!(guest_efer & EFER_NX), !!(guest_cr4 & X86_CR4_SMEP));
+
+ *pxe = pxe_orig;
+}
+
+static void _svm_npt_rsvd_bits_test(u64 *pxe, u64 pxe_rsvd_bits, u64 efer,
+ ulong cr4, u64 guest_efer, ulong guest_cr4)
+{
+ u64 rsvd_bits;
+ int i;
+
+ /*
+ * Test all combinations of guest/host EFER.NX and CR4.SMEP. If host
+ * EFER.NX=0, use NX as the reserved bit, otherwise use the passed in
+ * @pxe_rsvd_bits.
+ */
+ for (i = 0; i < 16; i++) {
+ if (i & 1) {
+ rsvd_bits = pxe_rsvd_bits;
+ efer |= EFER_NX;
+ } else {
+ rsvd_bits = PT64_NX_MASK;
+ efer &= ~EFER_NX;
+ }
+ if (i & 2)
+ cr4 |= X86_CR4_SMEP;
+ else
+ cr4 &= ~X86_CR4_SMEP;
+ if (i & 4)
+ guest_efer |= EFER_NX;
+ else
+ guest_efer &= ~EFER_NX;
+ if (i & 8)
+ guest_cr4 |= X86_CR4_SMEP;
+ else
+ guest_cr4 &= ~X86_CR4_SMEP;
+
+ __svm_npt_rsvd_bits_test(pxe, rsvd_bits, efer, cr4,
+ guest_efer, guest_cr4);
+ }
+}
+
+static u64 get_random_bits(u64 hi, u64 low)
+{
+ u64 rsvd_bits;
+
+ do {
+ rsvd_bits = (rdtsc() << low) & GENMASK_ULL(hi, low);
+ } while (!rsvd_bits);
+
+ return rsvd_bits;
+}
+
+
+static void svm_npt_rsvd_bits_test(void)
+{
+ u64 saved_efer, host_efer, sg_efer, guest_efer;
+ ulong saved_cr4, host_cr4, sg_cr4, guest_cr4;
+
+ if (!npt_supported()) {
+ report_skip("NPT not supported");
+ return;
+ }
+
+ saved_efer = host_efer = rdmsr(MSR_EFER);
+ saved_cr4 = host_cr4 = read_cr4();
+ sg_efer = guest_efer = vmcb->save.efer;
+ sg_cr4 = guest_cr4 = vmcb->save.cr4;
+
+ test_set_guest(basic_guest_main);
+
+ /*
+ * 4k PTEs don't have reserved bits if MAXPHYADDR >= 52, just skip the
+ * sub-test. The NX test is still valid, but the extra bit of coverage
+ * isn't worth the extra complexity.
+ */
+ if (cpuid_maxphyaddr() >= 52)
+ goto skip_pte_test;
+
+ _svm_npt_rsvd_bits_test(npt_get_pte((u64)basic_guest_main),
+ get_random_bits(51, cpuid_maxphyaddr()),
+ host_efer, host_cr4, guest_efer, guest_cr4);
+
+skip_pte_test:
+ _svm_npt_rsvd_bits_test(npt_get_pde((u64)basic_guest_main),
+ get_random_bits(20, 13) | PT_PAGE_SIZE_MASK,
+ host_efer, host_cr4, guest_efer, guest_cr4);
+
+ _svm_npt_rsvd_bits_test(npt_get_pdpe(),
+ PT_PAGE_SIZE_MASK |
+ (this_cpu_has(X86_FEATURE_GBPAGES) ? get_random_bits(29, 13) : 0),
+ host_efer, host_cr4, guest_efer, guest_cr4);
+
+ _svm_npt_rsvd_bits_test(npt_get_pml4e(), BIT_ULL(8),
+ host_efer, host_cr4, guest_efer, guest_cr4);
+
+ wrmsr(MSR_EFER, saved_efer);
+ write_cr4(saved_cr4);
+ vmcb->save.efer = sg_efer;
+ vmcb->save.cr4 = sg_cr4;
+}
static bool volatile svm_errata_reproduced = false;
static unsigned long volatile physical = 0;
@@ -2741,6 +2883,7 @@ struct svm_test svm_tests[] = {
host_rflags_finished, host_rflags_check },
TEST(svm_cr4_osxsave_test),
TEST(svm_guest_state_test),
+ TEST(svm_npt_rsvd_bits_test),
TEST(svm_vmrun_errata_test),
TEST(svm_vmload_vmsave),
{ NULL, NULL, NULL, NULL, NULL, NULL, NULL }
Add a test to verify that KVM generates the correct #NPF and PFEC when host and guest EFER.NX and CR4.SMEP values diverge, and that KVM correctly detects reserved bits in the first place. Signed-off-by: Sean Christopherson <seanjc@google.com> --- x86/svm.c | 5 +- x86/svm_tests.c | 143 ++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 147 insertions(+), 1 deletion(-)