public inbox for kvm@vger.kernel.org
 help / color / mirror / Atom feed
* [PATCH 0/2] KVM: selftests: Add test case for readonly memslots on x86
@ 2026-02-26  7:35 Yohei Kojima
  2026-02-26  7:37 ` [PATCH 1/2] KVM: selftests: Extract memslot setup code from spawn_vm() Yohei Kojima
  2026-02-26  7:37 ` [PATCH 2/2] KVM: selftests: Add test case for readonly memslots on x86 Yohei Kojima
  0 siblings, 2 replies; 3+ messages in thread
From: Yohei Kojima @ 2026-02-26  7:35 UTC (permalink / raw)
  To: Paolo Bonzini, Shuah Khan
  Cc: Yohei Kojima, kvm, linux-kselftest, linux-kernel, Daniel Palmer,
	Tim Bird

Currently, there are no x86 test cases to verify that read and execute
on RO-memslot succeeds, and write is blocked as MMIO fault. This
behavior might break if changes are made to the MMIO path, because
x86's RO-memslot relies on the fault propagation mechanism for MMIO.

The first patch adds a function vm_userspace_mem_region_add_map(), which
is essentially the memslot setup code extracted from spawn_vm(). The
second patch adds an x86 test case for read/write/exec on RO-memslot.

This series is developed based on kvm/master, although this series adds
a test case for x86. This is because the first patch changes the
spawn_vm() function, which is used in the tests for other architectures.

base-commit: 0de4a0eec25b9171f2a2abb1a820e125e6797770

Yohei Kojima (2):
  KVM: selftests: Extract memslot setup code from spawn_vm()
  KVM: selftests: Add test case for readonly memslots on x86

 .../selftests/kvm/set_memory_region_test.c    | 147 +++++++++++++++---
 1 file changed, 129 insertions(+), 18 deletions(-)

-- 
2.43.0


^ permalink raw reply	[flat|nested] 3+ messages in thread

* [PATCH 1/2] KVM: selftests: Extract memslot setup code from spawn_vm()
  2026-02-26  7:35 [PATCH 0/2] KVM: selftests: Add test case for readonly memslots on x86 Yohei Kojima
@ 2026-02-26  7:37 ` Yohei Kojima
  2026-02-26  7:37 ` [PATCH 2/2] KVM: selftests: Add test case for readonly memslots on x86 Yohei Kojima
  1 sibling, 0 replies; 3+ messages in thread
From: Yohei Kojima @ 2026-02-26  7:37 UTC (permalink / raw)
  To: Paolo Bonzini, Shuah Khan
  Cc: Yohei Kojima, Daniel Palmer, Tim Bird, kvm, linux-kselftest,
	linux-kernel

Add vm_userspace_mem_region_add_map() function to set up memslot, and
update spawn_vm() to use it. This helps the test cases to create
additional memslots easily.

Signed-off-by: Yohei Kojima <yohei.kojima@sony.com>
---
 .../selftests/kvm/set_memory_region_test.c    | 47 ++++++++++++-------
 1 file changed, 29 insertions(+), 18 deletions(-)

diff --git a/tools/testing/selftests/kvm/set_memory_region_test.c b/tools/testing/selftests/kvm/set_memory_region_test.c
index 7fe427ff9b38..8d4fd713347c 100644
--- a/tools/testing/selftests/kvm/set_memory_region_test.c
+++ b/tools/testing/selftests/kvm/set_memory_region_test.c
@@ -107,31 +107,42 @@ static void wait_for_vcpu(void)
 	usleep(100000);
 }
 
+static void vm_userspace_mem_region_add_map(struct kvm_vm *vm,
+		uint64_t addr, uint32_t slot, size_t size, uint32_t flags)
+{
+	uint64_t *hva;
+	uint64_t gpa;
+
+	vm_userspace_mem_region_add(vm, VM_MEM_SRC_ANONYMOUS_THP,
+				    addr, slot,
+				    size / getpagesize(), flags);
+
+	/*
+	 * Allocate and map two pages so that the GPA accessed by guest_code()
+	 * stays valid across the memslot move.
+	 */
+	gpa = vm_phy_pages_alloc(vm, 2, addr, slot);
+	TEST_ASSERT(gpa == addr, "Failed vm_phy_pages_alloc\n");
+
+	virt_map(vm, addr, addr, 2);
+
+	/* Ditto for the host mapping so that both pages can be zeroed. */
+	hva = addr_gpa2hva(vm, MEM_REGION_GPA);
+	memset(hva, 0, 2 * 4096);
+}
+
 static struct kvm_vm *spawn_vm(struct kvm_vcpu **vcpu, pthread_t *vcpu_thread,
 			       void *guest_code)
 {
 	struct kvm_vm *vm;
-	uint64_t *hva;
-	uint64_t gpa;
 
 	vm = vm_create_with_one_vcpu(vcpu, guest_code);
 
-	vm_userspace_mem_region_add(vm, VM_MEM_SRC_ANONYMOUS_THP,
-				    MEM_REGION_GPA, MEM_REGION_SLOT,
-				    MEM_REGION_SIZE / getpagesize(), 0);
-
-	/*
-	 * Allocate and map two pages so that the GPA accessed by guest_code()
-	 * stays valid across the memslot move.
-	 */
-	gpa = vm_phy_pages_alloc(vm, 2, MEM_REGION_GPA, MEM_REGION_SLOT);
-	TEST_ASSERT(gpa == MEM_REGION_GPA, "Failed vm_phy_pages_alloc\n");
-
-	virt_map(vm, MEM_REGION_GPA, MEM_REGION_GPA, 2);
-
-	/* Ditto for the host mapping so that both pages can be zeroed. */
-	hva = addr_gpa2hva(vm, MEM_REGION_GPA);
-	memset(hva, 0, 2 * 4096);
+	vm_userspace_mem_region_add_map(vm,
+					MEM_REGION_GPA,
+					MEM_REGION_SLOT,
+					MEM_REGION_SIZE,
+					0);
 
 	pthread_create(vcpu_thread, NULL, vcpu_worker, *vcpu);
 
-- 
2.43.0


^ permalink raw reply related	[flat|nested] 3+ messages in thread

* [PATCH 2/2] KVM: selftests: Add test case for readonly memslots on x86
  2026-02-26  7:35 [PATCH 0/2] KVM: selftests: Add test case for readonly memslots on x86 Yohei Kojima
  2026-02-26  7:37 ` [PATCH 1/2] KVM: selftests: Extract memslot setup code from spawn_vm() Yohei Kojima
@ 2026-02-26  7:37 ` Yohei Kojima
  1 sibling, 0 replies; 3+ messages in thread
From: Yohei Kojima @ 2026-02-26  7:37 UTC (permalink / raw)
  To: Paolo Bonzini, Shuah Khan
  Cc: Yohei Kojima, Daniel Palmer, Tim Bird, kvm, linux-kselftest,
	linux-kernel

Extend set_memory_region_test to verify the following properties:

* read on RO-memslot succeeds,
* execute on RO-memslot backed by executable memory succeeds, and
* write on RO-memslot fails with mmio fault.

Signed-off-by: Yohei Kojima <yohei.kojima@sony.com>
---
 .../selftests/kvm/set_memory_region_test.c    | 100 ++++++++++++++++++
 1 file changed, 100 insertions(+)

diff --git a/tools/testing/selftests/kvm/set_memory_region_test.c b/tools/testing/selftests/kvm/set_memory_region_test.c
index 8d4fd713347c..2f21bfcbc821 100644
--- a/tools/testing/selftests/kvm/set_memory_region_test.c
+++ b/tools/testing/selftests/kvm/set_memory_region_test.c
@@ -28,7 +28,10 @@
  * Somewhat arbitrary location and slot, intended to not overlap anything.
  */
 #define MEM_REGION_GPA		0xc0000000
+#define MEM_REGION_RO_GPA	0xd0000000
+
 #define MEM_REGION_SLOT		10
+#define MEM_REGION_RO_SLOT	11
 
 static const uint64_t MMIO_VAL = 0xbeefull;
 
@@ -49,6 +52,8 @@ static inline uint64_t guest_spin_on_val(uint64_t spin_val)
 	return val;
 }
 
+static int allow_mmio_fault;
+
 static void *vcpu_worker(void *data)
 {
 	struct kvm_vcpu *vcpu = data;
@@ -76,6 +81,13 @@ static void *vcpu_worker(void *data)
 		if (run->exit_reason != KVM_EXIT_MMIO)
 			break;
 
+		if (allow_mmio_fault && run->mmio.is_write)
+			/*
+			 * in this case, skip checking mmio-related assertions
+			 * and exit status
+			 */
+			return NULL;
+
 		TEST_ASSERT(!run->mmio.is_write, "Unexpected exit mmio write");
 		TEST_ASSERT(run->mmio.len == 8,
 			    "Unexpected exit mmio size = %u", run->mmio.len);
@@ -336,6 +348,92 @@ static void test_delete_memory_region(bool disable_slot_zap_quirk)
 	kvm_vm_free(vm);
 }
 
+static void guest_code_ro_memory_region(void)
+{
+	uint64_t val;
+	unsigned char c;
+	void *instruction_addr;
+
+	GUEST_SYNC(0);
+
+	val = guest_spin_on_val(0);
+	__GUEST_ASSERT(val == 1, "Expected '1', got '%lx'", val);
+
+	/* RO memory read; should succeed if the backing memory is readable */
+	c = *(unsigned char *)MEM_REGION_RO_GPA;
+	__GUEST_ASSERT(c == 0xab, "Expected '0xab', got '0x%x'", c);
+
+	/* RO memory exec; should succeed if the backing memory is executable */
+	instruction_addr = ((unsigned char *)MEM_REGION_RO_GPA) + 8;
+	val = ((uint32_t (*)(void))instruction_addr)();
+	__GUEST_ASSERT(val == 0xbeef,
+			"Expected 0xbeef, but got '%lx'", val);
+
+	/* Spin until the mmio fault is allowed for RO-memslot write */
+	val = guest_spin_on_val(1);
+	__GUEST_ASSERT(val == 2, "Expected '2', got '%lx'", val);
+
+	/* RO memory write; should fail */
+	WRITE_ONCE(*((uint64_t *)MEM_REGION_RO_GPA), 0x12);
+	__GUEST_ASSERT(0, "RO memory write is expected to fail, but it didn't");
+}
+
+/*
+ * On x86 environment, write access to the readonly memslots are trapped as
+ * a special MMIO fault. This test verifies that write access on the readonly
+ * memslot is blocked, and read/exec access isn't.
+ */
+static void test_ro_memory_region(void)
+{
+	pthread_t vcpu_thread;
+	uint64_t *hva, *hva_ro;
+	struct kvm_vcpu *vcpu;
+	struct kvm_vm *vm;
+
+	/*
+	 * Equivalent C function (assuming SysV ABI):
+	 * uint32_t some_function(void) {
+	 * 	return 0xbeef;
+	 * }
+	 */
+	unsigned char inst_bytes[] = {
+		0x48, 0xc7, 0xc0, 0xef, 0xbe, 0x00, 0x00,	// mov %eax, $0xbeef
+		0xc3,						// ret
+	};
+
+	pr_info("Testing write on RO memslot\n");
+
+	vm = spawn_vm(&vcpu, &vcpu_thread, guest_code_ro_memory_region);
+
+	vm_userspace_mem_region_add_map(vm,
+					MEM_REGION_RO_GPA,
+					MEM_REGION_RO_SLOT,
+					MEM_REGION_SIZE,
+					KVM_MEM_READONLY);
+
+	hva = addr_gpa2hva(vm, MEM_REGION_GPA);
+	hva_ro = addr_gpa2hva(vm, MEM_REGION_RO_GPA);
+
+	memset(hva_ro, 0xcccccccc, 0x2000);
+	WRITE_ONCE(*hva_ro, 0xab);
+	memcpy(((unsigned char *)hva_ro) + 8, inst_bytes, sizeof(inst_bytes));
+
+	WRITE_ONCE(*hva, 1);
+
+	/* Wait the vcpu thread to complete read/exec on ro memory */
+	usleep(100000);
+
+	allow_mmio_fault = true;
+	WRITE_ONCE(*hva, 2);
+
+	wait_for_vcpu();
+
+	pthread_join(vcpu_thread, NULL);
+
+	kvm_vm_free(vm);
+	allow_mmio_fault = false;
+}
+
 static void test_zero_memory_regions(void)
 {
 	struct kvm_vcpu *vcpu;
@@ -629,6 +727,8 @@ int main(int argc, char *argv[])
 	 */
 	test_zero_memory_regions();
 	test_mmio_during_vectoring();
+
+	test_ro_memory_region();
 #endif
 
 	test_invalid_memory_region_flags();
-- 
2.43.0


^ permalink raw reply related	[flat|nested] 3+ messages in thread

end of thread, other threads:[~2026-02-26  7:45 UTC | newest]

Thread overview: 3+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-02-26  7:35 [PATCH 0/2] KVM: selftests: Add test case for readonly memslots on x86 Yohei Kojima
2026-02-26  7:37 ` [PATCH 1/2] KVM: selftests: Extract memslot setup code from spawn_vm() Yohei Kojima
2026-02-26  7:37 ` [PATCH 2/2] KVM: selftests: Add test case for readonly memslots on x86 Yohei Kojima

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox