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