From: Sean Christopherson <seanjc@google.com>
To: Kevin Cheng <chengkev@google.com>
Cc: pbonzini@redhat.com, kvm@vger.kernel.org,
linux-kernel@vger.kernel.org, yosry@kernel.org
Subject: Re: [PATCH V3 4/4] KVM: selftests: Add nested page fault injection test
Date: Fri, 22 May 2026 15:33:27 -0700 [thread overview]
Message-ID: <ahDZt1fT3vh-S3Yn@google.com> (raw)
In-Reply-To: <20260313071033.4153209-5-chengkev@google.com>
On Fri, Mar 13, 2026, Kevin Cheng wrote:
> Add a test that exercises nested page fault injection during L2
> execution. L2 executes I/O string instructions (OUTSB/INSB) that access
> memory restricted in L1's nested page tables (NPT/EPT), triggering a
> nested page fault that L0 must inject to L1.
>
> The test supports both AMD SVM (NPF) and Intel VMX (EPT violation) and
> verifies that:
> - The exit reason is an NPF/EPT violation
> - The access type and permission bits are correct
> - The faulting GPA is correct
>
> Three test cases are implemented:
> - Unmap the final data page (final translation fault, OUTSB read)
> - Unmap a PT page (page walk fault, OUTSB read)
> - Write-protect the final data page (protection violation, INSB write)
> - Write-protect a PT page (protection violation on A/D update, OUTSB
> read)
>
> Signed-off-by: Kevin Cheng <chengkev@google.com>
> ---
> tools/testing/selftests/kvm/Makefile.kvm | 1 +
> .../selftests/kvm/x86/nested_npf_test.c | 374 ++++++++++++++++++
> 2 files changed, 375 insertions(+)
> create mode 100644 tools/testing/selftests/kvm/x86/nested_npf_test.c
>
> diff --git a/tools/testing/selftests/kvm/Makefile.kvm b/tools/testing/selftests/kvm/Makefile.kvm
> index 3d372d78a275..9308e6100f27 100644
> --- a/tools/testing/selftests/kvm/Makefile.kvm
> +++ b/tools/testing/selftests/kvm/Makefile.kvm
> @@ -94,6 +94,7 @@ TEST_GEN_PROGS_x86 += x86/nested_dirty_log_test
> TEST_GEN_PROGS_x86 += x86/nested_emulation_test
> TEST_GEN_PROGS_x86 += x86/nested_exceptions_test
> TEST_GEN_PROGS_x86 += x86/nested_invalid_cr3_test
> +TEST_GEN_PROGS_x86 += x86/nested_npf_test
NPF is AMD specific. Call it nested_tdp_fault_test.
> TEST_GEN_PROGS_x86 += x86/nested_set_state_test
> TEST_GEN_PROGS_x86 += x86/nested_tsc_adjust_test
> TEST_GEN_PROGS_x86 += x86/nested_tsc_scaling_test
> diff --git a/tools/testing/selftests/kvm/x86/nested_npf_test.c b/tools/testing/selftests/kvm/x86/nested_npf_test.c
> new file mode 100644
> index 000000000000..7725e5dc3a38
> --- /dev/null
> +++ b/tools/testing/selftests/kvm/x86/nested_npf_test.c
> @@ -0,0 +1,374 @@
> +// SPDX-License-Identifier: GPL-2.0-only
> +/*
> + * Copyright (C) 2025, Google, Inc.
> + */
> +
> +#include "test_util.h"
> +#include "kvm_util.h"
> +#include "processor.h"
> +#include "svm_util.h"
> +#include "vmx.h"
> +
> +#define L2_GUEST_STACK_SIZE 64
> +
> +#define EPT_VIOLATION_ACC_READ BIT(0)
> +#define EPT_VIOLATION_ACC_WRITE BIT(1)
> +#define EPT_VIOLATION_ACC_INSTR BIT(2)
> +#define EPT_VIOLATION_PROT_READ BIT(3)
> +#define EPT_VIOLATION_PROT_WRITE BIT(4)
> +#define EPT_VIOLATION_PROT_EXEC BIT(5)
> +#define EPT_VIOLATION_GVA_IS_VALID BIT(7)
> +#define EPT_VIOLATION_GVA_TRANSLATED BIT(8)
Put these in tools/testing/selftests/kvm/include/x86/processor.h.
> +enum test_type {
> + TEST_FINAL_PAGE_UNMAPPED, /* Final data page not present */
> + TEST_PT_PAGE_UNMAPPED, /* Page table page not present */
> + TEST_FINAL_PAGE_WRITE_PROTECTED, /* Final data page read-only */
> + TEST_PT_PAGE_WRITE_PROTECTED, /* Page table page read-only */
> +};
> +
> +static vm_vaddr_t l2_test_page;
> +static void (*l2_entry)(void);
> +
> +#define TEST_IO_PORT 0x80
> +#define TEST1_VADDR 0x8000000ULL
> +#define TEST2_VADDR 0x10000000ULL
> +#define TEST3_VADDR 0x18000000ULL
> +#define TEST4_VADDR 0x20000000ULL
> +
> +/*
> + * L2 executes OUTS reading from l2_test_page, triggering a nested page
> + * fault on the read access.
> + */
> +static void l2_guest_code_outs(void)
> +{
> + asm volatile("outsb" ::"S"(l2_test_page), "d"(TEST_IO_PORT) : "memory");
> + GUEST_FAIL("L2 should not reach here");
> +}
> +
> +/*
> + * L2 executes INS writing to l2_test_page, triggering a nested page
> + * fault on the write access.
> + */
> +static void l2_guest_code_ins(void)
> +{
> + asm volatile("insb" ::"D"(l2_test_page), "d"(TEST_IO_PORT) : "memory");
> + GUEST_FAIL("L2 should not reach here");
> +}
> +
> +static void l1_vmx_code(struct vmx_pages *vmx, uint64_t expected_fault_gpa,
> + uint64_t test_type)
> +{
> + unsigned long l2_guest_stack[L2_GUEST_STACK_SIZE];
> + uint64_t exit_qual;
> +
> + GUEST_ASSERT(vmx->vmcs_gpa);
> + GUEST_ASSERT(prepare_for_vmx_operation(vmx));
> + GUEST_ASSERT(load_vmcs(vmx));
> +
> + prepare_vmcs(vmx, l2_entry, &l2_guest_stack[L2_GUEST_STACK_SIZE]);
> +
> + GUEST_ASSERT(!vmlaunch());
> +
> + /* Verify we got an EPT violation exit */
> + __GUEST_ASSERT(vmreadz(VM_EXIT_REASON) == EXIT_REASON_EPT_VIOLATION,
> + "Expected EPT violation (0x%x), got 0x%lx",
> + EXIT_REASON_EPT_VIOLATION,
> + vmreadz(VM_EXIT_REASON));
> +
> + exit_qual = vmreadz(EXIT_QUALIFICATION);
> +
> + switch (test_type) {
> + case TEST_FINAL_PAGE_UNMAPPED:
> + /* Read access, final translation, page not present */
These comments aren't super helpful, because for the most part the information
is redundant with respect to "test_type. E.g. it's pretty darn easy to see this
is a !PRESENT fault on the final page.
And what _is_ notable is that TDP page walks are *always* read+write, and that
NPT walks are *always* USER (see below for why this test doesn't get false failures).
> + __GUEST_ASSERT(exit_qual & EPT_VIOLATION_ACC_READ,
> + "Expected ACC_READ set, exit_qual 0x%lx",
> + exit_qual);
> + __GUEST_ASSERT(exit_qual & EPT_VIOLATION_GVA_IS_VALID,
> + "Expected GVA_IS_VALID set, exit_qual 0x%lx",
> + exit_qual);
> + __GUEST_ASSERT(exit_qual & EPT_VIOLATION_GVA_TRANSLATED,
> + "Expected GVA_TRANSLATED set, exit_qual 0x%lx",
> + exit_qual);
I appreciate trying to make life easier for debuggers, but this goes a bit too
far. Checking only select bits also reduces coverage, e.g. KVM could generate
completely bogus information, but this test wouldn't detect it.
IMO, this is much more readable, and just as easy to debug:
/*
* Note, EPT page table accesses are always read+write, e.g. so that
* the CPU can do A/D updates at-will.
*/
switch (test_type) {
case TEST_FINAL_PAGE_UNMAPPED:
GUEST_ASSERT_EXIT_QUAL(exit_qual, EPT_VIOLATION_ACC_READ |
EPT_VIOLATION_GVA_IS_VALID |
EPT_VIOLATION_GVA_TRANSLATED);
break;
case TEST_PT_PAGE_UNMAPPED:
GUEST_ASSERT_EXIT_QUAL(exit_qual, EPT_VIOLATION_ACC_READ |
EPT_VIOLATION_ACC_WRITE |
EPT_VIOLATION_GVA_IS_VALID);
break;
case TEST_FINAL_PAGE_WRITE_PROTECTED:
GUEST_ASSERT_EXIT_QUAL(exit_qual, EPT_VIOLATION_ACC_WRITE |
EPT_VIOLATION_PROT_READ |
EPT_VIOLATION_PROT_EXEC |
EPT_VIOLATION_GVA_IS_VALID |
EPT_VIOLATION_GVA_TRANSLATED);
break;
case TEST_PT_PAGE_WRITE_PROTECTED:
GUEST_ASSERT_EXIT_QUAL(exit_qual, EPT_VIOLATION_ACC_READ |
EPT_VIOLATION_ACC_WRITE |
EPT_VIOLATION_PROT_READ |
EPT_VIOLATION_PROT_EXEC |
EPT_VIOLATION_GVA_IS_VALID);
break;
}
and then for NPT:
/*
* Note, without GMET enabled, NPT walks are always user accesses. And
* like EPT, page table accesses are always read+write.
*/
switch (test_type) {
case TEST_FINAL_PAGE_UNMAPPED:
GUEST_ASSERT_NPF_EC(exit_info_1, PFERR_USER_MASK |
PFERR_GUEST_FINAL_MASK);
break;
case TEST_PT_PAGE_UNMAPPED:
GUEST_ASSERT_NPF_EC(exit_info_1, PFERR_WRITE_MASK |
PFERR_USER_MASK |
PFERR_GUEST_PAGE_MASK);
break;
case TEST_FINAL_PAGE_WRITE_PROTECTED:
GUEST_ASSERT_NPF_EC(exit_info_1, PFERR_PRESENT_MASK |
PFERR_WRITE_MASK |
PFERR_USER_MASK |
PFERR_GUEST_FINAL_MASK);
break;
case TEST_PT_PAGE_WRITE_PROTECTED:
GUEST_ASSERT_NPF_EC(exit_info_1, PFERR_PRESENT_MASK |
PFERR_WRITE_MASK |
PFERR_USER_MASK |
PFERR_GUEST_PAGE_MASK);
break;
}
next prev parent reply other threads:[~2026-05-22 22:33 UTC|newest]
Thread overview: 9+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-03-13 7:10 [PATCH V3 0/4] KVM: X86: Correctly populate nested page fault injection error information Kevin Cheng
2026-03-13 7:10 ` [PATCH V3 1/4] KVM: x86: Widen x86_exception's error_code to 64 bits Kevin Cheng
2026-03-13 7:10 ` [PATCH V3 2/4] KVM: SVM: Fix nested NPF injection to set PFERR_GUEST_{PAGE,FINAL}_MASK Kevin Cheng
2026-05-22 22:04 ` Sean Christopherson
2026-03-13 7:10 ` [PATCH V3 3/4] KVM: VMX: Fix nested EPT violation injection of GVA_IS_VALID/GVA_TRANSLATED bits Kevin Cheng
2026-05-22 22:07 ` Sean Christopherson
2026-03-13 7:10 ` [PATCH V3 4/4] KVM: selftests: Add nested page fault injection test Kevin Cheng
2026-05-22 22:33 ` Sean Christopherson [this message]
2026-05-22 22:34 ` [PATCH V3 0/4] KVM: X86: Correctly populate nested page fault injection error information Sean Christopherson
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=ahDZt1fT3vh-S3Yn@google.com \
--to=seanjc@google.com \
--cc=chengkev@google.com \
--cc=kvm@vger.kernel.org \
--cc=linux-kernel@vger.kernel.org \
--cc=pbonzini@redhat.com \
--cc=yosry@kernel.org \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.