mirror of https://lore.kernel.org/lkml/
 help / color / mirror / Atom feed
From: Paolo Bonzini <pbonzini@redhat.com>
To: linux-kernel@vger.kernel.org, kvm@vger.kernel.org
Cc: nsaenz@amazon.com, vkuznets@redhat.com, snambakam@linux.microsoft.com
Subject: [PATCH 07/31] KVM: selftests: test hypercall memory fault exits
Date: Fri, 18 Sep 2026 09:50:06 -0400	[thread overview]
Message-ID: <20260918135030.171564-8-pbonzini@redhat.com> (raw)
In-Reply-To: <20260918135030.171564-1-pbonzini@redhat.com>

Cover various scenarios where hypercalls are invoked with invalid or
read-only GPAs, and check that they exit to userspace.

Signed-off-by: Paolo Bonzini <pbonzini@redhat.com>
---
 tools/testing/selftests/kvm/Makefile.kvm      |   1 +
 tools/testing/selftests/kvm/x86/hcall_fault.c | 246 ++++++++++++++++++
 .../testing/selftests/kvm/x86/hyperv_evmcs.c  |   4 +
 .../selftests/kvm/x86/hyperv_svm_test.c       |   2 +
 4 files changed, 253 insertions(+)
 create mode 100644 tools/testing/selftests/kvm/x86/hcall_fault.c

diff --git a/tools/testing/selftests/kvm/Makefile.kvm b/tools/testing/selftests/kvm/Makefile.kvm
index 96bab7002d39..6e2bbf743eaa 100644
--- a/tools/testing/selftests/kvm/Makefile.kvm
+++ b/tools/testing/selftests/kvm/Makefile.kvm
@@ -79,6 +79,7 @@ TEST_GEN_PROGS_x86 += x86/evmcs_smm_controls_test
 TEST_GEN_PROGS_x86 += x86/exit_on_emulation_failure_test
 TEST_GEN_PROGS_x86 += x86/fastops_test
 TEST_GEN_PROGS_x86 += x86/fix_hypercall_test
+TEST_GEN_PROGS_x86 += x86/hcall_fault
 TEST_GEN_PROGS_x86 += x86/hwcr_msr_test
 TEST_GEN_PROGS_x86 += x86/hyperv_clock
 TEST_GEN_PROGS_x86 += x86/hyperv_cpuid
diff --git a/tools/testing/selftests/kvm/x86/hcall_fault.c b/tools/testing/selftests/kvm/x86/hcall_fault.c
new file mode 100644
index 000000000000..725c59ff750c
--- /dev/null
+++ b/tools/testing/selftests/kvm/x86/hcall_fault.c
@@ -0,0 +1,246 @@
+// SPDX-License-Identifier: GPL-2.0-only
+#include <errno.h>
+
+#include "kvm_util.h"
+#include "processor.h"
+#include "hyperv.h"
+
+#define TEST_MEM_GPA		0x100000000ull
+#define READONLY_GPA		(TEST_MEM_GPA + PAGE_SIZE)
+#define UNMAPPED_IN_GPA		(TEST_MEM_GPA + 2 * PAGE_SIZE)
+#define UNMAPPED_OUT_GPA	(TEST_MEM_GPA + 3 * PAGE_SIZE)
+
+struct hcall_test {
+	/* TEST_MEM_GPA in the guest */
+	u64 control;
+	u64 ingpa;
+	u64 outgpa;
+	u64 expected_status;
+	u8 unused[PAGE_SIZE - 32];
+
+	/* READONLY_GPA in the guest */
+	u8 readonly_data[];
+};
+
+#define HV_FLUSH_ALL_PROCESSORS                 BIT(0)
+#define HV_FLUSH_ALL_VIRTUAL_ADDRESS_SPACES     BIT(1)
+
+/* HvFlushVirtualAddressSpace, HvFlushVirtualAddressList hypercalls */
+struct hv_tlb_flush {
+        u64 address_space;
+        u64 flags;
+        u64 processor_mask;
+        u64 gva_list[];
+} __packed;
+
+static void guest_code(gpa_t hcall_page, struct hcall_test *test)
+{
+	u64 result;
+	u8 vector;
+
+	wrmsr(HV_X64_MSR_GUEST_OS_ID, HYPERV_LINUX_OS_ID);
+	wrmsr(HV_X64_MSR_HYPERCALL, hcall_page);
+
+	vector = __hyperv_hypercall(test->control, test->ingpa, test->outgpa, &result);
+	GUEST_ASSERT(!vector);
+	GUEST_ASSERT_EQ(result & 0xffff, test->expected_status);
+	GUEST_DONE();
+}
+
+static void clock_pairing_guest(gpa_t gpa)
+{
+	kvm_hypercall(KVM_HC_CLOCK_PAIRING, gpa, KVM_CLOCK_PAIRING_WALLCLOCK, 0, 0);
+	GUEST_DONE();
+}
+
+static gva_t create_test_regions(struct kvm_vm *vm)
+{
+	gva_t test_gva;
+
+	vm_userspace_mem_region_add(vm, VM_MEM_SRC_ANONYMOUS, TEST_MEM_GPA,
+				    10, 1, 0);
+	memset(addr_gpa2hva(vm, TEST_MEM_GPA), 0, PAGE_SIZE);
+
+	vm_userspace_mem_region_add(vm, VM_MEM_SRC_ANONYMOUS, READONLY_GPA,
+				    11, 1, KVM_MEM_READONLY);
+	memset(addr_gpa2hva(vm, READONLY_GPA), 0, PAGE_SIZE);
+
+	test_gva = vm_unused_gva_gap(vm, PAGE_SIZE * 2, 1 << 24);
+	for (int i = 0; i < 2; i++)
+		virt_pg_map(vm, test_gva + PAGE_SIZE * i, TEST_MEM_GPA + PAGE_SIZE * i);
+
+	return test_gva;
+}
+
+static struct kvm_vm *create_vm(struct kvm_vcpu **vcpu, struct hcall_test **test,
+				gpa_t *hcall_page_gpa)
+{
+	gva_t hcall_page, test_gva;
+	struct kvm_vm *vm;
+
+	vm = vm_create_with_one_vcpu(vcpu, guest_code);
+	vcpu_set_hv_cpuid(*vcpu);
+	vm_enable_cap(vm, KVM_CAP_HCALL_FAULT_EXIT, 1);
+
+	hcall_page = vm_alloc_page(vm);
+	memset(addr_gva2hva(vm, hcall_page), 0, PAGE_SIZE);
+	*hcall_page_gpa = addr_gva2gpa(vm, hcall_page);
+
+	test_gva = create_test_regions(vm);
+	*test = addr_gva2hva(vm, test_gva);
+
+	vcpu_args_set(*vcpu, 2, *hcall_page_gpa, test_gva);
+	return vm;
+}
+
+static void run_vm(struct kvm_vcpu *vcpu)
+{
+	struct ucall uc;
+
+	vcpu_run(vcpu);
+	TEST_ASSERT_KVM_EXIT_REASON(vcpu, KVM_EXIT_IO);
+
+	switch (get_ucall(vcpu, &uc)) {
+	case UCALL_ABORT:
+		REPORT_GUEST_ASSERT(uc);
+		break;
+	case UCALL_DONE:
+		break;
+	default:
+		TEST_FAIL("Unexpected ucall: %lu", uc.cmd);
+	}
+}
+
+static void test_unused_gpas(void)
+{
+	struct hcall_test *test;
+	struct kvm_vcpu *vcpu;
+	struct kvm_vm *vm;
+	gpa_t hcall_page;
+
+	vm = create_vm(&vcpu, &test, &hcall_page);
+	test->control = HVCALL_NOTIFY_LONG_SPIN_WAIT;
+	test->ingpa = UNMAPPED_IN_GPA;
+	test->outgpa = UNMAPPED_OUT_GPA;
+	test->expected_status = HV_STATUS_SUCCESS;
+	run_vm(vcpu);
+	kvm_vm_free(vm);
+
+	vm = create_vm(&vcpu, &test, &hcall_page);
+	test->control = 0xbeef;
+	test->ingpa = UNMAPPED_IN_GPA;
+	test->outgpa = UNMAPPED_OUT_GPA;
+	test->expected_status = HV_STATUS_INVALID_HYPERCALL_CODE;
+	run_vm(vcpu);
+	kvm_vm_free(vm);
+}
+
+static void assert_memory_fault(struct kvm_vcpu *vcpu, gpa_t gpa, u64 flags)
+{
+	int r;
+
+	r = _vcpu_run(vcpu);
+	TEST_ASSERT(r == -1 && errno == EFAULT, KVM_IOCTL_ERROR(KVM_RUN, r));
+	TEST_ASSERT_KVM_EXIT_REASON(vcpu, KVM_EXIT_MEMORY_FAULT);
+	TEST_ASSERT_EQ(vcpu->run->memory_fault.flags, flags);
+	TEST_ASSERT_EQ(vcpu->run->memory_fault.gpa, gpa & PAGE_MASK);
+	TEST_ASSERT_EQ(vcpu->run->memory_fault.size, PAGE_SIZE);
+}
+
+/* Read-only hypercalls, read-only inputs, valid/invalid argument */
+static void test_readonly_input(void)
+{
+	struct hv_tlb_flush *flush;
+	struct hcall_test *test;
+	struct kvm_vcpu *vcpu;
+	struct kvm_vm *vm;
+	gpa_t hcall_page;
+
+	vm = create_vm(&vcpu, &test, &hcall_page);
+	flush = (struct hv_tlb_flush *)addr_gpa2hva(vm, READONLY_GPA);
+	flush->flags = HV_FLUSH_ALL_VIRTUAL_ADDRESS_SPACES | HV_FLUSH_ALL_PROCESSORS;
+	flush->processor_mask = 0;
+	test->control = HVCALL_FLUSH_VIRTUAL_ADDRESS_SPACE;
+	test->ingpa = READONLY_GPA;
+	test->outgpa = UNMAPPED_OUT_GPA;
+	test->expected_status = HV_STATUS_SUCCESS;
+	run_vm(vcpu);
+	kvm_vm_free(vm);
+
+	/* fails immediately because it requires SynIC */
+	vm = create_vm(&vcpu, &test, &hcall_page);
+	test->control = HVCALL_POST_MESSAGE;
+	test->ingpa = UNMAPPED_IN_GPA;
+	test->outgpa = UNMAPPED_OUT_GPA;
+	test->expected_status = HV_STATUS_INVALID_HYPERCALL_INPUT;
+	run_vm(vcpu);
+	kvm_vm_free(vm);
+}
+
+/* Read-only hypercall, unmapped input */
+static void test_input_fault(void)
+{
+	struct hcall_test *test;
+	struct kvm_vcpu *vcpu;
+	struct kvm_vm *vm;
+	gpa_t hcall_page;
+
+	vm = create_vm(&vcpu, &test, &hcall_page);
+	test->control = HVCALL_SIGNAL_EVENT;
+	test->ingpa = UNMAPPED_IN_GPA;
+	test->outgpa = UNMAPPED_OUT_GPA;
+	assert_memory_fault(vcpu, test->ingpa, KVM_MEMORY_EXIT_FLAG_READ);
+	kvm_vm_free(vm);
+}
+
+/* Read-write hypercall, unmapped or readonly input and output */
+static void test_output_fault(bool readonly_in, bool readonly_out)
+{
+	struct hcall_test *test;
+	struct kvm_vcpu *vcpu;
+	struct kvm_vm *vm;
+	gpa_t hcall_page;
+
+	vm = create_vm(&vcpu, &test, &hcall_page);
+	test->control = HV_EXT_CALL_QUERY_CAPABILITIES;
+	test->ingpa = readonly_in ? READONLY_GPA : UNMAPPED_IN_GPA;
+	test->outgpa = readonly_out ? READONLY_GPA : UNMAPPED_OUT_GPA;
+
+	assert_memory_fault(vcpu, test->outgpa, KVM_MEMORY_EXIT_FLAG_WRITE);
+	kvm_vm_free(vm);
+}
+
+static void test_clock_pairing_fault(bool readonly)
+{
+	struct kvm_vcpu *vcpu;
+	struct kvm_vm *vm;
+	gpa_t gpa;
+
+	/* Unlike create_vm do not enable Hyper-V hypercalls. */
+	vm = vm_create_with_one_vcpu(&vcpu, clock_pairing_guest);
+	create_test_regions(vm);
+	vm_enable_cap(vm, KVM_CAP_HCALL_FAULT_EXIT, 1);
+	gpa = readonly ? READONLY_GPA : UNMAPPED_OUT_GPA;
+	vcpu_args_set(vcpu, 1, gpa);
+
+	assert_memory_fault(vcpu, gpa, KVM_MEMORY_EXIT_FLAG_WRITE);
+	kvm_vm_free(vm);
+}
+
+int main(void)
+{
+	TEST_REQUIRE(kvm_has_cap(KVM_CAP_HYPERV_CPUID));
+	TEST_REQUIRE(kvm_has_cap(KVM_CAP_HCALL_FAULT_EXIT));
+	TEST_REQUIRE(kvm_cpuid_has(kvm_get_supported_hv_cpuid(),
+				   HV_ENABLE_EXTENDED_HYPERCALLS));
+
+	test_unused_gpas();
+	test_readonly_input();
+	test_input_fault();
+	test_output_fault(false, false);
+	test_output_fault(true, false);
+	test_output_fault(false, true);
+	test_clock_pairing_fault(false);
+	test_clock_pairing_fault(true);
+	return 0;
+}
diff --git a/tools/testing/selftests/kvm/x86/hyperv_evmcs.c b/tools/testing/selftests/kvm/x86/hyperv_evmcs.c
index 29baae74ad3a..59d200ccb408 100644
--- a/tools/testing/selftests/kvm/x86/hyperv_evmcs.c
+++ b/tools/testing/selftests/kvm/x86/hyperv_evmcs.c
@@ -216,6 +216,8 @@ static struct kvm_vcpu *save_restore_vm(struct kvm_vm *vm,
 
 	/* Restore state in a new VM.  */
 	vcpu = vm_recreate_with_one_vcpu(vm);
+	if (kvm_has_cap(KVM_CAP_HCALL_FAULT_EXIT))
+		vm_enable_cap(vm, KVM_CAP_HCALL_FAULT_EXIT, 1);
 	vcpu_set_hv_cpuid(vcpu);
 	vcpu_enable_evmcs(vcpu);
 	vcpu_load_state(vcpu, state);
@@ -246,6 +248,8 @@ int main(int argc, char *argv[])
 	TEST_REQUIRE(kvm_cpu_has_ept());
 
 	vm = vm_create_with_one_vcpu(&vcpu, guest_code);
+	if (kvm_has_cap(KVM_CAP_HCALL_FAULT_EXIT))
+		vm_enable_cap(vm, KVM_CAP_HCALL_FAULT_EXIT, 1);
 	vm_enable_ept(vm);
 
 	hcall_page = vm_alloc_pages(vm, 1);
diff --git a/tools/testing/selftests/kvm/x86/hyperv_svm_test.c b/tools/testing/selftests/kvm/x86/hyperv_svm_test.c
index 18f0ad6debd8..ecad242e6623 100644
--- a/tools/testing/selftests/kvm/x86/hyperv_svm_test.c
+++ b/tools/testing/selftests/kvm/x86/hyperv_svm_test.c
@@ -162,6 +162,8 @@ int main(int argc, char *argv[])
 
 	/* Create VM */
 	vm = vm_create_with_one_vcpu(&vcpu, guest_code);
+	if (kvm_has_cap(KVM_CAP_HCALL_FAULT_EXIT))
+		vm_enable_cap(vm, KVM_CAP_HCALL_FAULT_EXIT, 1);
 	vm_enable_npt(vm);
 	vcpu_set_hv_cpuid(vcpu);
 	vcpu_alloc_svm(vm, &nested_gva);
-- 
2.52.0



  parent reply	other threads:[~2026-09-18 13:50 UTC|newest]

Thread overview: 35+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-09-18 13:49 [PATCH v3 00/28] KVM: x86: Introduce memory protection attributes Paolo Bonzini
2026-09-18 13:50 ` [PATCH 01/31] KVM: x86/hyperv: do not overwrite hc->ingpa for slow SIGNAL_EVENT hypercall Paolo Bonzini
2026-09-18 13:50 ` [PATCH 02/31] KVM: selftests: Take into account mixed memory fault flags Paolo Bonzini
2026-09-18 13:50 ` [PATCH 03/31] KVM: Define and communicate KVM_EXIT_MEMORY_FAULT RWX flags to userspace Paolo Bonzini
2026-09-18 13:50 ` [PATCH 04/31] KVM: selftests: Test address translation for Hyper-V direct L2 hypercalls Paolo Bonzini
2026-09-18 13:50 ` [PATCH 05/31] KVM: apply nGPA->GPA translation to KVM_HC_CLOCK_PAIRING Paolo Bonzini
2026-09-18 13:50 ` [PATCH 06/31] KVM: x86: Introduce memory fault on invalid hypercalls reads/writes Paolo Bonzini
2026-09-21 16:56   ` Vitaly Kuznetsov
2026-09-18 13:50 ` Paolo Bonzini [this message]
2026-09-18 13:50 ` [PATCH 08/31] KVM: x86/mmu: intersect writability from __kvm_faultin_pfn with fault->map_writable Paolo Bonzini
2026-09-18 13:50 ` [PATCH 09/31] KVM: x86/mmu: Extend map_writable to a full ACC_* mask Paolo Bonzini
2026-09-18 13:50 ` [PATCH 10/31] KVM: x86/mmu: Init memslot hugepage information for non-private_mem VMs too Paolo Bonzini
2026-09-18 13:50 ` [PATCH 11/31] KVM: pass kvm == NULL case to kvm_arch_has_private_mem Paolo Bonzini
2026-09-18 13:50 ` [PATCH 12/31] KVM: adjust for presence of more than one attribute Paolo Bonzini
2026-09-18 13:50 ` [PATCH 13/31] KVM: Introduce NR/NW/NX memory attributes Paolo Bonzini
2026-09-18 13:50 ` [PATCH 14/31] KVM: Include memory protections in result of gfn->hva conversion Paolo Bonzini
2026-09-18 13:50 ` [PATCH 15/31] KVM: Introduce kvm_fetch_guest_page() and use it for x86 Paolo Bonzini
2026-09-18 13:50 ` [PATCH 16/31] KVM: Take memory protections into account for memory read/write/fetch Paolo Bonzini
2026-09-18 13:50 ` [PATCH 17/31] KVM: Take memory protections into account for __kvm_vcpu_map Paolo Bonzini
2026-09-18 13:50 ` [PATCH 18/31] KVM: Encapsulate memattrs array into anonymous struct Paolo Bonzini
2026-09-18 13:50 ` [PATCH 19/31] KVM: loongarch: do full validity check on the gfn-to-hva cache Paolo Bonzini
2026-09-18 13:50 ` [PATCH 20/31] KVM: Introduce kvm_check_gen()/kvm_memslots_check_gen() Paolo Bonzini
2026-09-18 13:50 ` [PATCH 21/31] KVM: Introduce a generation number for memory attributes Paolo Bonzini
2026-09-18 13:50 ` [PATCH 22/31] KVM: Take memory protections into account for accesses with cached gfn->hva Paolo Bonzini
2026-09-18 13:50 ` [PATCH 23/31] KVM: pfncache: Fail to refresh if it contains memory protections Paolo Bonzini
2026-09-18 13:50 ` [PATCH 24/31] KVM: x86/mmu: Do not prefetch sptes on gfns backed by memory attributes Paolo Bonzini
2026-09-18 13:50 ` [PATCH 25/31] KVM: x86/mmu: Take memory protection attributes into account during faults Paolo Bonzini
2026-09-18 13:50 ` [PATCH 26/31] KVM: x86/mmu: Issue memory fault exit if walk failed due to memory attribute Paolo Bonzini
2026-09-18 13:50 ` [PATCH 27/31] KVM: let kvm_arch_post_set_memory_attributes drop mmu_lock Paolo Bonzini
2026-09-18 13:50 ` [PATCH 28/31] KVM: x86/mmu: Obsolete all roots if memattr contains gPTEs Paolo Bonzini
2026-09-18 13:50 ` [PATCH 29/31] KVM: x86: selftests: Introduce memory protection attributes test Paolo Bonzini
2026-09-18 13:50 ` [PATCH 30/31] KVM: x86: selftests: Introduce memory attributes PTE test Paolo Bonzini
2026-09-18 13:50 ` [PATCH 31/31] KVM: x86: selftests: Introduce memory attributes side-channel tests Paolo Bonzini
2026-09-18 15:20 ` [PATCH v3 00/28] KVM: x86: Introduce memory protection attributes Paolo Bonzini
2026-09-21 16:56 ` Vitaly Kuznetsov

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=20260918135030.171564-8-pbonzini@redhat.com \
    --to=pbonzini@redhat.com \
    --cc=kvm@vger.kernel.org \
    --cc=linux-kernel@vger.kernel.org \
    --cc=nsaenz@amazon.com \
    --cc=snambakam@linux.microsoft.com \
    --cc=vkuznets@redhat.com \
    /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 a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox

all inboxes | Powered by JetHome®