xref: /xnu-12377.81.4/tests/vm/configurator_vm_protect.c (revision 043036a2b3718f7f0be807e2870f8f47d3fa0796)
1 /*
2  * Copyright (c) 2024 Apple Inc. All rights reserved.
3  *
4  * @APPLE_OSREFERENCE_LICENSE_HEADER_START@
5  *
6  * This file contains Original Code and/or Modifications of Original Code
7  * as defined in and that are subject to the Apple Public Source License
8  * Version 2.0 (the 'License'). You may not use this file except in
9  * compliance with the License. The rights granted to you under the License
10  * may not be used to create, or enable the creation or redistribution of,
11  * unlawful or unlicensed copies of an Apple operating system, or to
12  * circumvent, violate, or enable the circumvention or violation of, any
13  * terms of an Apple operating system software license agreement.
14  *
15  * Please obtain a copy of the License at
16  * http://www.opensource.apple.com/apsl/ and read it before using this file.
17  *
18  * The Original Code and all software distributed under the License are
19  * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
20  * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
21  * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
22  * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT.
23  * Please see the License for the specific language governing rights and
24  * limitations under the License.
25  *
26  * @APPLE_OSREFERENCE_LICENSE_HEADER_END@
27  */
28 
29 /*
30  * vm/configurator_vm_protect.c
31  *
32  * Test vm_protect with many different VM states.
33  */
34 
35 #include "configurator/vm_configurator_tests.h"
36 
37 T_GLOBAL_META(
38 	T_META_NAMESPACE("xnu.vm.configurator"),
39 	T_META_RADAR_COMPONENT_NAME("xnu"),
40 	T_META_RADAR_COMPONENT_VERSION("VM"),
41 	T_META_RUN_CONCURRENTLY(true),
42 	T_META_ASROOT(true),  /* required for vm submap sysctls */
43 	T_META_ALL_VALID_ARCHS(true)
44 	);
45 
46 
47 /*
48  * Update checker state to mirror a successful call to vm_protect.
49  */
50 static void
checker_perform_vm_protect(checker_list_t * checker_list,mach_vm_address_t start,mach_vm_size_t size,bool set_max,vm_prot_t prot)51 checker_perform_vm_protect(
52 	checker_list_t *checker_list,
53 	mach_vm_address_t start,
54 	mach_vm_size_t size,
55 	bool set_max,
56 	vm_prot_t prot)
57 {
58 	entry_checker_range_t limit =
59 	    checker_list_find_and_clip(checker_list, start, size);
60 	FOREACH_CHECKER(checker, limit) {
61 		if (set_max) {
62 			checker->max_protection = prot;
63 			checker->protection &= checker->max_protection;
64 		} else {
65 			checker->protection = prot;
66 		}
67 	}
68 	checker_list_simplify(checker_list, start, size);
69 }
70 
71 /*
72  * Perform and check a call to mach_vm_protect that is expected to succeed.
73  */
74 static test_result_t
vm_protect_successfully(checker_list_t * checker_list,mach_vm_address_t start,mach_vm_size_t size,vm_prot_t prot)75 vm_protect_successfully(
76 	checker_list_t *checker_list,
77 	mach_vm_address_t start,
78 	mach_vm_size_t size,
79 	vm_prot_t prot)
80 {
81 	kern_return_t kr;
82 
83 	bool set_max = false;
84 
85 	checker_perform_vm_protect(checker_list, start, size, set_max, prot);
86 	kr = mach_vm_protect(mach_task_self(), start, size, set_max, prot);
87 	if (kr != 0) {
88 		T_FAIL("mach_vm_protect(%s) failed (%s)",
89 		    name_for_prot(prot), name_for_kr(kr));
90 		return TestFailed;
91 	}
92 
93 	TEMP_CSTRING(name, "after vm_protect(%s)", name_for_prot(prot));
94 	return verify_vm_state(checker_list, name);
95 }
96 
97 /*
98  * Perform and check mach_vm_protect that is expected to fail due to holes.
99  */
100 static test_result_t
vm_protect_with_holes(checker_list_t * checker_list,mach_vm_address_t start,mach_vm_size_t size)101 vm_protect_with_holes(
102 	checker_list_t *checker_list,
103 	mach_vm_address_t start,
104 	mach_vm_size_t size)
105 {
106 	kern_return_t kr;
107 
108 	/*
109 	 * No checker updates here. vm_map_protect preflights its checks,
110 	 * so it fails with no side effects when the address range has holes.
111 	 */
112 
113 	kr = mach_vm_protect(mach_task_self(), start, size, false, VM_PROT_READ);
114 	if (kr != KERN_INVALID_ADDRESS) {
115 		T_FAIL("mach_vm_protect(holes) expected %s, got %s\n",
116 		    name_for_kr(KERN_INVALID_ADDRESS), name_for_kr(kr));
117 		return TestFailed;
118 	}
119 
120 	return verify_vm_state(checker_list, "after vm_protect");
121 }
122 
123 /*
124  * Perform and check mach_vm_protect that is expected to fail because
125  * the requested protections are more permissive than max_protection.
126  */
127 static test_result_t
vm_protect_beyond_max_prot(checker_list_t * checker_list,mach_vm_address_t start,mach_vm_size_t size,vm_prot_t prot)128 vm_protect_beyond_max_prot(
129 	checker_list_t *checker_list,
130 	mach_vm_address_t start,
131 	mach_vm_size_t size,
132 	vm_prot_t prot)
133 {
134 	kern_return_t kr;
135 
136 	/*
137 	 * No checker updates here. vm_map_protect preflights its checks,
138 	 * so it fails with no effect.
139 	 */
140 
141 	kr = mach_vm_protect(mach_task_self(), start, size, false /*set max*/, prot);
142 	if (kr != KERN_PROTECTION_FAILURE) {
143 		T_FAIL("mach_vm_protect(%s which is beyond max) expected %s, got %s\n",
144 		    name_for_prot(prot),
145 		    name_for_kr(KERN_PROTECTION_FAILURE), name_for_kr(kr));
146 		return TestFailed;
147 	}
148 
149 	TEMP_CSTRING(name, "after vm_protect(%s)", name_for_prot(prot));
150 	return verify_vm_state(checker_list, name);
151 }
152 
153 
154 /*
155  * Perform multiple successful and unsuccessful vm_protect operations
156  * on a region whose max_protections are VM_PROT_NONE
157  */
158 static test_result_t
vm_protect_max_000(checker_list_t * checker_list,mach_vm_address_t start,mach_vm_size_t size)159 vm_protect_max_000(
160 	checker_list_t *checker_list,
161 	mach_vm_address_t start,
162 	mach_vm_size_t size)
163 {
164 	test_result_t results[4];
165 
166 	results[0] = vm_protect_successfully(checker_list, start, size, VM_PROT_NONE);
167 	results[1] = vm_protect_beyond_max_prot(checker_list, start, size, VM_PROT_READ);
168 	results[2] = vm_protect_beyond_max_prot(checker_list, start, size, VM_PROT_WRITE);
169 	results[3] = vm_protect_beyond_max_prot(checker_list, start, size, VM_PROT_READ | VM_PROT_WRITE);
170 
171 	return worst_result(results, countof(results));
172 }
173 
174 /*
175  * Perform multiple successful and unsuccessful vm_protect operations
176  * on a region whose max_protections are VM_PROT_READ
177  */
178 static test_result_t
vm_protect_max_r00(checker_list_t * checker_list,mach_vm_address_t start,mach_vm_size_t size)179 vm_protect_max_r00(
180 	checker_list_t *checker_list,
181 	mach_vm_address_t start,
182 	mach_vm_size_t size)
183 {
184 	test_result_t results[4];
185 
186 	results[0] = vm_protect_successfully(checker_list, start, size, VM_PROT_NONE);
187 	results[1] = vm_protect_successfully(checker_list, start, size, VM_PROT_READ);
188 	results[2] = vm_protect_beyond_max_prot(checker_list, start, size, VM_PROT_WRITE);
189 	results[3] = vm_protect_beyond_max_prot(checker_list, start, size, VM_PROT_READ | VM_PROT_WRITE);
190 
191 	return worst_result(results, countof(results));
192 }
193 
194 /*
195  * Perform multiple successful and unsuccessful vm_protect operations
196  * on a region whose max_protections are VM_PROT_WRITE
197  */
198 static test_result_t
vm_protect_max_0w0(checker_list_t * checker_list,mach_vm_address_t start,mach_vm_size_t size)199 vm_protect_max_0w0(
200 	checker_list_t *checker_list,
201 	mach_vm_address_t start,
202 	mach_vm_size_t size)
203 {
204 	test_result_t results[4];
205 
206 	results[0] = vm_protect_successfully(checker_list, start, size, VM_PROT_NONE);
207 	results[1] = vm_protect_beyond_max_prot(checker_list, start, size, VM_PROT_READ);
208 	results[2] = vm_protect_successfully(checker_list, start, size, VM_PROT_WRITE);
209 	results[3] = vm_protect_beyond_max_prot(checker_list, start, size, VM_PROT_READ | VM_PROT_WRITE);
210 
211 	return worst_result(results, countof(results));
212 }
213 
214 
215 /*
216  * Perform multiple successful and unsuccessful vm_protect operations
217  * on a region whose max_protections are VM_PROT_READ | VM_PROT_WRITE
218  */
219 static test_result_t
vm_protect_max_rw0(checker_list_t * checker_list,mach_vm_address_t start,mach_vm_size_t size)220 vm_protect_max_rw0(
221 	checker_list_t *checker_list,
222 	mach_vm_address_t start,
223 	mach_vm_size_t size)
224 {
225 	test_result_t results[4];
226 
227 	results[0] = vm_protect_successfully(checker_list, start, size, VM_PROT_NONE);
228 	results[1] = vm_protect_successfully(checker_list, start, size, VM_PROT_READ);
229 	results[2] = vm_protect_successfully(checker_list, start, size, VM_PROT_WRITE);
230 	results[3] = vm_protect_successfully(checker_list, start, size, VM_PROT_READ | VM_PROT_WRITE);
231 
232 	return worst_result(results, countof(results));
233 }
234 
235 #if __x86_64__
236 /*
237  * Perform multiple successful and unsuccessful vm_protect operations
238  * on a region whose max_protections are VM_PROT_READ | VM_PROT_WRITE | VM_PROT_EXEC
239  */
240 static test_result_t
vm_protect_max_rwx(checker_list_t * checker_list,mach_vm_address_t start,mach_vm_size_t size)241 vm_protect_max_rwx(
242 	checker_list_t *checker_list,
243 	mach_vm_address_t start,
244 	mach_vm_size_t size)
245 {
246 	/* TODO VM_PROT_EXEC */
247 	return vm_protect_max_rw0(checker_list, start, size);
248 }
249 #endif  /* __x86_64__ */
250 
251 /*
252  * Perform multiple successful and unsuccessful vm_protect operations
253  * on a region whose max_protections are VM_PROT_READ
254  * OR whose max protections are READ|WRITE|EXEC due to Intel submap unnesting.
255  */
256 static test_result_t
vm_protect_max_r00_or_unnested_submap(checker_list_t * checker_list,mach_vm_address_t start,mach_vm_size_t size)257 vm_protect_max_r00_or_unnested_submap(
258 	checker_list_t *checker_list,
259 	mach_vm_address_t start,
260 	mach_vm_size_t size)
261 {
262 #if __x86_64__
263 	return vm_protect_max_rwx(checker_list, start, size);
264 #else  /* not __x86_64__ */
265 	return vm_protect_max_r00(checker_list, start, size);
266 #endif /* not __x86_64__ */
267 }
268 
269 T_DECL(vm_protect,
270     "run vm_protect with various vm configurations")
271 {
272 	vm_tests_t tests = {
273 		.single_entry_1 = vm_protect_max_rw0,
274 		.single_entry_2 = vm_protect_max_rw0,
275 		.single_entry_3 = vm_protect_max_rw0,
276 		.single_entry_4 = vm_protect_max_rw0,
277 
278 		.multiple_entries_1 = vm_protect_max_rw0,
279 		.multiple_entries_2 = vm_protect_max_rw0,
280 		.multiple_entries_3 = vm_protect_max_rw0,
281 		.multiple_entries_4 = vm_protect_max_rw0,
282 		.multiple_entries_5 = vm_protect_max_rw0,
283 		.multiple_entries_6 = vm_protect_max_rw0,
284 
285 		.some_holes_1 = vm_protect_with_holes,
286 		.some_holes_2 = vm_protect_with_holes,
287 		.some_holes_3 = vm_protect_with_holes,
288 		.some_holes_4 = vm_protect_with_holes,
289 		.some_holes_5 = vm_protect_with_holes,
290 		.some_holes_6 = vm_protect_with_holes,
291 		.some_holes_7 = vm_protect_with_holes,
292 		.some_holes_8 = vm_protect_with_holes,
293 		.some_holes_9 = vm_protect_with_holes,
294 		.some_holes_10 = vm_protect_with_holes,
295 		.some_holes_11 = vm_protect_with_holes,
296 		.some_holes_12 = vm_protect_with_holes,
297 
298 		.all_holes_1 = vm_protect_with_holes,
299 		.all_holes_2 = vm_protect_with_holes,
300 		.all_holes_3 = vm_protect_with_holes,
301 		.all_holes_4 = vm_protect_with_holes,
302 
303 		.null_entry        = vm_protect_max_rw0,
304 		.nonresident_entry = vm_protect_max_rw0,
305 		.resident_entry    = vm_protect_max_rw0,
306 
307 		.shared_entry               = vm_protect_max_rw0,
308 		.shared_entry_discontiguous = vm_protect_max_rw0,
309 		.shared_entry_partial       = vm_protect_max_rw0,
310 		.shared_entry_pairs         = vm_protect_max_rw0,
311 		.shared_entry_x1000         = vm_protect_max_rw0,
312 
313 		.cow_entry = vm_protect_max_rw0,
314 		.cow_unreferenced = vm_protect_max_rw0,
315 		.cow_nocow = vm_protect_max_rw0,
316 		.nocow_cow = vm_protect_max_rw0,
317 		.cow_unreadable = vm_protect_max_rw0,
318 		.cow_unwriteable = vm_protect_max_rw0,
319 
320 		.permanent_entry = vm_protect_max_rw0,
321 		.permanent_before_permanent = vm_protect_max_rw0,
322 		.permanent_before_allocation = vm_protect_max_rw0,
323 		.permanent_before_allocation_2 = vm_protect_max_rw0,
324 		.permanent_before_hole = vm_protect_with_holes,
325 		.permanent_after_allocation = vm_protect_max_rw0,
326 		.permanent_after_hole = vm_protect_with_holes,
327 
328 		/*
329 		 * vm_protect without VM_PROT_COPY does not descend into submaps.
330 		 * The parent map's submap entry is r--/r--.
331 		 */
332 		.single_submap_single_entry = vm_protect_max_r00_or_unnested_submap,
333 		.single_submap_single_entry_first_pages = vm_protect_max_r00_or_unnested_submap,
334 		.single_submap_single_entry_last_pages = vm_protect_max_r00_or_unnested_submap,
335 		.single_submap_single_entry_middle_pages = vm_protect_max_r00_or_unnested_submap,
336 		.single_submap_oversize_entry_at_start = vm_protect_max_r00_or_unnested_submap,
337 		.single_submap_oversize_entry_at_end = vm_protect_max_r00_or_unnested_submap,
338 		.single_submap_oversize_entry_at_both = vm_protect_max_r00_or_unnested_submap,
339 
340 		.submap_before_allocation = vm_protect_max_r00_or_unnested_submap,
341 		.submap_after_allocation = vm_protect_max_r00_or_unnested_submap,
342 		.submap_before_hole = vm_protect_with_holes,
343 		.submap_after_hole = vm_protect_with_holes,
344 		.submap_allocation_submap_one_entry = vm_protect_max_r00_or_unnested_submap,
345 		.submap_allocation_submap_two_entries = vm_protect_max_r00_or_unnested_submap,
346 		.submap_allocation_submap_three_entries = vm_protect_max_r00_or_unnested_submap,
347 
348 		.submap_before_allocation_ro = vm_protect_max_r00_or_unnested_submap,
349 		.submap_after_allocation_ro = vm_protect_max_r00_or_unnested_submap,
350 		.submap_before_hole_ro = vm_protect_with_holes,
351 		.submap_after_hole_ro = vm_protect_with_holes,
352 		.submap_allocation_submap_one_entry_ro = vm_protect_max_r00_or_unnested_submap,
353 		.submap_allocation_submap_two_entries_ro = vm_protect_max_r00_or_unnested_submap,
354 		.submap_allocation_submap_three_entries_ro = vm_protect_max_r00_or_unnested_submap,
355 
356 		.protection_single_000_000 = vm_protect_max_000,
357 		.protection_single_000_r00 = vm_protect_max_r00,
358 		.protection_single_r00_r00 = vm_protect_max_r00,
359 		.protection_single_000_0w0 = vm_protect_max_0w0,
360 		.protection_single_0w0_0w0 = vm_protect_max_0w0,
361 		.protection_single_000_rw0 = vm_protect_max_rw0,
362 		.protection_single_r00_rw0 = vm_protect_max_rw0,
363 		.protection_single_0w0_rw0 = vm_protect_max_rw0,
364 		.protection_single_rw0_rw0 = vm_protect_max_rw0,
365 
366 		.protection_pairs_000_000 = vm_protect_max_rw0,
367 		.protection_pairs_000_r00 = vm_protect_max_rw0,
368 		.protection_pairs_000_0w0 = vm_protect_max_rw0,
369 		.protection_pairs_000_rw0 = vm_protect_max_rw0,
370 		.protection_pairs_r00_000 = vm_protect_max_rw0,
371 		.protection_pairs_r00_r00 = vm_protect_max_rw0,
372 		.protection_pairs_r00_0w0 = vm_protect_max_rw0,
373 		.protection_pairs_r00_rw0 = vm_protect_max_rw0,
374 		.protection_pairs_0w0_000 = vm_protect_max_rw0,
375 		.protection_pairs_0w0_r00 = vm_protect_max_rw0,
376 		.protection_pairs_0w0_0w0 = vm_protect_max_rw0,
377 		.protection_pairs_0w0_rw0 = vm_protect_max_rw0,
378 		.protection_pairs_rw0_000 = vm_protect_max_rw0,
379 		.protection_pairs_rw0_r00 = vm_protect_max_rw0,
380 		.protection_pairs_rw0_0w0 = vm_protect_max_rw0,
381 		.protection_pairs_rw0_rw0 = vm_protect_max_rw0,
382 	};
383 
384 	run_vm_tests("vm_protect", __FILE__, &tests, argc, argv);
385 }
386