xref: /linux/tools/testing/selftests/kvm/x86/nested_tdp_fault_test.c (revision 2bee2e6c983baa3605765621f26173ff0fa40365)
1 // SPDX-License-Identifier: GPL-2.0-only
2 /*
3  * Copyright (C) 2025, Google, Inc.
4  */
5 
6 #include "test_util.h"
7 #include "kvm_util.h"
8 #include "processor.h"
9 #include "svm_util.h"
10 #include "vmx.h"
11 
12 enum test_type {
13 	TEST_FINAL_PAGE_UNMAPPED,	    /* Final data page not present */
14 	TEST_PT_PAGE_UNMAPPED,		    /* Page table page not present */
15 	TEST_FINAL_PAGE_WRITE_PROTECTED,    /* Final data page read-only */
16 	TEST_PT_PAGE_WRITE_PROTECTED,	    /* Page table page read-only */
17 };
18 
19 static gva_t l2_test_page;
20 static void (*l2_entry)(void);
21 
22 #define TEST_IO_PORT 0x80
23 #define TEST1_VADDR 0x8000000ULL
24 #define TEST2_VADDR 0x10000000ULL
25 #define TEST3_VADDR 0x18000000ULL
26 #define TEST4_VADDR 0x20000000ULL
27 
28 /*
29  * L2 executes OUTS reading from l2_test_page, triggering a nested page
30  * fault on the read access.
31  */
32 static void l2_guest_code_outs(void)
33 {
34 	asm volatile("outsb" ::"S"(l2_test_page), "d"(TEST_IO_PORT) : "memory");
35 	GUEST_FAIL("L2 should not reach here");
36 }
37 
38 /*
39  * L2 executes INS writing to l2_test_page, triggering a nested page
40  * fault on the write access.
41  */
42 static void l2_guest_code_ins(void)
43 {
44 	asm volatile("insb" ::"D"(l2_test_page), "d"(TEST_IO_PORT) : "memory");
45 	GUEST_FAIL("L2 should not reach here");
46 }
47 
48 #define GUEST_ASSERT_EXIT_QUAL(ac_eq, ex_eq)		\
49 	__GUEST_ASSERT((ac_eq) == (ex_eq),		\
50 		       "Wanted EXIT_QUAL '0x%lx', got '0x%lx'", ex_eq, ac_eq)
51 
52 static void l1_vmx_code(struct vmx_pages *vmx, u64 expected_fault_gpa,
53 			u64 test_type)
54 {
55 	u64 exit_qual;
56 
57 	GUEST_ASSERT(vmx->vmcs_gpa);
58 	GUEST_ASSERT(prepare_for_vmx_operation(vmx));
59 	GUEST_ASSERT(load_vmcs(vmx));
60 
61 	prepare_vmcs(vmx, l2_entry);
62 
63 	GUEST_ASSERT(!vmlaunch());
64 
65 	/* Verify we got an EPT violation exit */
66 	__GUEST_ASSERT(vmreadz(VM_EXIT_REASON) == EXIT_REASON_EPT_VIOLATION,
67 		       "Expected EPT violation (0x%x), got 0x%lx",
68 		       EXIT_REASON_EPT_VIOLATION,
69 		       vmreadz(VM_EXIT_REASON));
70 
71 	__GUEST_ASSERT(vmreadz(GUEST_PHYSICAL_ADDRESS) == expected_fault_gpa,
72 		       "Expected guest_physical_address = 0x%lx, got 0x%lx",
73 		       expected_fault_gpa,
74 		       vmreadz(GUEST_PHYSICAL_ADDRESS));
75 
76 	exit_qual = vmreadz(EXIT_QUALIFICATION);
77 
78 	/*
79 	 * Note, EPT page table accesses are always read+write, e.g. so that
80 	 * the CPU can do A/D updates at-will.
81 	 */
82 	switch (test_type) {
83 	case TEST_FINAL_PAGE_UNMAPPED:
84 		GUEST_ASSERT_EXIT_QUAL(exit_qual, EPT_VIOLATION_ACC_READ |
85 						  EPT_VIOLATION_GVA_IS_VALID |
86 						  EPT_VIOLATION_GVA_TRANSLATED);
87 		break;
88 	case TEST_PT_PAGE_UNMAPPED:
89 		GUEST_ASSERT_EXIT_QUAL(exit_qual, EPT_VIOLATION_ACC_READ |
90 						  EPT_VIOLATION_ACC_WRITE |
91 						  EPT_VIOLATION_GVA_IS_VALID);
92 		break;
93 	case TEST_FINAL_PAGE_WRITE_PROTECTED:
94 		GUEST_ASSERT_EXIT_QUAL(exit_qual, EPT_VIOLATION_ACC_WRITE |
95 						  EPT_VIOLATION_PROT_READ |
96 						  EPT_VIOLATION_PROT_EXEC |
97 						  EPT_VIOLATION_GVA_IS_VALID |
98 						  EPT_VIOLATION_GVA_TRANSLATED);
99 		break;
100 	case TEST_PT_PAGE_WRITE_PROTECTED:
101 		GUEST_ASSERT_EXIT_QUAL(exit_qual, EPT_VIOLATION_ACC_READ |
102 						  EPT_VIOLATION_ACC_WRITE |
103 						  EPT_VIOLATION_PROT_READ |
104 						  EPT_VIOLATION_PROT_EXEC |
105 						  EPT_VIOLATION_GVA_IS_VALID);
106 		break;
107 	}
108 
109 	GUEST_DONE();
110 }
111 
112 #define GUEST_ASSERT_NPF_EC(ac_ec, ex_ec)		\
113 	__GUEST_ASSERT((ac_ec) == (ex_ec),		\
114 		       "Wanted NPF error code '0x%lx', got '0x%lx'", (u64)(ex_ec), ac_ec)
115 
116 
117 static void l1_svm_code(struct svm_test_data *svm, u64 expected_fault_gpa,
118 			 u64 test_type)
119 {
120 	struct vmcb *vmcb = svm->vmcb;
121 	u64 exit_info_1;
122 
123 	generic_svm_setup(svm, l2_entry);
124 
125 	run_guest(vmcb, svm->vmcb_gpa);
126 
127 	/* Verify we got an NPF exit */
128 	__GUEST_ASSERT(vmcb->control.exit_code == SVM_EXIT_NPF,
129 		       "Expected NPF exit (0x%x), got 0x%lx", SVM_EXIT_NPF,
130 		       vmcb->control.exit_code);
131 
132 	__GUEST_ASSERT(vmcb->control.exit_info_2 == expected_fault_gpa,
133 		       "Expected exit_info_2 = 0x%lx, got 0x%lx",
134 		       expected_fault_gpa,
135 		       vmcb->control.exit_info_2);
136 
137 	exit_info_1 = vmcb->control.exit_info_1;
138 
139 	/*
140 	 * Note, without GMET enabled, NPT walks are always user accesses.  And
141 	 * like EPT, page table accesses are always read+write.
142 	 */
143 	switch (test_type) {
144 	case TEST_FINAL_PAGE_UNMAPPED:
145 		GUEST_ASSERT_NPF_EC(exit_info_1, PFERR_USER_MASK |
146 						 PFERR_GUEST_FINAL_MASK);
147 		break;
148 	case TEST_PT_PAGE_UNMAPPED:
149 		GUEST_ASSERT_NPF_EC(exit_info_1, PFERR_WRITE_MASK |
150 						 PFERR_USER_MASK |
151 						 PFERR_GUEST_PAGE_MASK);
152 		break;
153 	case TEST_FINAL_PAGE_WRITE_PROTECTED:
154 		GUEST_ASSERT_NPF_EC(exit_info_1, PFERR_PRESENT_MASK |
155 						 PFERR_WRITE_MASK |
156 						 PFERR_USER_MASK |
157 						 PFERR_GUEST_FINAL_MASK);
158 		break;
159 	case TEST_PT_PAGE_WRITE_PROTECTED:
160 		GUEST_ASSERT_NPF_EC(exit_info_1, PFERR_PRESENT_MASK |
161 						 PFERR_WRITE_MASK |
162 						 PFERR_USER_MASK |
163 						 PFERR_GUEST_PAGE_MASK);
164 		break;
165 	}
166 
167 	GUEST_DONE();
168 }
169 
170 static void l1_guest_code(void *data, u64 expected_fault_gpa,
171 			  u64 test_type)
172 {
173 	if (this_cpu_has(X86_FEATURE_VMX))
174 		l1_vmx_code(data, expected_fault_gpa, test_type);
175 	else
176 		l1_svm_code(data, expected_fault_gpa, test_type);
177 }
178 
179 /* Returns the GPA of the PT page that maps @vaddr. */
180 static u64 get_pt_gpa_for_vaddr(struct kvm_vm *vm, u64 vaddr)
181 {
182 	u64 *pte;
183 
184 	pte = vm_get_pte(vm, vaddr);
185 	TEST_ASSERT(pte && (*pte & 0x1), "PTE not present for vaddr 0x%lx",
186 		    (unsigned long)vaddr);
187 
188 	return addr_hva2gpa(vm, (void *)((u64)pte & ~0xFFFULL));
189 }
190 
191 static void run_test(enum test_type type)
192 {
193 	gpa_t expected_fault_gpa;
194 	gva_t nested_gva;
195 
196 	struct kvm_vcpu *vcpu;
197 	struct kvm_vm *vm;
198 	struct ucall uc;
199 
200 	vm = vm_create_with_one_vcpu(&vcpu, l1_guest_code);
201 	vm_enable_tdp(vm);
202 
203 	if (kvm_cpu_has(X86_FEATURE_VMX))
204 		vcpu_alloc_vmx(vm, &nested_gva);
205 	else
206 		vcpu_alloc_svm(vm, &nested_gva);
207 
208 	switch (type) {
209 	case TEST_FINAL_PAGE_UNMAPPED:
210 		/*
211 		 * Unmap the final data page from NPT/EPT. The guest page
212 		 * table walk succeeds, but the final GPA->HPA translation
213 		 * fails. L2 reads from the page via OUTS.
214 		 */
215 		l2_entry = l2_guest_code_outs;
216 		l2_test_page = vm_alloc(vm, vm->page_size, TEST1_VADDR);
217 		expected_fault_gpa = addr_gva2gpa(vm, l2_test_page);
218 		break;
219 	case TEST_PT_PAGE_UNMAPPED:
220 		/*
221 		 * Unmap a page table page from NPT/EPT. The hardware page
222 		 * table walk fails when translating the PT page's GPA
223 		 * through NPT/EPT. L2 reads from the page via OUTS.
224 		 */
225 		l2_entry = l2_guest_code_outs;
226 		l2_test_page = vm_alloc(vm, vm->page_size, TEST2_VADDR);
227 		expected_fault_gpa = get_pt_gpa_for_vaddr(vm, l2_test_page);
228 		break;
229 	case TEST_FINAL_PAGE_WRITE_PROTECTED:
230 		/*
231 		 * Write-protect the final data page in NPT/EPT.  The page
232 		 * is present and readable, but not writable.  L2 writes to
233 		 * the page via INS, triggering a protection violation.
234 		 */
235 		l2_entry = l2_guest_code_ins;
236 		l2_test_page = vm_alloc(vm, vm->page_size, TEST3_VADDR);
237 		expected_fault_gpa = addr_gva2gpa(vm, l2_test_page);
238 		break;
239 	case TEST_PT_PAGE_WRITE_PROTECTED:
240 		/*
241 		 * Write-protect a page table page in NPT/EPT.  The page is
242 		 * present and readable, but not writable.  The guest page
243 		 * table walk needs write access to set A/D bits, so it
244 		 * triggers a protection violation on the PT page.
245 		 * L2 reads from the page via OUTS.
246 		 */
247 		l2_entry = l2_guest_code_outs;
248 		l2_test_page = vm_alloc(vm, vm->page_size, TEST4_VADDR);
249 		expected_fault_gpa = get_pt_gpa_for_vaddr(vm, l2_test_page);
250 		break;
251 	}
252 
253 	tdp_identity_map_default_memslots(vm);
254 
255 	if (type == TEST_FINAL_PAGE_WRITE_PROTECTED ||
256 	    type == TEST_PT_PAGE_WRITE_PROTECTED)
257 		*tdp_get_pte(vm, expected_fault_gpa) &= ~PTE_WRITABLE_MASK(&vm->stage2_mmu);
258 	else
259 		*tdp_get_pte(vm, expected_fault_gpa) &= ~(PTE_PRESENT_MASK(&vm->stage2_mmu) |
260 							   PTE_READABLE_MASK(&vm->stage2_mmu) |
261 							   PTE_WRITABLE_MASK(&vm->stage2_mmu) |
262 							   PTE_EXECUTABLE_MASK(&vm->stage2_mmu));
263 
264 	sync_global_to_guest(vm, l2_entry);
265 	sync_global_to_guest(vm, l2_test_page);
266 	vcpu_args_set(vcpu, 3, nested_gva, expected_fault_gpa, (u64)type);
267 
268 	/*
269 	 * For the INS-based write test, KVM emulates the instruction and
270 	 * first reads from the I/O port, which exits to userspace.
271 	 * Re-enter the guest so emulation can proceed to the memory
272 	 * write, where the nested page fault is triggered.
273 	 */
274 	for (;;) {
275 		vcpu_run(vcpu);
276 
277 		if (vcpu->run->exit_reason == KVM_EXIT_IO &&
278 		    vcpu->run->io.port == TEST_IO_PORT &&
279 		    vcpu->run->io.direction == KVM_EXIT_IO_IN) {
280 			continue;
281 		}
282 		break;
283 	}
284 
285 	switch (get_ucall(vcpu, &uc)) {
286 	case UCALL_DONE:
287 		break;
288 	case UCALL_ABORT:
289 		REPORT_GUEST_ASSERT(uc);
290 	default:
291 		TEST_FAIL("Unexpected exit reason: %d", vcpu->run->exit_reason);
292 	}
293 
294 	kvm_vm_free(vm);
295 }
296 
297 int main(int argc, char *argv[])
298 {
299 	TEST_REQUIRE(kvm_cpu_has(X86_FEATURE_VMX) || kvm_cpu_has(X86_FEATURE_SVM));
300 	TEST_REQUIRE(kvm_cpu_has_tdp());
301 
302 	run_test(TEST_FINAL_PAGE_UNMAPPED);
303 	run_test(TEST_PT_PAGE_UNMAPPED);
304 	run_test(TEST_FINAL_PAGE_WRITE_PROTECTED);
305 	run_test(TEST_PT_PAGE_WRITE_PROTECTED);
306 
307 	return 0;
308 }
309