1 /*
2 * Copyright (c) 2023 Apple Computer, 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 #include <arm_acle.h>
30 #include <darwintest.h>
31 #include <fcntl.h>
32 #include <mach/mach.h>
33 #include <mach/mach_vm.h>
34 #include <mach/vm_map.h>
35 #include <mach-o/dyld.h>
36 #include <spawn_private.h>
37 #include <sys/aio.h>
38 #include <sys/spawn_internal.h>
39 #include <stdlib.h>
40 #include <sys/mman.h>
41 #include <sys/sysctl.h>
42 #include <sys/wait.h>
43 #include <signal.h>
44
45 #include "arm_mte_utilities.h"
46 #include "test_utils.h"
47
48 #if (TARGET_OS_OSX || TARGET_OS_IOS) && defined(__arm64__)
49 // TODO(PT): It'd be nice to have this as an allow list rather than the inverse,
50 // but I wasn't able to restrict based on TARGET_OS_[IPHONE|IOS] as this is sometimes set even for XR_OS.
51 // For now, to keep things moving, just restrict this from being set on platforms where
52 // we know it's not the case.
53 #if !(TARGET_OS_XR || TARGET_OS_TV || TARGET_OS_WATCH || TARGET_OS_BRIDGE)
54 #define TARGET_SUPPORTS_MTE_EMULATION 1
55 #endif
56 #endif
57
58 T_GLOBAL_META(
59 T_META_NAMESPACE("xnu.arm"),
60 T_META_RADAR_COMPONENT_NAME("xnu"),
61 T_META_RADAR_COMPONENT_VERSION("arm"),
62 T_META_OWNER("ghackmann"),
63 T_META_RUN_CONCURRENTLY(true),
64 T_META_IGNORECRASHES(".*arm_mte.*"),
65 T_META_CHECK_LEAKS(false));
66
67 static uint64_t
task_footprint(void)68 task_footprint(void)
69 {
70 task_vm_info_data_t ti;
71 kern_return_t kr;
72 mach_msg_type_number_t count;
73
74 count = TASK_VM_INFO_COUNT;
75 kr = task_info(mach_task_self(),
76 TASK_VM_INFO,
77 (task_info_t) &ti,
78 &count);
79 T_QUIET;
80 T_ASSERT_MACH_SUCCESS(kr, "task_info()");
81 #if defined(__arm64__)
82 T_QUIET;
83 T_ASSERT_EQ(count, TASK_VM_INFO_COUNT, "task_info() count = %d (expected %d)",
84 count, TASK_VM_INFO_COUNT);
85 #endif /* defined(__arm64__) */
86 return ti.phys_footprint;
87 }
88
89 static void
do_mte_tag_check(void)90 do_mte_tag_check(void)
91 {
92 static const size_t ALLOC_SIZE = MTE_GRANULE_SIZE * 2;
93
94 vm_address_t address = 0;
95 kern_return_t kr = vm_allocate(mach_task_self(), &address, ALLOC_SIZE, VM_FLAGS_ANYWHERE | VM_FLAGS_MTE);
96 T_ASSERT_MACH_SUCCESS(kr, "allocate tagged memory");
97 char *untagged_ptr = (char *)address;
98
99 char *orig_tagged_ptr = __arm_mte_get_tag(untagged_ptr);
100 unsigned int orig_tag = extract_mte_tag(orig_tagged_ptr);
101 T_ASSERT_EQ_UINT(orig_tag, 0U, "originally assigned tag is zero");
102
103 uint64_t mask = __arm_mte_exclude_tag(orig_tagged_ptr, 0);
104 T_EXPECT_EQ_LLONG(mask, (1LL << 0), "zero tag is excluded");
105
106 char *random_tagged_ptr = NULL;
107 /*
108 * Generate the random tag. We've excluded the original tag, so it should never
109 * reappear no matter how many times we regenerate a new tag.
110 */
111 for (unsigned int i = 0; i < NUM_MTE_TAGS * 4; i++) {
112 random_tagged_ptr = __arm_mte_create_random_tag(untagged_ptr, mask);
113 T_QUIET; T_EXPECT_NE_PTR(orig_tagged_ptr, random_tagged_ptr,
114 "random tag was not taken from excluded tag set");
115
116 ptrdiff_t diff = __arm_mte_ptrdiff(untagged_ptr, random_tagged_ptr);
117 T_QUIET; T_EXPECT_EQ_ULONG(diff, (ptrdiff_t)0, "untagged %p and tagged %p have identical address bits",
118 untagged_ptr, random_tagged_ptr);
119 }
120
121 /* Time to make things real, commit the tag to memory */
122 __arm_mte_set_tag(random_tagged_ptr);
123
124 /* Ensure that we can read back the tag */
125 char *read_back = __arm_mte_get_tag(untagged_ptr);
126 T_EXPECT_EQ_PTR(read_back, random_tagged_ptr, "tag was committed to memory correctly");
127
128 /* Verify that accessing memory actually works */
129 random_tagged_ptr[0] = 't';
130 random_tagged_ptr[1] = 'e';
131 random_tagged_ptr[2] = 's';
132 random_tagged_ptr[3] = 't';
133 T_EXPECT_EQ_STR(random_tagged_ptr, "test", "read/write from tagged memory");
134
135 /*
136 * Confirm that the next MTE granule still has the default tag, and then
137 * simulate an out-of-bounds access into that granule.
138 */
139 void *next_granule_ptr = orig_tagged_ptr + MTE_GRANULE_SIZE;
140 unsigned int next_granule_tag = extract_mte_tag(next_granule_ptr);
141 T_QUIET; T_ASSERT_EQ_UINT(next_granule_tag, 0U,
142 "next MTE granule still has its originally assigned tag");
143
144 T_LOG("attempting out-of-bounds access to tagged memory");
145 expect_sigkill(^{
146 random_tagged_ptr[MTE_GRANULE_SIZE] = '!';
147 }, "out-of-bounds access to tagged memory raises uncatchable exception");
148
149 /*
150 * Simulate a use-after-free by accessing orig_tagged_ptr, which has an
151 * out-of-date tag.
152 */
153 T_LOG("attempting use-after-free access to tagged memory");
154 expect_sigkill(^{
155 orig_tagged_ptr[0] = 'T';
156 }, "use-after-free access to tagged memory raises uncatchable exception");
157
158 __arm_mte_set_tag(orig_tagged_ptr);
159 __arm_mte_set_tag(orig_tagged_ptr + MTE_GRANULE_SIZE);
160 vm_deallocate(mach_task_self(), address, ALLOC_SIZE);
161 }
162
163 T_DECL(mte_tag_check,
164 "Test MTE2 tag check fault handling",
165 T_META_REQUIRES_SYSCTL_EQ("hw.optional.arm.FEAT_MTE2", 1),
166 XNU_T_META_SOC_SPECIFIC)
167 {
168 #if !__arm64__
169 T_SKIP("Running on non-arm64 target, skipping...");
170 #else /* !__arm64__ */
171 do_mte_tag_check();
172 #endif
173 }
174
175 T_DECL(mte_tag_check_child,
176 "Test MTE2 tag check fault in a child process",
177 T_META_REQUIRES_SYSCTL_EQ("hw.optional.arm.FEAT_MTE2", 1),
178 XNU_T_META_SOC_SPECIFIC)
179 {
180 #if !__arm64__
181 T_SKIP("Running on non-arm64 target, skipping...");
182 #else /* !__arm64__ */
183 pid_t pid = fork();
184 if (pid == 0) {
185 /*
186 * Make sure the child process also has tag checks enabled.
187 */
188 do_mte_tag_check();
189 } else {
190 T_ASSERT_TRUE(pid != -1, "Checking fork success in parent");
191
192 int status = 0;
193 T_ASSERT_POSIX_SUCCESS(waitpid(pid, &status, 0), "waitpid");
194 }
195 #endif
196 }
197
198 T_DECL(mte_canonical_tag_check,
199 "Test MTE4 Canonical Tag Check fault handling",
200 T_META_REQUIRES_SYSCTL_EQ("hw.optional.arm.FEAT_MTE4", 1),
201 XNU_T_META_SOC_SPECIFIC)
202 {
203 #if !__arm64__
204 T_SKIP("Running on non-arm64 target, skipping...");
205 #else /* !__arm64__ */
206 vm_address_t address = 0;
207 kern_return_t kr = vm_allocate(mach_task_self(), &address, MTE_GRANULE_SIZE, VM_FLAGS_ANYWHERE);
208 T_ASSERT_MACH_SUCCESS(kr, "allocate a canonically-tagged page");
209 char *ptr = (char *)address;
210
211 T_LOG("attempting to set tag on canonically-tagged memory");
212 char *tagged_ptr = __arm_mte_increment_tag(ptr, 1);
213 expect_signal(SIGBUS, ^{
214 __arm_mte_set_tag(tagged_ptr);
215 }, "setting tag on canonically-tagged memory raises a canonical memory permission fault");
216
217 T_LOG("attempting to access canonically-tagged memory with a tagged address");
218 expect_sigkill(^{
219 tagged_ptr[0] = '!';
220 }, "accessing canonically-tagged memory with a tagged address raises a canonical tag check fault");
221
222 vm_deallocate(mach_task_self(), address, MTE_GRANULE_SIZE);
223 #endif
224 }
225
226 static void
run_mte_copyio_tests(bool tag_check_faults_enabled)227 run_mte_copyio_tests(bool tag_check_faults_enabled)
228 {
229 static_assert(MAXTHREADNAMESIZE >= MTE_GRANULE_SIZE * 2, "kern.threadname parameter can span multiple MTE granules");
230
231 const size_t buf_size = MAXTHREADNAMESIZE;
232 const size_t threadname_len = MTE_GRANULE_SIZE * 2;
233 vm_address_t address = 0;
234 kern_return_t kr = vm_allocate(mach_task_self(), &address, buf_size, VM_FLAGS_ANYWHERE | VM_FLAGS_MTE);
235 T_QUIET; T_ASSERT_MACH_SUCCESS(kr, "allocate tagged memory");
236
237 char *untagged_ptr = (char *)address;
238 /* n.b.: kern.threadname uses unterminated strings */
239 memset(untagged_ptr, 'A', threadname_len);
240
241 char *tagged_ptr = __arm_mte_create_random_tag(untagged_ptr, 0);
242 __arm_mte_set_tag(tagged_ptr);
243 char *next_granule_ptr = tagged_ptr + MTE_GRANULE_SIZE;
244 __arm_mte_set_tag(next_granule_ptr);
245
246 int err = sysctlbyname("kern.threadname", NULL, NULL, tagged_ptr, threadname_len);
247 T_ASSERT_POSIX_SUCCESS(err, "copyin using tagged pointer succeeds");
248
249 /* Simulate use-after-free by passing in obsolete tag */
250 if (tag_check_faults_enabled) {
251 expect_sigkill(^{
252 sysctlbyname("kern.threadname", NULL, NULL, untagged_ptr, threadname_len);
253 }, "copyin using incorrectly-tagged pointer");
254 } else {
255 err = sysctlbyname("kern.threadname", NULL, NULL, untagged_ptr, threadname_len);
256 T_ASSERT_POSIX_SUCCESS(err, "bypass: copyin using incorrectly-tagged pointer succeeds");
257 }
258
259 /* Simulate out-of-bounds access by giving the second MTE granule a different tag */
260 char *different_tag_next_granule_ptr = __arm_mte_increment_tag(next_granule_ptr, 1);
261 T_QUIET; T_ASSERT_NE(different_tag_next_granule_ptr, next_granule_ptr, "__arm_mte_increment_tag()");
262 __arm_mte_set_tag(different_tag_next_granule_ptr);
263 if (tag_check_faults_enabled) {
264 expect_sigkill(^{
265 sysctlbyname("kern.threadname", NULL, NULL, tagged_ptr, threadname_len);
266 }, "copyin using inconsistently-tagged buffer");
267 } else {
268 err = sysctlbyname("kern.threadname", NULL, NULL, tagged_ptr, threadname_len);
269 T_ASSERT_POSIX_SUCCESS(err, "bypass: copyin using inconsistently-tagged buffer succeeds");
270 }
271 __arm_mte_set_tag(next_granule_ptr);
272
273 size_t oldlen = buf_size;
274 err = sysctlbyname("kern.threadname", tagged_ptr, &oldlen, NULL, 0);
275 T_EXPECT_POSIX_SUCCESS(err, "copyout using tagged pointer succeeds");
276
277 #pragma clang diagnostic push
278 #pragma clang diagnostic ignored "-Wshadow"
279
280 if (tag_check_faults_enabled) {
281 expect_sigkill(^{
282 /* We need to repopulate kern.threadname since it isn't inherited across fork() */
283 int err = sysctlbyname("kern.threadname", NULL, NULL, tagged_ptr, threadname_len);
284 T_QUIET; T_ASSERT_POSIX_SUCCESS(err, "sysctlbyname(kern.threadname)");
285
286 size_t oldlen = buf_size;
287 sysctlbyname("kern.threadname", untagged_ptr, &oldlen, NULL, 0);
288 }, "copyout using incorrectly-tagged pointer");
289 } else {
290 size_t oldlen = buf_size;
291 int err = sysctlbyname("kern.threadname", untagged_ptr, &oldlen, NULL, 0);
292 T_EXPECT_POSIX_SUCCESS(err, "bypass: copyout using incorrectly-tagged pointer succeeds");
293 }
294
295 __arm_mte_set_tag(different_tag_next_granule_ptr);
296 if (tag_check_faults_enabled) {
297 expect_sigkill(^{
298 int err = sysctlbyname("kern.threadname", NULL, NULL, tagged_ptr, threadname_len);
299 T_QUIET; T_ASSERT_POSIX_SUCCESS(err, "sysctlbyname(kern.threadname)");
300
301 size_t oldlen = buf_size;
302 sysctlbyname("kern.threadname", tagged_ptr, &oldlen, NULL, 0);
303 }, "copyout using inconsistently-tagged buffer");
304 } else {
305 size_t oldlen = buf_size;
306 int err = sysctlbyname("kern.threadname", tagged_ptr, &oldlen, NULL, 0);
307 T_EXPECT_POSIX_SUCCESS(err, "bypass: copyout using inconsistently-tagged buffer succeeds");
308 }
309 __arm_mte_set_tag(next_granule_ptr);
310
311 #pragma clang diagnostic pop
312
313 vm_deallocate(mach_task_self(), address, buf_size);
314 }
315
316 T_DECL(mte_copyio,
317 "Test MTE tag handling during copyin/copyout operations",
318 T_META_ENABLED(TARGET_CPU_ARM64),
319 T_META_REQUIRES_SYSCTL_EQ("hw.optional.arm.FEAT_MTE4", 1),
320 XNU_T_META_SOC_SPECIFIC)
321 {
322 run_mte_copyio_tests(true);
323 }
324
325 T_DECL(mte_malloc_footprint_test,
326 "Test footprint across malloc() and free()",
327 T_META_REQUIRES_SYSCTL_EQ("hw.optional.arm.FEAT_MTE2", 1),
328 XNU_T_META_SOC_SPECIFIC,
329 T_META_ENABLED(false) /* rdar://131390446 */)
330 {
331 #if !__arm64__
332 T_SKIP("Running on non-arm64 target, skipping...");
333 #else /* !__arm64__ */
334 uint64_t count = 1024;
335 uint64_t margin = 4;
336 char* address[count];
337 uint64_t size = PAGE_SIZE;
338
339 for (unsigned int i = 0; i < count; i++) {
340 address[i] = (char *) malloc(size);
341
342 char *cp;
343 for (cp = (char *) (address[i]); cp < (char *) (address[i] + size); cp += PAGE_SIZE) {
344 *cp = 'x';
345 }
346 }
347
348 uint64_t fp1 = task_footprint();
349 T_LOG("Footprint after malloc(): %llu bytes", fp1);
350
351 for (unsigned int i = 0; i < count; i++) {
352 free(address[i]);
353 }
354 uint64_t fp2 = task_footprint();
355 T_LOG("Footprint after free(): %llu bytes", fp2);
356
357 T_EXPECT_TRUE(((fp2 + PAGE_SIZE * (count - margin)) <= fp1), "Footprint after free() is higher than expected.");
358 #endif
359 }
360
361 T_DECL(mte_tagged_memory_direct_io,
362 "Test direct I/O on tagged memory",
363 T_META_REQUIRES_SYSCTL_EQ("hw.optional.arm.FEAT_MTE2", 1),
364 XNU_T_META_SOC_SPECIFIC)
365 {
366 #if !__arm64__
367 T_SKIP("Running on non-arm64 target, skipping...");
368 #else /* !__arm64__ */
369
370 uint64_t size = PAGE_SIZE;
371 char* address = (char*) malloc(size);
372
373 char *cp;
374 for (cp = (char *) (address); cp < (char *) (address + size); cp += PAGE_SIZE) {
375 *cp = 'x';
376 }
377
378 int fd = open("/tmp/file1", O_CREAT | O_WRONLY | O_TRUNC | O_CLOEXEC, 0644);
379 T_ASSERT_TRUE(fd > 0, "File open successful");
380 T_ASSERT_TRUE(((fcntl(fd, F_NOCACHE, 1)) != -1), "Setting F_NOCACHE");
381 ssize_t ret = pwrite(fd, address, size, 0);
382 T_ASSERT_TRUE((uint64_t) ret == size, "pwrite() on tagged memory");
383
384 char *incorrectly_tagged = __arm_mte_increment_tag(address, 1);
385 ret = pwrite(fd, incorrectly_tagged, size, 0);
386 T_ASSERT_TRUE((uint64_t) ret == size, "pwrite() on incorrectly tagged memory passes with direct I/O");
387
388 free(address);
389 #endif
390 }
391
392 T_DECL(mte_tagged_memory_copy_io,
393 "Test direct I/O on tagged memory",
394 T_META_REQUIRES_SYSCTL_EQ("hw.optional.arm.FEAT_MTE2", 1),
395 XNU_T_META_SOC_SPECIFIC)
396 {
397 #if !__arm64__
398 T_SKIP("Running on non-arm64 target, skipping...");
399 #else /* !__arm64__ */
400
401 uint64_t size = PAGE_SIZE;
402 char* address = (char*) malloc(size);
403
404 char *cp;
405 for (cp = (char *) (address); cp < (char *) (address + size); cp += PAGE_SIZE) {
406 *cp = 'x';
407 }
408
409 int fd = open("/tmp/file1", O_CREAT | O_WRONLY | O_TRUNC | O_CLOEXEC, 0644);
410 T_ASSERT_TRUE(fd > 0, "File open successful");
411 ssize_t ret = pwrite(fd, address, size, 0);
412 T_ASSERT_TRUE((uint64_t) ret == size, "pwrite() on tagged memory");
413
414 char *incorrectly_tagged = __arm_mte_increment_tag(address, 1);
415 expect_sigkill(^{
416 (void)pwrite(fd, incorrectly_tagged, size, 0);
417 }, "copy I/O on wrongly tagged memory");
418
419 free(address);
420 #endif
421 }
422
423
424 static int FORK_TEST_CHILD_WRITES_FIRST = 0x1;
425 static int FORK_TEST_CHILD_FORKS = 0x2;
426 static int FORK_TEST_CHILD_RETAGS = 0x4;
427 static void
do_fork_test(vm_size_t vm_alloc_sz,int flags)428 do_fork_test(vm_size_t vm_alloc_sz, int flags)
429 {
430 #if !__arm64__
431 T_SKIP("Running on non-arm64 target, skipping...");
432 #else /* !__arm64__ */
433 vm_address_t address = 0;
434 kern_return_t kr = vm_allocate(mach_task_self(), &address, vm_alloc_sz, VM_FLAGS_ANYWHERE | VM_FLAGS_MTE);
435
436 T_ASSERT_MACH_SUCCESS(kr, "allocate tagged memory");
437
438 char *untagged_ptr = (char *)address;
439 char *orig_tagged_ptr = __arm_mte_get_tag(untagged_ptr);
440 uint64_t mask = __arm_mte_exclude_tag(orig_tagged_ptr, 0);
441
442 size_t count;
443 size_t offset;
444 const vm_size_t NUM_GRANULES = vm_alloc_sz / MTE_GRANULE_SIZE;
445 char *tagged_ptrs[NUM_GRANULES];
446
447 /*
448 * Tag the entire page
449 */
450 for (count = 0; count < NUM_GRANULES; count++) {
451 offset = count * MTE_GRANULE_SIZE;
452 tagged_ptrs[count] = __arm_mte_create_random_tag(untagged_ptr + offset, mask);
453 __arm_mte_set_tag(tagged_ptrs[count]);
454 }
455
456 if (!(flags & FORK_TEST_CHILD_WRITES_FIRST)) {
457 for (count = 0; count < NUM_GRANULES; count++) {
458 *(tagged_ptrs[count]) = 'a';
459 }
460 }
461
462 pid_t pid = fork();
463 if (pid == 0) {
464 T_LOG("Child forked");
465
466 if (flags & FORK_TEST_CHILD_RETAGS) {
467 T_LOG("Child editing tags");
468 /* re-tag the entire page */
469 for (count = 0; count < NUM_GRANULES; count++) {
470 tagged_ptrs[count] = __arm_mte_increment_tag(tagged_ptrs[count], 1);
471 __arm_mte_set_tag(tagged_ptrs[count]);
472 }
473 }
474
475 T_LOG("Accessing parent tagged memory");
476 /*
477 * Make sure the child process also has tag checks enabled.
478 */
479 for (count = 0; count < NUM_GRANULES; count++) {
480 *(tagged_ptrs[count]) = 'a';
481 }
482
483 T_LOG("Child access to tagged memory success");
484
485 expect_sigkill(^{
486 *untagged_ptr = 'b';
487 }, "Child access through untagged ptr");
488
489 if (flags & FORK_TEST_CHILD_FORKS) {
490 pid_t pid2 = fork();
491
492 if (pid2 == 0) {
493 T_LOG("Grandchild forked");
494
495 T_LOG("Accessing grandparent's tagged memory");
496
497 for (count = 0; count < NUM_GRANULES; count++) {
498 *(tagged_ptrs[count]) = 'a';
499 }
500
501 T_LOG("Grandchild access to tagged memory success");
502
503 pid_t pid3 = fork();
504
505 if (pid3 == 0) {
506 T_LOG("Great grandchild forked");
507
508 T_LOG("Accessing great grandparent's tagged memory");
509
510 for (count = 0; count < NUM_GRANULES; count++) {
511 *(tagged_ptrs[count]) = 'a';
512 }
513
514 T_LOG("Great grandchild access to tagged memory success");
515
516 kr = vm_deallocate(mach_task_self(), address, vm_alloc_sz);
517 T_QUIET; T_ASSERT_MACH_SUCCESS(kr, "Great grandchild vm_deallocate");
518 exit(0);
519 } else {
520 T_ASSERT_TRUE(pid3 != -1, "Checking fork success in grandchild");
521 int status2 = 0;
522
523 T_ASSERT_POSIX_SUCCESS(waitpid(pid3, &status2, 0), "waitpid");
524 T_ASSERT_TRUE(WIFEXITED(status2) > 0, "Great grandchild exited normally");
525 }
526
527 kr = vm_deallocate(mach_task_self(), address, vm_alloc_sz);
528 T_QUIET; T_ASSERT_MACH_SUCCESS(kr, "Grandchild vm_deallocate");
529 exit(0);
530 } else {
531 T_ASSERT_TRUE(pid2 != -1, "Checking fork success in child");
532 int status2 = 0;
533 T_ASSERT_POSIX_SUCCESS(waitpid(pid2, &status2, 0), "waitpid");
534 T_ASSERT_TRUE(WIFEXITED(status2) > 0, "Grandchild exited normally");
535 }
536 }
537
538 kr = vm_deallocate(mach_task_self(), address, vm_alloc_sz);
539 T_QUIET; T_ASSERT_MACH_SUCCESS(kr, "Child vm_deallocate");
540 exit(0);
541 } else {
542 T_ASSERT_TRUE(pid != -1, "Checking fork success in parent");
543
544 int status = 0;
545 T_ASSERT_POSIX_SUCCESS(waitpid(pid, &status, 0), "waitpid");
546
547 T_ASSERT_TRUE(WIFEXITED(status) > 0, "Child exited normally");
548
549 /* Verify that accessing memory actually works */
550 for (count = 0; count < NUM_GRANULES; count++) {
551 *(tagged_ptrs[count]) = 'a';
552 }
553
554 T_LOG("Parent access to tagged memory sucessfull");
555
556 expect_sigkill(^{
557 *untagged_ptr = 'b';
558 }, "Parent access through untagged ptr");
559 }
560
561 kr = vm_deallocate(mach_task_self(), address, vm_alloc_sz);
562 T_QUIET; T_ASSERT_MACH_SUCCESS(kr, "Parent vm_deallocate");
563 #endif
564 }
565
566 T_DECL(mte_tag_check_fork_after_alloc_less_page_sz,
567 "Test MTE2 tag check fault in a child process after vm_allocate(ALLOC_SIZE, MTE)",
568 T_META_REQUIRES_SYSCTL_EQ("hw.optional.arm.FEAT_MTE2", 1),
569 XNU_T_META_SOC_SPECIFIC)
570 {
571 static const size_t ALLOC_SIZE = MTE_GRANULE_SIZE * 2;
572 do_fork_test(ALLOC_SIZE, 0);
573 }
574
575 T_DECL(mte_tag_check_fork_after_alloc_page_sz,
576 "Test MTE2 tag check fault in a child process after vm_allocate(PAGE_SIZE, MTE)",
577 T_META_REQUIRES_SYSCTL_EQ("hw.optional.arm.FEAT_MTE2", 1),
578 XNU_T_META_SOC_SPECIFIC)
579 {
580 do_fork_test(PAGE_SIZE, 0);
581 }
582
583 /* NOTE: These following tests matter for when we switch to MEMORY_OBJECT_COPY_DELAY_FORK */
584 T_DECL(mte_tag_check_fork_child_fault_write,
585 "Test MTE2 tag check fault in a child process after vm_allocate(MTE) and child writes to tagged memory first",
586 T_META_REQUIRES_SYSCTL_EQ("hw.optional.arm.FEAT_MTE2", 1),
587 XNU_T_META_SOC_SPECIFIC)
588 {
589 do_fork_test(PAGE_SIZE, FORK_TEST_CHILD_WRITES_FIRST);
590 }
591
592 T_DECL(mte_tag_check_fork_child_double_fork,
593 "Test MTE2 tag check fault in a child process after vm_allocate(MTE) and child writes to tagged memory first and then forks again",
594 T_META_REQUIRES_SYSCTL_EQ("hw.optional.arm.FEAT_MTE2", 1),
595 XNU_T_META_SOC_SPECIFIC)
596 {
597 do_fork_test(PAGE_SIZE, FORK_TEST_CHILD_WRITES_FIRST | FORK_TEST_CHILD_FORKS);
598 }
599
600 /*
601 * These cases specifically test that tag setting instructions (STG) resolve CoW
602 * on fork correctly, since the child doesn't fault in the mapping by writing first.
603 */
604 T_DECL(mte_tag_check_fork_child_retag,
605 "Test MTE2 tag check fault in a child process after vm_allocate(MTE) and child changes tags",
606 T_META_REQUIRES_SYSCTL_EQ("hw.optional.arm.FEAT_MTE2", 1),
607 XNU_T_META_SOC_SPECIFIC)
608 {
609 do_fork_test(PAGE_SIZE, FORK_TEST_CHILD_RETAGS);
610 }
611
612 T_DECL(mte_tag_check_fork_child_fault_write_retag,
613 "Test MTE2 tag check fault in a child process after vm_allocate(MTE) and child changes tags and writes to tagged memory first",
614 T_META_REQUIRES_SYSCTL_EQ("hw.optional.arm.FEAT_MTE2", 1),
615 XNU_T_META_SOC_SPECIFIC)
616 {
617 do_fork_test(PAGE_SIZE, FORK_TEST_CHILD_WRITES_FIRST | FORK_TEST_CHILD_RETAGS);
618 }
619
620 T_DECL(mte_tag_check_fork_child_fault_write_retag_double_fork,
621 "Test MTE2 tag check fault in a child process after vm_allocate(MTE) and child changes tags, writes to tagged memory first, and then forks again",
622 T_META_REQUIRES_SYSCTL_EQ("hw.optional.arm.FEAT_MTE2", 1),
623 XNU_T_META_SOC_SPECIFIC)
624 {
625 do_fork_test(PAGE_SIZE, FORK_TEST_CHILD_WRITES_FIRST | FORK_TEST_CHILD_RETAGS | FORK_TEST_CHILD_FORKS);
626 }
627
628
629 T_DECL(mte_userland_uses_fake_kernel_pointer,
630 "Test that VM correctly rejects kernel-looking pointer from userspace",
631 T_META_REQUIRES_SYSCTL_EQ("hw.optional.arm.FEAT_MTE4", 1),
632 XNU_T_META_SOC_SPECIFIC,
633 T_META_ENABLED(__arm64__))
634 {
635 #if __arm64__
636 /*
637 * When the VM is given a user address that looks like a kernel pointer,
638 * we want to make sure that it still gets canonicalized as a user address
639 * (rather than a valid kernel pointer).
640 * This should result in a nonsensical pointer that shouldn't exist in any
641 * VM map, so the memory access should fail.
642 */
643 vm_address_t addr = 0;
644 kern_return_t kr = vm_allocate(
645 mach_task_self(),
646 &addr,
647 MTE_GRANULE_SIZE,
648 VM_FLAGS_ANYWHERE);
649 T_QUIET;
650 T_ASSERT_MACH_SUCCESS(kr, "allocate an untagged page");
651 T_LOG("Allocated untagged page at addr: 0x%lx", addr);
652
653 /* Create a kernel-like pointer in userspace */
654 char *tampered_ptr = (char *)(addr | VM_MIN_KERNEL_ADDRESS);
655 T_LOG("Tampered ptr: %p", tampered_ptr);
656
657 /* segfault is expected, since the pointer is not valid in the userspace map */
658 expect_signal(SIGSEGV, ^{
659 *tampered_ptr = 'a';
660 }, "Accessing kernel-like pointer from userspace");
661 vm_deallocate(mach_task_self(), addr, MTE_GRANULE_SIZE);
662 #endif /* __arm64__ */
663 }
664
665 /*
666 * Allocates tagged memory, assigns the memory a tag, and attempts to
667 * read the memory into its own address space via mach_vm_read().
668 *
669 * Also attempts to read the memory into its own address space with an untagged
670 * pointer, which we expect to fail.
671 */
672 static void
mte_mach_vm_read(mach_vm_size_t sz)673 mte_mach_vm_read(mach_vm_size_t sz)
674 {
675 T_SETUPBEGIN;
676 __block mach_vm_address_t addr = 0;
677 __block vm_offset_t read_addr = 0;
678 __block mach_msg_type_number_t read_size = 0;
679
680 mach_vm_size_t sz_rounded = (sz + (MTE_GRANULE_SIZE - 1)) & (unsigned)~((signed)(MTE_GRANULE_SIZE - 1));
681 T_LOG("sz rounded: %llu", sz_rounded);
682 /* Allocate some tagged memory */
683 T_LOG("Allocate tagged memory");
684 kern_return_t kr = mach_vm_allocate(
685 mach_task_self(),
686 &addr,
687 sz_rounded,
688 VM_FLAGS_ANYWHERE | VM_FLAGS_MTE);
689 T_ASSERT_MACH_SUCCESS(kr, "Allocated tagged page");
690 T_QUIET; T_ASSERT_NE_ULLONG(0ULL, addr, "Allocated address is not null");
691
692 uint64_t *untagged_ptr = (uint64_t *)addr;
693
694 uint64_t *orig_tagged_ptr = __arm_mte_get_tag(untagged_ptr);
695 unsigned int orig_tag = extract_mte_tag(orig_tagged_ptr);
696 T_QUIET; T_ASSERT_EQ_UINT(orig_tag, 0U, "Originally assigned tag is zero");
697
698 uint64_t mask = __arm_mte_exclude_tag(orig_tagged_ptr, 0);
699 T_QUIET; T_EXPECT_EQ_LLONG(mask, (1ULL << 0), "Zero tag is excluded");
700
701 /* Generate random tag */
702 uint64_t *tagged_ptr = NULL;
703 tagged_ptr = __arm_mte_create_random_tag(untagged_ptr, mask);
704 T_QUIET; T_EXPECT_NE_PTR(orig_tagged_ptr, tagged_ptr,
705 "Random tag was not taken from excluded tag set");
706
707 /* Time to make things real, commit the tag to memory */
708 for (uintptr_t cur_ptr = (uintptr_t)tagged_ptr;
709 cur_ptr < (uintptr_t)tagged_ptr + sz_rounded;
710 cur_ptr += MTE_GRANULE_SIZE) {
711 __arm_mte_set_tag((void *)cur_ptr);
712 }
713 T_LOG("Commited tagged pointer to memory: %p", tagged_ptr);
714
715 /* Write to the memory */
716 for (uint i = 0; i < sz_rounded / sizeof(uint64_t); ++i) {
717 tagged_ptr[i] = addr;
718 }
719 T_LOG("Wrote to memory");
720 T_SETUPEND;
721
722 T_LOG("Reading %llu bytes from %p", sz, tagged_ptr);
723 kr = mach_vm_read(
724 mach_task_self(),
725 (mach_vm_address_t)tagged_ptr,
726 sz,
727 &read_addr,
728 &read_size);
729 T_ASSERT_EQ(kr, KERN_SUCCESS,
730 "mach_vm_read %llu bytes from tagged ptr", sz);
731
732 /* Make sure we get the same thing back */
733 T_ASSERT_EQ_UINT((unsigned int)sz, read_size,
734 "sz:%llu == read_size:%d", sz, read_size);
735 int result = memcmp(tagged_ptr, (void *)read_addr, sz);
736 T_ASSERT_EQ(result, 0, "mach_vm_read back the same info");
737
738 /* Now try with incorrectly tagged pointer (aka, no tag) */
739 uint64_t *random_tagged_ptr = NULL;
740 /* Exclude the previous tag */
741 unsigned int previous_tag = extract_mte_tag(tagged_ptr);
742 mask = __arm_mte_exclude_tag(tagged_ptr, previous_tag);
743 random_tagged_ptr = __arm_mte_create_random_tag(untagged_ptr, mask);
744 T_LOG("random tagged ptr: %p", random_tagged_ptr);
745 T_EXPECT_NE_PTR(tagged_ptr, random_tagged_ptr,
746 "Random tag was not taken from excluded tag set");
747
748 T_LOG("Reading %llu bytes from %p", sz, random_tagged_ptr);
749 expect_sigkill(^{
750 T_LOG("tagged_ptr[0]: %llu", random_tagged_ptr[0]);
751 }, "Accessing memory with the wrong tag, should fail");
752
753 expect_sigkill(^{
754 (void)mach_vm_read(
755 mach_task_self(),
756 (mach_vm_address_t)random_tagged_ptr,
757 KERNEL_BUFFER_COPY_THRESHOLD,
758 &read_addr,
759 &read_size);
760 }, "Untagged pointer access leads to tag check fault");
761
762 /* Reset tags to 0 before freeing */
763 for (uintptr_t cur_ptr = (uintptr_t)orig_tagged_ptr;
764 cur_ptr < (uintptr_t)orig_tagged_ptr + sz_rounded;
765 cur_ptr += MTE_GRANULE_SIZE) {
766 __arm_mte_set_tag((void *)cur_ptr);
767 }
768 vm_deallocate(mach_task_self(), addr, sz_rounded);
769 }
770
771 T_DECL(mte_mach_vm_read_16b,
772 "mach_vm_read 16 bytes of tagged memory",
773 T_META_REQUIRES_SYSCTL_EQ("hw.optional.arm.FEAT_MTE4", 1),
774 XNU_T_META_SOC_SPECIFIC,
775 T_META_ENABLED(__arm64__))
776 {
777 #if __arm64__
778 mte_mach_vm_read(MTE_GRANULE_SIZE);
779 #endif /* __arm64__ */
780 }
781
782 T_DECL(mte_mach_vm_read_32k,
783 "mach_vm_read 32k bytes of tagged memory",
784 T_META_REQUIRES_SYSCTL_EQ("hw.optional.arm.FEAT_MTE4", 1),
785 XNU_T_META_SOC_SPECIFIC,
786 T_META_ENABLED(__arm64__))
787 {
788 #if __arm64__
789 mte_mach_vm_read(KERNEL_BUFFER_COPY_THRESHOLD);
790 #endif /* __arm64__ */
791 }
792
793 T_DECL(mte_mach_vm_read_over_32k,
794 "mach_vm_read 32k + 1 bytes of tagged memory",
795 T_META_REQUIRES_SYSCTL_EQ("hw.optional.arm.FEAT_MTE4", 1),
796 XNU_T_META_SOC_SPECIFIC,
797 T_META_ENABLED(__arm64__))
798 {
799 #if __arm64__
800 /* This will actually get rounded to 32K + 16 */
801 mte_mach_vm_read(KERNEL_BUFFER_COPY_THRESHOLD + 1);
802 #endif /* __arm64__ */
803 }
804
805 T_DECL(mte_vm_map_copyinout_in_kernel,
806 "Test that the VM handles vm_map_copyin correctly for kernel-to-kernel tagged memory",
807 T_META_REQUIRES_SYSCTL_EQ("hw.optional.arm.FEAT_MTE4", 1),
808 XNU_T_META_SOC_SPECIFIC,
809 T_META_ASROOT(true),
810 T_META_ENABLED(__arm64__))
811 {
812 #if __arm64__
813 T_SKIP("This test is expected to panic; comment this line to be able to run it at desk.");
814 (void) run_sysctl_test("vm_map_copyio", 0);
815 #endif /* __arm64__ */
816 }
817
818 #if __arm64__
819 static void
do_remap_test(bool own_memory)820 do_remap_test(bool own_memory)
821 {
822 mach_vm_address_t tagged_addr, untagged_addr;
823 mach_vm_size_t size = PAGE_SIZE;
824
825 T_LOG("Allocate tagged memory");
826 tagged_addr = allocate_and_tag_range(size, TAG_RANDOM);
827 char *tagged_ptr = (char*) tagged_addr;
828 untagged_addr = tagged_addr & ~MTE_TAG_MASK;
829
830 /* Write to the memory */
831 for (unsigned int i = 0; i < size; i++) {
832 tagged_ptr[i] = 'a';
833 }
834
835 T_LOG("Wrote to memory");
836
837 expect_normal_exit(^{
838 kern_return_t kr;
839 mach_port_t port;
840 if (own_memory) {
841 port = mach_task_self();
842 } else {
843 /* note: expect_normal_exit forks, so the parent has the allocation as well */
844 kr = task_for_pid(mach_task_self(), getppid(), &port);
845 T_ASSERT_MACH_SUCCESS(kr, "task_for_pid");
846 }
847
848 mach_vm_address_t remap_addr = 0;
849 vm_prot_t curprot = VM_PROT_WRITE | VM_PROT_READ;
850 vm_prot_t maxprot = VM_PROT_WRITE | VM_PROT_READ;
851 kr = mach_vm_remap_new(mach_task_self(), &remap_addr, size,
852 /* mask = */ 0, VM_FLAGS_ANYWHERE, port, untagged_addr,
853 /* copy = */ FALSE, &curprot, &maxprot, VM_INHERIT_DEFAULT);
854 T_ASSERT_MACH_SUCCESS(kr, "successfully remapped tagged memory");
855
856 T_ASSERT_EQ(remap_addr & MTE_TAG_MASK, 0ULL, "vm_remap returns an untagged pointer");
857
858 char *untagged_remap_ptr = (char*) remap_addr;
859 char *tagged_remap_ptr = __arm_mte_get_tag(untagged_remap_ptr);
860 char *incorrectly_tagged_remap_ptr = __arm_mte_increment_tag(tagged_remap_ptr, 1);
861
862 /* verify the data is correct; check every granule for speed */
863 for (unsigned int i = 0; i < size; i += MTE_GRANULE_SIZE) {
864 T_QUIET; T_EXPECT_EQ(tagged_remap_ptr[i], 'a', "read value %u from array", i);
865 }
866 T_LOG("Verified data from child");
867
868 /* make sure the new mapping is also tagged */
869 expect_sigkill(^{
870 *untagged_remap_ptr = 'b';
871 }, "remapped MTE memory sends SIGKILL when accessed with canonical tag");
872 expect_sigkill(^{
873 *incorrectly_tagged_remap_ptr = 'b';
874 }, "remapped MTE memory sends SIGKILL when accessed with incorrect tag");
875 expect_normal_exit(^{
876 *tagged_remap_ptr = 'b';
877 }, "remapped MTE memory can be accessed with correct tag");
878
879 if (!own_memory) {
880 kr = mach_port_deallocate(mach_task_self(), port);
881 T_QUIET; T_ASSERT_MACH_SUCCESS(kr, "deallocate parent port");
882 }
883 kr = mach_vm_deallocate(mach_task_self(), remap_addr, size);
884 T_QUIET; T_ASSERT_MACH_SUCCESS(kr, "deallocate remapped memory");
885 kr = mach_vm_deallocate(mach_task_self(), untagged_addr, size);
886 T_QUIET; T_ASSERT_MACH_SUCCESS(kr, "deallocate original memory from child");
887 }, "remap tagged memory");
888 kern_return_t kr = mach_vm_deallocate(mach_task_self(), untagged_addr, size);
889 T_QUIET; T_ASSERT_MACH_SUCCESS(kr, "deallocate original memory");
890 }
891
892 T_DECL(mte_vm_map_remap_self,
893 "mach_vm_remap_new() on a tagged memory of the same process",
894 T_META_REQUIRES_SYSCTL_EQ("hw.optional.arm.FEAT_MTE4", 1),
895 XNU_T_META_SOC_SPECIFIC,
896 T_META_ENABLED(__arm64__))
897 {
898 do_remap_test(true);
899 }
900
901 T_DECL(mte_vm_map_remap_other,
902 "mach_vm_remap_new() on a tagged memory of a different process",
903 T_META_REQUIRES_SYSCTL_EQ("hw.optional.arm.FEAT_MTE4", 1),
904 XNU_T_META_SOC_SPECIFIC,
905 T_META_ENABLED(__arm64__))
906 {
907 do_remap_test(false);
908 }
909
910 #endif /* __arm64__ */
911
912 T_DECL(vm_allocate_zero_tags,
913 "Ensure tags are zeroed when tagged memory is allocated from userspace",
914 T_META_REQUIRES_SYSCTL_EQ("hw.optional.arm.FEAT_MTE2", 1),
915 XNU_T_META_SOC_SPECIFIC)
916 {
917 #if !__arm64__
918 T_SKIP("Running on non-arm64 target, skipping...");
919 #else /* !__arm64__ */
920 /*
921 * Do a bunch of allocations and check that the returned tags are zeroed.
922 * We do NUM_ALLOCATIONS_PER_ITERATION allocations, check the tags,
923 * deallocate them, and then do it again for a total of NUM_ITERATIONS
924 * iterations.
925 * NUM_ALLOCATIONS_PER_ITERATION is equal to the array bound.
926 */
927 vm_address_t addresses[1000];
928 const unsigned int NUM_ALLOCATIONS_PER_ITERATION = sizeof(addresses) / sizeof(addresses[0]);
929 const unsigned int NUM_ITERATIONS = 3;
930
931 kern_return_t kr;
932 for (size_t i = 0; i < NUM_ITERATIONS; i++) {
933 unsigned int failures = 0;
934 for (size_t j = 0; j < NUM_ALLOCATIONS_PER_ITERATION; j++) {
935 kr = vm_allocate(mach_task_self(), &addresses[j], MTE_GRANULE_SIZE, VM_FLAGS_ANYWHERE | VM_FLAGS_MTE);
936 T_QUIET; T_ASSERT_MACH_SUCCESS(kr, "allocate tagged memory (%zu, %zu)", i, j);
937
938 /*
939 * This is the actual test - we get the correctly tagged pointer and
940 * verify that it is zero.
941 */
942 char *tagged_ptr = __arm_mte_get_tag((char*) addresses[j]);
943 unsigned int orig_tag = extract_mte_tag(tagged_ptr);
944 T_QUIET; T_EXPECT_EQ(orig_tag, 0, "vm_allocate returns memory with zeroed tags (%zu, %zu)", i, j);
945 failures += (orig_tag != 0);
946
947 /* Assign an arbitrary nonzero tag and commit it to memory */
948 tagged_ptr = __arm_mte_create_random_tag(tagged_ptr, 1);
949 __arm_mte_set_tag(tagged_ptr);
950
951 /* Fail early if a zero tag was somehow assigned */
952 unsigned int new_tag = extract_mte_tag(tagged_ptr);
953 T_QUIET; T_ASSERT_NE(new_tag, 0, "random tag is nonzero (%zu, %zu)", i, j);
954 }
955
956 for (size_t j = 0; j < NUM_ALLOCATIONS_PER_ITERATION; j++) {
957 kr = vm_deallocate(mach_task_self(), addresses[j], MTE_GRANULE_SIZE);
958 T_QUIET; T_ASSERT_MACH_SUCCESS(kr, "deallocate tagged memory (%zu, %zu)", i, j);
959 }
960 /* Aggregate results per iteration to avoid too much noise */
961 T_EXPECT_EQ(failures, 0, "Iteration %zu success", i);
962 }
963 #endif /* !__arm64__ */
964 }
965
966 /*
967 * Policy (MTE_VMSEC_13): VM performed range-checks must be done with
968 * canonicalized pointers, regardless of whether MTE is enabled
969 *
970 * Note that this specifically tests vm_map_copyin, vm_map_copy_overwrite,
971 * since those kernel functions are intended to take tagged pointers.
972 */
973 T_DECL(mte_copy_range_checks,
974 "Test that VM range checks operate on canonicalized pointers",
975 T_META_REQUIRES_SYSCTL_EQ("hw.optional.arm.FEAT_MTE2", 1),
976 XNU_T_META_SOC_SPECIFIC)
977 {
978 #if !__arm64__
979 T_SKIP("Running on non-arm64 target, skipping...");
980 #else /* !__arm64__ */
981 vm_address_t tagged_addr, incorrectly_tagged_addr;
982 /*
983 * Test setup
984 */
985 const mach_vm_size_t alloc_size = PAGE_SIZE;
986 tagged_addr = allocate_and_tag_range(alloc_size, 1);
987 incorrectly_tagged_addr = (tagged_addr & ~MTE_TAG_MASK) | (2LLU << MTE_TAG_SHIFT);
988
989 /*
990 * mach_vm_copyin test:
991 * If mach_vm_copyin canonicalizes the tagged pointer for its range checks
992 * like it should, the range check will succeed and the actual "copy-in"
993 * operation will be allowed to go through. This will result in a tag check
994 * fault and the process being killed since the tag is incorrect.
995 *
996 * If, erroneously, the range check is done on tagged pointers, we expect
997 * to see a failure since the "incorrect" tag is larger than the "correct"
998 * one so it would be treated as out-of-bounds for the map.
999 */
1000
1001 expect_sigkill(^{
1002 pointer_t read_address;
1003 mach_msg_type_number_t read_size;
1004 kern_return_t kr = mach_vm_read(mach_task_self(), incorrectly_tagged_addr,
1005 alloc_size, &read_address, &read_size);
1006 T_LOG("SIGKILL not received, kr was %d", kr);
1007 }, "mach_vm_read with incorrectly tagged pointer should cause a tag check fault");
1008
1009 /*
1010 * mach_vm_copy_overwrite test:
1011 * Essentially the same logic using mach_vm_write instead of mach_vm_read.
1012 * To be able to do a vm_map_write, we need to first set up a vm_map_copy_t,
1013 * which we can get from a correctly-executed vm_map_read.
1014 */
1015 T_SETUPBEGIN;
1016 pointer_t copy_address;
1017 mach_msg_type_number_t copy_size;
1018 kern_return_t kr = mach_vm_read(mach_task_self(), tagged_addr,
1019 alloc_size, ©_address, ©_size);
1020 T_ASSERT_MACH_SUCCESS(kr, "set up vm_map_copy_t for mach_vm_write test");
1021 T_SETUPEND;
1022 expect_sigkill(^{
1023 kern_return_t kr2 = mach_vm_write(mach_task_self(), incorrectly_tagged_addr,
1024 copy_address, copy_size);
1025 T_LOG("SIGKILL not received, kr was %d", kr2);
1026 }, "mach_vm_write with incorrectly tagged pointer should cause a tag check fault");
1027 #endif /* !__arm64__ */
1028 }
1029
1030 /*
1031 * Policy (MTE_VMSEC_14): VM performed range math must be done using canonical
1032 * pointers, regardless of whether MTE is enabled.
1033 *
1034 * Note that this specifically tests vm_map_copyin, vm_map_copy_overwrite,
1035 * since those kernel functions are intended to take tagged pointers.
1036 */
1037 T_DECL(mte_copy_range_math,
1038 "Test that pointer values are not canonicalized after range math",
1039 T_META_REQUIRES_SYSCTL_EQ("hw.optional.arm.FEAT_MTE2", 1),
1040 XNU_T_META_SOC_SPECIFIC)
1041 {
1042 #if !__arm64__
1043 T_SKIP("Running on non-arm64 target, skipping...");
1044 #else /* !__arm64__ */
1045 vm_address_t tagged_addr;
1046 kern_return_t kr;
1047
1048 /*
1049 * Test setup
1050 */
1051 const mach_vm_size_t alloc_size = MTE_GRANULE_SIZE;
1052 tagged_addr = allocate_and_tag_range(alloc_size, TAG_RANDOM);
1053
1054 vm_offset_t read_address;
1055 mach_msg_type_number_t read_size;
1056 mach_vm_size_t malformed_size;
1057
1058 /*
1059 * A size which extends into the MTE tag bits is too large to fit in
1060 * memory and should be rejected. If range math is operating on tagged
1061 * pointers (and the tag bits get stripped later), then this would
1062 * be accepted.
1063 */
1064 // Test vm_map_copyin using mach_vm_read
1065 malformed_size = (mach_vm_size_t) alloc_size | (7LLU << MTE_TAG_SHIFT);
1066 kr = mach_vm_read(mach_task_self(), tagged_addr, malformed_size,
1067 &read_address, &read_size);
1068 T_EXPECT_MACH_ERROR_(kr, KERN_INVALID_ARGUMENT, "mach_vm_read should reject size which extends into tag bits");
1069
1070 /*
1071 * Cannot test vm_map_copy_overwrite from userspace. The only entry point
1072 * that hits this function without first hitting mach_vm_read is
1073 * mach_vm_write, which takes its size as a 32-bit mach_msg_type_number_t.
1074 */
1075 #endif /* !__arm64__ */
1076 }
1077
1078 /*
1079 * Policy (MTE_VMSEC_16): if the parameter/target of a VM API is a range of
1080 * memory, VM APIs must ensure that the address is not tagged
1081 *
1082 * Corollary: to ease adoption in cases in which pointers obtained from
1083 * the memory allocator are directly passed to some of these functions,
1084 * we implement stripping at the kernel API entrypoint for APIs that do
1085 * not affect the VM state or that are safe and common enough to strip.
1086 * This helps also clearing/making deterministic
1087 * cases where addresses were passed along the VM subsystem just waiting
1088 * to eventually be rejected.
1089 *
1090 * note: this does not apply to APIs which lead to vm_map_copy{in,out}, since
1091 * these need tags to be able to read tagged memory.
1092 */
1093 T_DECL(mte_vm_reject_tagged_pointers,
1094 "Test that most VM APIs reject tagged pointers",
1095 T_META_REQUIRES_SYSCTL_EQ("hw.optional.arm.FEAT_MTE2", 1),
1096 XNU_T_META_SOC_SPECIFIC,
1097 T_META_ASROOT(true) /* to be able to get host_priv port for mach_vm_wire */)
1098 {
1099 #if !__arm64__
1100 T_SKIP("Running on non-arm64 target, skipping...");
1101 #else /* !__arm64__ */
1102 vm_address_t untagged_addr, tagged_addr, tagged_addr_mprotect;
1103 void *untagged_ptr, *tagged_ptr, *tagged_ptr_mprotect;
1104 kern_return_t kr;
1105 int ret;
1106
1107 /*
1108 * Test setup
1109 */
1110 const size_t alloc_size = PAGE_SIZE;
1111 tagged_addr = allocate_and_tag_range(alloc_size, TAG_RANDOM);
1112 tagged_addr_mprotect = allocate_and_tag_range(alloc_size, TAG_RANDOM);
1113 untagged_addr = tagged_addr & ~MTE_TAG_MASK;
1114 untagged_ptr = (void*) untagged_addr;
1115 tagged_ptr = (void*) tagged_addr;
1116 tagged_ptr_mprotect = (void *)tagged_addr_mprotect;
1117
1118 T_QUIET; T_ASSERT_NE(tagged_addr & MTE_TAG_MASK, 0ULL, "validate tagged_addr");
1119 T_QUIET; T_ASSERT_EQ(untagged_addr & MTE_TAG_MASK, 0ULL, "validate untagged_addr");
1120
1121 __block struct vm_region_submap_info_64 region_info;
1122 void (^get_region_info)(void) = ^{
1123 vm_address_t address = untagged_addr;
1124 unsigned int depth = 1;
1125 vm_size_t size;
1126 mach_msg_type_number_t count = VM_REGION_SUBMAP_INFO_COUNT_64;
1127 kern_return_t region_kr = vm_region_recurse_64(mach_task_self(), &address, &size,
1128 &depth, (vm_region_info_t) ®ion_info, &count);
1129 T_QUIET; T_ASSERT_MACH_SUCCESS(region_kr, "get allocation region info");
1130 };
1131
1132 /*
1133 * Test various APIs with tagged pointers
1134 */
1135 /* mprotect, mach_vm_protect are common enough, we strip implicitly. */
1136 ret = mprotect(tagged_ptr_mprotect, alloc_size, PROT_NONE);
1137 T_EXPECT_POSIX_SUCCESS(ret, "mprotect");
1138 kr = mach_vm_protect(mach_task_self(), tagged_addr_mprotect, alloc_size, false, PROT_NONE);
1139 T_EXPECT_MACH_SUCCESS(kr, "mach_vm_protect");
1140
1141 /*
1142 * mincore: SUCCESS
1143 */
1144 char vec[100] = {0};
1145 T_QUIET; T_ASSERT_LE(alloc_size, sizeof(vec) * PAGE_SIZE, "vec is large enough to fit mincore result");
1146 ret = mincore(tagged_ptr, alloc_size, vec);
1147 T_EXPECT_POSIX_SUCCESS(ret, "mincore: return value");
1148
1149 /* msync, mach_vm_msync */
1150 ret = msync(tagged_ptr, alloc_size, MS_SYNC);
1151 T_EXPECT_POSIX_SUCCESS(ret, "msync");
1152 kr = mach_vm_msync(mach_task_self(), tagged_addr, alloc_size, VM_SYNC_SYNCHRONOUS | VM_SYNC_CONTIGUOUS);
1153 T_EXPECT_MACH_SUCCESS(kr, "mach_vm_msync");
1154
1155 /* madvise, mach_vm_behavior_set strip tagged addresses */
1156 ret = madvise(tagged_ptr, alloc_size, MADV_NORMAL);
1157 T_EXPECT_POSIX_SUCCESS(ret, "madvise");
1158 kr = mach_vm_behavior_set(mach_task_self(), tagged_addr, alloc_size,
1159 VM_BEHAVIOR_DEFAULT);
1160 T_EXPECT_MACH_SUCCESS(kr, "mach_vm_behavior_set");
1161
1162 /*
1163 * minherit, mach_vm_inherit:
1164 * mach_vm_inherit would just silently succeed and do nothing if the range was tagged, so
1165 * we strip addresses to have consistent behavior.
1166 */
1167 const vm_inherit_t NEW_INHERIT = VM_INHERIT_NONE;
1168 ret = minherit(tagged_ptr, alloc_size, NEW_INHERIT);
1169 T_EXPECT_POSIX_SUCCESS(ret, "minherit");
1170 kr = mach_vm_inherit(mach_task_self(), tagged_addr, alloc_size, NEW_INHERIT);
1171 T_EXPECT_MACH_SUCCESS(kr, "mach_vm_inherit");
1172
1173 /*
1174 * mlock, mach_vm_wire(prot != VM_PROT_NONE):
1175 * Allow implicitly stripping to avoid no-op success that might confuse third parties.
1176 */
1177 mach_port_t host_priv = HOST_PRIV_NULL;
1178 kr = host_get_host_priv_port(mach_host_self(), &host_priv); \
1179 T_QUIET; T_ASSERT_MACH_SUCCESS(kr, "get host_priv port");
1180
1181 ret = mlock(tagged_ptr, alloc_size);
1182 T_EXPECT_POSIX_SUCCESS(ret, "mlock");
1183 get_region_info();
1184 T_EXPECT_EQ(region_info.user_wired_count, (unsigned short) 1, "mlock on tagged pointer should wire memory");
1185 ret = munlock(tagged_ptr, alloc_size);
1186 T_EXPECT_POSIX_SUCCESS(ret, "munlock");
1187 get_region_info();
1188 T_EXPECT_EQ(region_info.user_wired_count, (unsigned short) 0, "munlock on tagged pointer should unwire memory");
1189
1190 kr = mach_vm_wire(host_priv, mach_task_self(), tagged_addr,
1191 alloc_size, VM_PROT_DEFAULT);
1192 T_EXPECT_MACH_SUCCESS(kr, "mach_vm_wire (wire)");
1193 get_region_info();
1194 T_EXPECT_EQ(region_info.user_wired_count, (unsigned short) 1, "mach_vm_wire on tagged address should wire memory");
1195 ret = munlock(tagged_ptr, alloc_size);
1196 T_EXPECT_POSIX_SUCCESS(ret, "munlock");
1197
1198 /* List of flags used to test vm_allocate, vm_map and vm_remap */
1199 const int ALLOCATE_FLAGS[] = {
1200 VM_FLAGS_FIXED | VM_FLAGS_OVERWRITE,
1201 VM_FLAGS_FIXED | VM_FLAGS_OVERWRITE | VM_FLAGS_MTE,
1202 VM_FLAGS_ANYWHERE,
1203 VM_FLAGS_ANYWHERE | VM_FLAGS_MTE
1204 };
1205 const size_t NUM_ALLOCATE_FLAGS = sizeof(ALLOCATE_FLAGS) / sizeof(*ALLOCATE_FLAGS);
1206
1207 /* vm_allocate tests: */
1208 for (size_t i = 0; i < NUM_ALLOCATE_FLAGS; i++) {
1209 mach_vm_address_t new_addr = tagged_addr;
1210 kr = mach_vm_allocate(mach_task_self(), &new_addr, alloc_size, ALLOCATE_FLAGS[i]);
1211 if (ALLOCATE_FLAGS[i] & VM_FLAGS_ANYWHERE) {
1212 T_EXPECT_MACH_SUCCESS(kr, "mach_vm_allocate %zu (%#x)", i, ALLOCATE_FLAGS[i]);
1213 T_QUIET; T_EXPECT_EQ(new_addr & MTE_TAG_MASK, 0ull, "mach_vm_allocate should return untagged pointer");
1214 T_QUIET; T_EXPECT_NE((vm_address_t) new_addr, untagged_addr, "allocate anywhere should return a new range");
1215
1216 /* clean up new allocation */
1217 if (kr == KERN_SUCCESS) {
1218 kr = mach_vm_deallocate(mach_task_self(), new_addr, alloc_size);
1219 T_QUIET; T_ASSERT_MACH_SUCCESS(kr, "cleanup mach_vm_map");
1220 }
1221 } else {
1222 T_EXPECT_MACH_ERROR(kr, KERN_INVALID_ADDRESS, "mach_vm_allocate %zu (%#x)", i, ALLOCATE_FLAGS[i]);
1223 }
1224 }
1225
1226 /* mach_vm_machine_attribute: allow tagged addresses */
1227 vm_machine_attribute_val_t machine_attribute_val = MATTR_VAL_CACHE_FLUSH;
1228 kr = mach_vm_machine_attribute(mach_task_self(), tagged_addr, alloc_size,
1229 MATTR_CACHE, &machine_attribute_val);
1230 T_EXPECT_MACH_SUCCESS(kr, "mach_vm_machine_attribute");
1231
1232 /* mach_make_memory_entry_64: DO NOT allow tagged addresses */
1233 mach_port_t object_handle;
1234 memory_object_size_t object_size = alloc_size;
1235 kr = mach_make_memory_entry_64(mach_task_self(), &object_size, tagged_addr,
1236 VM_PROT_DEFAULT, &object_handle, MACH_PORT_NULL);
1237 T_EXPECT_MACH_ERROR(kr, KERN_INVALID_ADDRESS, "mach_make_memory_entry_64");
1238
1239 /* mach_vm_map: DO NOT allow tagged addresses */
1240 /* setup: get a memory entry to map in */
1241 kr = mach_make_memory_entry_64(mach_task_self(), &object_size, untagged_addr,
1242 VM_PROT_DEFAULT | MAP_MEM_NAMED_CREATE, &object_handle, MACH_PORT_NULL);
1243 T_ASSERT_MACH_SUCCESS(kr, "create memory entry for mach_vm_map");
1244
1245 for (size_t i = 0; i < NUM_ALLOCATE_FLAGS; i++) {
1246 mach_vm_address_t new_addr = tagged_addr;
1247 kr = mach_vm_map(mach_task_self(), &new_addr, alloc_size, /* mask = */ 0,
1248 ALLOCATE_FLAGS[i], object_handle, /* offset = */ 0, /* copy = */ true,
1249 VM_PROT_DEFAULT, VM_PROT_DEFAULT, VM_INHERIT_DEFAULT);
1250 if (ALLOCATE_FLAGS[i] & VM_FLAGS_ANYWHERE) {
1251 /*
1252 * VM_FLAGS_ANYWHERE uses the provided address as a location to start
1253 * searching from. Since a tagged address is outside the map bounds,
1254 * it won't be able to find any space for the allocation.
1255 */
1256 T_EXPECT_MACH_ERROR(kr, KERN_NO_SPACE, "mach_vm_map %zu (%#x)", i, ALLOCATE_FLAGS[i]);
1257 } else {
1258 T_EXPECT_MACH_ERROR(kr, KERN_INVALID_ADDRESS, "mach_vm_map %zu (%#x)", i, ALLOCATE_FLAGS[i]);
1259 }
1260 }
1261
1262 /* clean up memory entry object handle */
1263 kr = mach_port_deallocate(mach_task_self(), object_handle);
1264 T_QUIET; T_ASSERT_MACH_SUCCESS(kr, "mach_vm_map tests: clean up memory entry object handle");
1265
1266 /* mach_vm_purgable_control */
1267 int purgable_state;
1268 kr = mach_vm_purgable_control(mach_task_self(), tagged_addr, VM_PURGABLE_GET_STATE, &purgable_state);
1269 T_EXPECT_MACH_ERROR(kr, KERN_INVALID_ADDRESS, "mach_vm_purgable_control");
1270
1271 /* mach_vm_region: reject tagged addresses */
1272 mach_vm_address_t region_addr = tagged_addr;
1273 mach_vm_size_t region_size;
1274 vm_region_basic_info_data_64_t region_info_64;
1275 mach_msg_type_number_t region_info_cnt = VM_REGION_BASIC_INFO_COUNT_64;
1276 mach_port_t unused;
1277
1278 kr = mach_vm_region(mach_task_self(), ®ion_addr, ®ion_size,
1279 VM_REGION_BASIC_INFO_64, (vm_region_info_t) ®ion_info_64,
1280 ®ion_info_cnt, &unused);
1281 T_EXPECT_MACH_ERROR(kr, KERN_INVALID_ADDRESS, "mach_vm_region");
1282
1283 /* mach_vm_remap_new */
1284 mach_vm_address_t untagged_addr2, tagged_addr2;
1285 tagged_addr2 = allocate_and_tag_range(alloc_size, TAG_RANDOM);
1286 untagged_addr2 = tagged_addr2 & ~MTE_TAG_MASK;
1287
1288 /* Test each flag value twice, once with source tagged and once with destination tagged */
1289 for (size_t i = 0; i < 2 * NUM_ALLOCATE_FLAGS; i++) {
1290 int flags = ALLOCATE_FLAGS[i % NUM_ALLOCATE_FLAGS];
1291 bool source_tagged = i < NUM_ALLOCATE_FLAGS;
1292 char *msg = source_tagged ? "source tagged" : "dest tagged";
1293 mach_vm_address_t src_addr = source_tagged ? tagged_addr : untagged_addr;
1294 mach_vm_address_t dest_addr = source_tagged ? untagged_addr2 : tagged_addr2;
1295
1296 vm_prot_t cur_prot = VM_PROT_DEFAULT, max_prot = VM_PROT_DEFAULT;
1297 kr = mach_vm_remap_new(mach_task_self(), &dest_addr, alloc_size, /* mask = */ 0,
1298 flags, mach_task_self(), src_addr, true, &cur_prot, &max_prot,
1299 VM_INHERIT_DEFAULT);
1300
1301 if (flags & VM_FLAGS_MTE) {
1302 /* VM_FLAGS_USER_REMAP does not include VM_FLAGS_MTE */
1303 T_EXPECT_MACH_ERROR(kr, KERN_INVALID_ARGUMENT, "mach_vm_remap_new %zu (%s, %#x)", i, msg, flags);
1304 } else if (!source_tagged && flags & VM_FLAGS_ANYWHERE) {
1305 /*
1306 * In this case, we pass vm_map_remap_extract since the source
1307 * address is untagged. When we try to find a space to insert it
1308 * into the map, we fail since VM_FLAGS_ANYWHERE uses the destination
1309 * passed in as a location to start searching from.
1310 */
1311 T_EXPECT_MACH_ERROR(kr, KERN_NO_SPACE, "mach_vm_remap_new %zu (%s, %#x)", i, msg, flags);
1312 } else {
1313 T_EXPECT_MACH_ERROR(kr, KERN_INVALID_ADDRESS, "mach_vm_remap_new %zu (%s, %#x)", i, msg, flags);
1314 }
1315
1316 if (kr == KERN_SUCCESS && (flags & VM_FLAGS_ANYWHERE)) {
1317 /* clean up the new allocation if we mistakenly suceeded */
1318 kr = mach_vm_deallocate(mach_task_self(), dest_addr, alloc_size);
1319 T_QUIET; T_ASSERT_MACH_SUCCESS(kr, "cleanup mach_vm_remap_new %zu (%s, %#x)", i, msg, flags);
1320 }
1321 }
1322
1323 /* clean up our second allocation */
1324 T_SETUPBEGIN;
1325 kr = vm_deallocate(mach_task_self(), untagged_addr2, alloc_size);
1326 T_QUIET; T_EXPECT_MACH_SUCCESS(kr, "clean up allocation for mach_vm_remap_new tests");
1327 T_SETUPEND;
1328
1329 /* vm_deallocate: vm_allocate() will return a canonical address, so we mandate a canonical address here */
1330 T_SETUPBEGIN;
1331 kr = vm_deallocate(mach_task_self(), tagged_addr, alloc_size);
1332 T_EXPECT_MACH_ERROR(kr, KERN_INVALID_ARGUMENT, "vm_deallocate denies a non-canonical addresses");
1333 T_SETUPEND;
1334
1335 /* test cleanup */
1336 T_SETUPBEGIN;
1337 kr = vm_deallocate(mach_task_self(), untagged_addr, alloc_size);
1338 T_ASSERT_MACH_SUCCESS(kr, "test region cleanup");
1339 T_SETUPEND;
1340 #endif /* !__arm64__ */
1341 }
1342
1343 T_DECL(mte_tagged_page_relocation,
1344 "Test that VM copies tags on page relocation for tagged memory",
1345 T_META_REQUIRES_SYSCTL_EQ("hw.optional.arm.FEAT_MTE4", 1),
1346 T_META_ASROOT(true),
1347 XNU_T_META_SOC_SPECIFIC,
1348 T_META_ENABLED(__arm64__))
1349 {
1350 #if __arm64__
1351 T_SETUPBEGIN;
1352 mach_vm_address_t addr = 0;
1353 kern_return_t kr = mach_vm_allocate(
1354 mach_task_self(),
1355 &addr,
1356 PAGE_SIZE,
1357 VM_FLAGS_ANYWHERE | VM_FLAGS_MTE
1358 );
1359 T_ASSERT_MACH_SUCCESS(kr,
1360 "allocate 32 bytes of tagged memory at 0x%llx", addr);
1361
1362 /* Verify originally assigned tags are zero */
1363 for (uint i = 0; i < PAGE_SIZE / MTE_GRANULE_SIZE; ++i) {
1364 char *untagged_ptr = (char *)((uintptr_t)addr + i * MTE_GRANULE_SIZE);
1365 char *orig_tagged_ptr = __arm_mte_get_tag(untagged_ptr);
1366 unsigned int orig_tag = extract_mte_tag(orig_tagged_ptr);
1367 T_QUIET; T_ASSERT_EQ_UINT(orig_tag, 0U, "originally assigned tag is zero");
1368 }
1369
1370 /*
1371 * Tag the first 16 bytes with non-zero tag, and
1372 * leave the second 16 bytes as is
1373 */
1374 char *untagged_ptr = (char *)addr;
1375 char *orig_tagged_ptr = __arm_mte_get_tag(untagged_ptr);
1376 uint64_t mask = __arm_mte_exclude_tag(orig_tagged_ptr, 0);
1377 T_EXPECT_EQ_LLONG(mask, (1LL << 0), "zero tag is excluded");
1378
1379 char *random_tagged_ptr = __arm_mte_create_random_tag(untagged_ptr, mask);
1380 T_QUIET; T_EXPECT_NE_PTR(orig_tagged_ptr, random_tagged_ptr,
1381 "random tag was not taken from excluded tag set");
1382
1383 ptrdiff_t diff = __arm_mte_ptrdiff(untagged_ptr, random_tagged_ptr);
1384 T_QUIET; T_EXPECT_EQ_ULONG(diff, (ptrdiff_t)0, "untagged %p and tagged %p have identical address bits",
1385 untagged_ptr, random_tagged_ptr);
1386
1387 /* Time to make things real, commit the tag to memory */
1388 __arm_mte_set_tag(random_tagged_ptr);
1389
1390 /* Ensure that we can read back the tag */
1391 char *read_back = __arm_mte_get_tag(untagged_ptr);
1392 T_EXPECT_EQ_PTR(read_back, random_tagged_ptr, "tag was committed to memory correctly");
1393
1394 T_LOG("tagged pointer: %p", random_tagged_ptr);
1395 random_tagged_ptr[0] = 'a';
1396 untagged_ptr[MTE_GRANULE_SIZE] = 'b';
1397 T_SETUPEND;
1398
1399 /*
1400 * Relocate the page.
1401 * The kernel will also write 'b' and 'c' to the memory.
1402 */
1403 int64_t ret = run_sysctl_test("vm_page_relocate", (int64_t)random_tagged_ptr);
1404 T_EXPECT_EQ_LLONG(ret, 1LL, "sysctl: relocate page");
1405
1406 T_EXPECT_EQ_CHAR(random_tagged_ptr[0], 'b',
1407 "reading from tagged ptr after relocation");
1408 T_EXPECT_EQ_CHAR(untagged_ptr[MTE_GRANULE_SIZE], 'c',
1409 "reading from untagged ptr after relocation");
1410 #endif /* __arm64__ */
1411 }
1412
1413 T_HELPER_DECL(mte_tag_violate, "child process to trigger an MTE violation")
1414 {
1415 static const size_t ALLOC_SIZE = MTE_GRANULE_SIZE * 2;
1416
1417 vm_address_t address = 0;
1418 kern_return_t kr = vm_allocate(mach_task_self(), &address, ALLOC_SIZE, VM_FLAGS_ANYWHERE | VM_FLAGS_MTE);
1419 T_ASSERT_MACH_SUCCESS(kr, "allocate tagged memory");
1420 char *untagged_ptr = (char *) address;
1421
1422 char *orig_tagged_ptr = __arm_mte_get_tag(untagged_ptr);
1423 unsigned int orig_tag = extract_mte_tag(orig_tagged_ptr);
1424 T_ASSERT_EQ_UINT(orig_tag, 0U, "originally assigned tag is zero");
1425
1426 uint64_t mask = __arm_mte_exclude_tag(orig_tagged_ptr, 0);
1427 T_EXPECT_EQ_LLONG(mask, (1LL << 0), "zero tag is excluded");
1428
1429 char *random_tagged_ptr = NULL;
1430 for (unsigned int i = 0; i < NUM_MTE_TAGS * 4; i++) {
1431 random_tagged_ptr = __arm_mte_create_random_tag(untagged_ptr, mask);
1432 T_QUIET; T_EXPECT_NE_PTR(orig_tagged_ptr, random_tagged_ptr,
1433 "random tag was not taken from excluded tag set");
1434
1435 ptrdiff_t diff = __arm_mte_ptrdiff(untagged_ptr, random_tagged_ptr);
1436 T_QUIET; T_EXPECT_EQ_ULONG(diff, (ptrdiff_t)0, "untagged %p and tagged %p have identical address bits",
1437 untagged_ptr, random_tagged_ptr);
1438 }
1439
1440 __arm_mte_set_tag(random_tagged_ptr);
1441
1442 char *read_back = __arm_mte_get_tag(untagged_ptr);
1443 T_EXPECT_EQ_PTR(read_back, random_tagged_ptr, "tag was committed to memory correctly");
1444
1445 random_tagged_ptr[0] = 't';
1446 random_tagged_ptr[1] = 'e';
1447 random_tagged_ptr[2] = 's';
1448 random_tagged_ptr[3] = 't';
1449 T_EXPECT_EQ_STR(random_tagged_ptr, "test", "read/write from tagged memory");
1450
1451 void *next_granule_ptr = orig_tagged_ptr + MTE_GRANULE_SIZE;
1452 unsigned int next_granule_tag = extract_mte_tag(next_granule_ptr);
1453 T_QUIET; T_ASSERT_EQ_UINT(next_granule_tag, 0U,
1454 "next MTE granule still has its originally assigned tag");
1455
1456 T_LOG("attempting out-of-bounds access to tagged memory");
1457 random_tagged_ptr[MTE_GRANULE_SIZE] = '!';
1458 T_LOG("bypass: survived OOB access");
1459
1460 __arm_mte_set_tag(orig_tagged_ptr);
1461 __arm_mte_set_tag(orig_tagged_ptr + MTE_GRANULE_SIZE);
1462 vm_deallocate(mach_task_self(), address, ALLOC_SIZE);
1463 exit(0);
1464 }
1465
1466 T_HELPER_DECL(mte_copyio_bypass_helper, "child process to test copyio in MTE tag check bypass mode")
1467 {
1468 run_mte_copyio_tests(false);
1469 }
1470
1471 static void
run_helper_with_sec_bypass(char * helper_name)1472 run_helper_with_sec_bypass(char *helper_name)
1473 {
1474 char path[PATH_MAX];
1475 uint32_t path_size = sizeof(path);
1476 T_ASSERT_POSIX_ZERO(_NSGetExecutablePath(path, &path_size), "_NSGetExecutablePath");
1477 char *args[] = { path, "-n", helper_name, NULL };
1478
1479 pid_t child_pid = 0;
1480 posix_spawnattr_t attr;
1481 errno_t ret = posix_spawnattr_init(&attr);
1482 T_ASSERT_POSIX_ZERO(ret, "posix_spawnattr_init");
1483
1484 ret = posix_spawnattr_set_use_sec_transition_shims_np(&attr, POSIX_SPAWN_SECFLAG_EXPLICIT_ENABLE | POSIX_SPAWN_SECFLAG_EXPLICIT_CHECK_BYPASS);
1485 T_ASSERT_POSIX_ZERO(ret, "posix_spawnattr_set_use_sec_transition_shims_np");
1486
1487 ret = posix_spawn(&child_pid, path, NULL, &attr, args, NULL);
1488 T_ASSERT_POSIX_ZERO(ret, "posix_spawn");
1489 T_ASSERT_NE(child_pid, 0, "posix_spawn");
1490
1491 ret = posix_spawnattr_destroy(&attr);
1492 T_ASSERT_POSIX_ZERO(ret, "posix_spawnattr_destroy");
1493
1494 int status = 0;
1495 T_ASSERT_POSIX_SUCCESS(waitpid(child_pid, &status, 0), "waitpid");
1496 T_EXPECT_TRUE(WIFEXITED(status), "exited successfully");
1497 T_EXPECT_TRUE(WEXITSTATUS(status) == 0, "exited with status %d", WEXITSTATUS(status));
1498 }
1499
1500 T_DECL(mte_tag_bypass,
1501 "Test MTE2 tag check bypass works with posix_spawnattr",
1502 T_META_ENABLED(TARGET_CPU_ARM64),
1503 T_META_REQUIRES_SYSCTL_EQ("hw.optional.arm.FEAT_MTE4", 1),
1504 XNU_T_META_SOC_SPECIFIC)
1505 {
1506 run_helper_with_sec_bypass("mte_tag_violate");
1507 }
1508
1509 T_DECL(mte_copyio_bypass,
1510 "Test MTE2 tag check bypass with copyio operations",
1511 T_META_ENABLED(TARGET_CPU_ARM64),
1512 T_META_REQUIRES_SYSCTL_EQ("hw.optional.arm.FEAT_MTE4", 1),
1513 XNU_T_META_SOC_SPECIFIC)
1514 {
1515 run_helper_with_sec_bypass("mte_copyio_bypass_helper");
1516 }
1517
1518 #ifdef __arm64__
1519 T_DECL(mte_read_only,
1520 "Verify that setting tags on a read-only mapping results in SIGBUS",
1521 T_META_REQUIRES_SYSCTL_EQ("hw.optional.arm.FEAT_MTE2", 1),
1522 XNU_T_META_SOC_SPECIFIC)
1523 {
1524 uint64_t mask;
1525 T_SETUPBEGIN;
1526 void* untagged_ptr = allocate_tagged_memory(MTE_GRANULE_SIZE, &mask);
1527 void *tagged_ptr = __arm_mte_create_random_tag(untagged_ptr, mask);
1528 T_SETUPEND;
1529
1530 assert_normal_exit(^{
1531 __arm_mte_set_tag(tagged_ptr);
1532 }, "can set tags on writable memory");
1533
1534 int ret = mprotect(untagged_ptr, MTE_GRANULE_SIZE, PROT_READ);
1535 T_ASSERT_POSIX_SUCCESS(ret, "mprotect");
1536
1537 tagged_ptr = __arm_mte_increment_tag(tagged_ptr, 1);
1538
1539 expect_signal(SIGBUS, ^{
1540 __arm_mte_set_tag(tagged_ptr);
1541 }, "set tag on read-only memory");
1542
1543 T_SETUPBEGIN;
1544 kern_return_t kr = vm_deallocate(mach_task_self(), (vm_address_t) untagged_ptr, MTE_GRANULE_SIZE);
1545 T_QUIET; T_EXPECT_MACH_SUCCESS(kr, "clean up tagged allocation");
1546 T_SETUPEND;
1547 }
1548
1549 T_DECL(mte_inherit_share,
1550 "Verify that you can't set VM_INHERIT_SHARE on tagged memory",
1551 T_META_REQUIRES_SYSCTL_EQ("hw.optional.arm.FEAT_MTE2", 1),
1552 XNU_T_META_SOC_SPECIFIC)
1553 {
1554 const mach_vm_size_t ALLOC_SIZE = PAGE_SIZE;
1555 __block kern_return_t kr;
1556
1557 T_SETUPBEGIN;
1558 vm_address_t tagged_addr = allocate_and_tag_range(ALLOC_SIZE, TAG_RANDOM);
1559 vm_address_t untagged_addr = tagged_addr & ~MTE_TAG_MASK;
1560 T_SETUPEND;
1561
1562 expect_sigkill(^{
1563 int ret = minherit((void*) untagged_addr, ALLOC_SIZE, VM_INHERIT_SHARE);
1564 T_LOG("minherit: was not killed and returned %d", ret);
1565 }, "minherit(VM_INHERIT_SHARE) on tagged memory");
1566
1567 expect_sigkill(^{
1568 kr = mach_vm_inherit(mach_task_self(), untagged_addr,
1569 ALLOC_SIZE, VM_INHERIT_SHARE);
1570 T_LOG("mach_vm_inherit: was not killed and returned %d", kr);
1571 }, "mach_vm_inherit(VM_INHERIT_SHARE) on tagged memory");
1572
1573 T_SETUPBEGIN;
1574 kr = vm_deallocate(mach_task_self(), untagged_addr, ALLOC_SIZE);
1575 T_QUIET; T_EXPECT_MACH_SUCCESS(kr, "clean up tagged allocation");
1576 T_SETUPEND;
1577
1578 expect_sigkill(^{
1579 mach_vm_address_t addr = 0;
1580 kr = mach_vm_map(mach_task_self(), &addr, ALLOC_SIZE, /* mask = */ 0,
1581 VM_FLAGS_ANYWHERE | VM_FLAGS_MTE, MACH_PORT_NULL, /* offset = */ 0,
1582 /* copy = */ false, VM_PROT_DEFAULT, VM_PROT_ALL, VM_INHERIT_SHARE);
1583 T_LOG("mach_vm_map: was not killed and returned %d", kr);
1584
1585 T_SETUPBEGIN;
1586 kr = vm_deallocate(mach_task_self(), addr, ALLOC_SIZE);
1587 T_QUIET; T_EXPECT_MACH_SUCCESS(kr, "clean up mach_vm_map allocation");
1588 T_SETUPEND;
1589 }, "mach_vm_map(VM_INHERIT_SHARE) to create new tagged memory");
1590 }
1591
1592 static vm_object_id_t
get_object_id(mach_port_t task,vm_address_t addr)1593 get_object_id(mach_port_t task, vm_address_t addr)
1594 {
1595 unsigned int depth = 1;
1596 vm_size_t size;
1597 struct vm_region_submap_info_64 info;
1598 mach_msg_type_number_t count = VM_REGION_SUBMAP_INFO_COUNT_64;
1599 kern_return_t kr = vm_region_recurse_64(task, &addr, &size, &depth,
1600 (vm_region_info_t) &info, &count);
1601 /*
1602 * I'm not sure why it returns KERN_INVALID_ADDRESS in this case, but this
1603 * can happen if the corpse task goes away. That happens if a jetsam event
1604 * occurs (even on an unrelated process) while the test is running.
1605 */
1606 if (task != mach_task_self() && kr == KERN_INVALID_ADDRESS) {
1607 T_SKIP("corpse port disappeared, bailing...");
1608 }
1609 T_QUIET; T_ASSERT_MACH_SUCCESS(kr, "get_object_id: vm_region_recurse_64");
1610 return info.object_id_full;
1611 }
1612
1613 T_DECL(mte_corpse_fork,
1614 "Verify that corpse-fork sharing paths work normally on tagged memory",
1615 T_META_REQUIRES_SYSCTL_EQ("hw.optional.arm.FEAT_MTE2", 1),
1616 XNU_T_META_SOC_SPECIFIC,
1617 /* rdar://138528295 (Provide a mechanism to guarantee availability of corpse slots for tests) */
1618 T_META_RUN_CONCURRENTLY(false))
1619 {
1620 /*
1621 * The corpse-fork path shares memory in two additional cases:
1622 * (1) if the entry has INHERIT_NONE, and
1623 * (2) if the memory is "owned" by the process for accounting purposes. This
1624 * essentially means that it is purgeable & volatile.
1625 * We want to ensure that these cases are unaffected by MTE restrictions on
1626 * VM_INHERIT_SHARE.
1627 */
1628 kern_return_t kr;
1629 mach_vm_size_t alloc_size = PAGE_SIZE;
1630 mach_vm_address_t inherit_none_addr, owned_addr, regular_addr;
1631
1632 T_SETUPBEGIN;
1633
1634 /* First up, expand the system's corpse pool size.
1635 * Otherwise, this test sporadically can't secure the corpse slots it needs.
1636 */
1637 int original_total_corpses_allowed;
1638 size_t original_total_corpses_allowed_sizeof = sizeof(original_total_corpses_allowed);
1639 int total_corpses_allowed = 20;
1640 int ret = sysctlbyname("kern.total_corpses_allowed",
1641 &original_total_corpses_allowed, &original_total_corpses_allowed_sizeof,
1642 &total_corpses_allowed, sizeof(total_corpses_allowed));
1643 T_QUIET; T_EXPECT_POSIX_ZERO(ret, "sysctl kern.total_corpses_allowed");
1644
1645 /* set up regular MTE-tagged region */
1646 kr = mach_vm_allocate(mach_task_self(), ®ular_addr, alloc_size,
1647 VM_FLAGS_ANYWHERE | VM_FLAGS_MTE);
1648 T_ASSERT_MACH_SUCCESS(kr, "vm_allocate regular region");
1649
1650 /* set up region for testing INHERIT_NONE */
1651 kr = mach_vm_allocate(mach_task_self(), &inherit_none_addr, alloc_size,
1652 VM_FLAGS_ANYWHERE | VM_FLAGS_MTE);
1653 T_ASSERT_MACH_SUCCESS(kr, "vm_allocate INHERIT_NONE region");
1654
1655 kr = mach_vm_inherit(mach_task_self(), inherit_none_addr, alloc_size,
1656 VM_INHERIT_NONE);
1657 T_ASSERT_MACH_SUCCESS(kr, "vm_inherit(INHERIT_NONE)");
1658
1659 /* set up region for testing "owned" memory */
1660 kr = mach_vm_allocate(mach_task_self(), &owned_addr, alloc_size,
1661 VM_FLAGS_ANYWHERE | VM_FLAGS_MTE | VM_FLAGS_PURGABLE);
1662 T_ASSERT_MACH_SUCCESS(kr, "vm_allocate owned region");
1663
1664 int purgable_state = VM_PURGABLE_VOLATILE;
1665 kr = mach_vm_purgable_control(mach_task_self(), owned_addr, VM_PURGABLE_SET_STATE,
1666 &purgable_state);
1667 T_ASSERT_MACH_SUCCESS(kr, "vm_purgable_control(VM_PURGABLE_VOLATILE)");
1668 T_SETUPEND;
1669
1670 /* Write in some data and tags */
1671 char *regular_ptr = __arm_mte_increment_tag((char*) regular_addr, 1);
1672 char *inherit_none_ptr = __arm_mte_increment_tag((char*) inherit_none_addr, 2);
1673 char *owned_ptr = __arm_mte_increment_tag((char*) owned_addr, 3);
1674 for (size_t i = 0; i < alloc_size; i++) {
1675 if (i % MTE_GRANULE_SIZE == 0) {
1676 __arm_mte_set_tag(®ular_ptr[i]);
1677 __arm_mte_set_tag(&inherit_none_ptr[i]);
1678 __arm_mte_set_tag(&owned_ptr[i]);
1679 }
1680 regular_ptr[i] = 'a';
1681 inherit_none_ptr[i] = 'b';
1682 owned_ptr[i] = 'c';
1683 }
1684 T_LOG("wrote data and tags");
1685
1686 mach_port_t corpse_port;
1687 size_t NUM_RETRIES = 5;
1688 for (size_t i = 0;; i++) {
1689 kr = task_generate_corpse(mach_task_self(), &corpse_port);
1690 if (kr == KERN_RESOURCE_SHORTAGE) {
1691 T_LOG("hit system corpse limit");
1692 if (i == NUM_RETRIES) {
1693 T_SKIP("retried too many times, bailing...");
1694 } else {
1695 /* give ReportCrash some time to finish handling some corpses */
1696 sleep(2);
1697 /* ... then retry */
1698 T_LOG("retrying... (%lu/%lu)", i + 1, NUM_RETRIES);
1699 continue;
1700 }
1701 }
1702 T_ASSERT_MACH_SUCCESS(kr, "task_generate_corpse");
1703 break;
1704 }
1705
1706 /*
1707 * Make sure the "regular" region was not shared.
1708 * Note: in the case of symmetric CoW, the object IDs may match even if
1709 * there is no true sharing happening. However, since we only expect delayed
1710 * CoW or eager copies for MTE objects, this isn't a concern here.
1711 */
1712 vm_object_id_t regular_id = get_object_id(mach_task_self(), regular_addr);
1713 vm_object_id_t regular_corpse_id = get_object_id(corpse_port, regular_addr);
1714 T_EXPECT_NE(regular_id, regular_corpse_id, "regular region was not shared");
1715
1716 /* Make sure the INHERIT_NONE region was shared */
1717 vm_object_id_t inherit_none_id = get_object_id(mach_task_self(), inherit_none_addr);
1718 vm_object_id_t inherit_none_corpse_id = get_object_id(corpse_port, inherit_none_addr);
1719 T_EXPECT_EQ(inherit_none_id, inherit_none_corpse_id, "INHERIT_NONE region was shared");
1720
1721 /* Make sure the owned region was shared */
1722 vm_object_id_t owned_id = get_object_id(mach_task_self(), owned_addr);
1723 vm_object_id_t owned_corpse_id = get_object_id(corpse_port, owned_addr);
1724 T_EXPECT_EQ(owned_id, owned_corpse_id, "owned region was shared");
1725
1726 /* Cleanup */
1727 T_SETUPBEGIN;
1728 kr = mach_vm_deallocate(mach_task_self(), regular_addr, alloc_size);
1729 T_QUIET; T_ASSERT_MACH_SUCCESS(kr, "deallocate regular allocation");
1730 kr = mach_vm_deallocate(mach_task_self(), inherit_none_addr, alloc_size);
1731 T_QUIET; T_ASSERT_MACH_SUCCESS(kr, "deallocate INHERIT_NONE allocation");
1732 kr = mach_vm_deallocate(mach_task_self(), owned_addr, alloc_size);
1733 T_QUIET; T_ASSERT_MACH_SUCCESS(kr, "deallocate owned allocation");
1734 kr = mach_port_deallocate(mach_task_self(), corpse_port);
1735 T_QUIET; T_ASSERT_MACH_SUCCESS(kr, "deallocate corpse port");
1736
1737 /* Reduce the corpse pool size back to its original value */
1738 ret = sysctlbyname("kern.total_corpses_allowed",
1739 NULL, 0,
1740 &original_total_corpses_allowed, sizeof(original_total_corpses_allowed));
1741 T_QUIET; T_EXPECT_POSIX_ZERO(ret, "sysctl kern.total_corpses_allowed");
1742
1743 T_SETUPEND;
1744 }
1745
1746 T_DECL(mte_aio,
1747 "Test MTE asynchronous access faults when the kernel does copyio on behalf of a process",
1748 T_META_REQUIRES_SYSCTL_EQ("hw.optional.arm.FEAT_MTE2", 1),
1749 XNU_T_META_SOC_SPECIFIC,
1750 T_META_ENABLED(false) /* rdar://154801490 */) {
1751 const mach_vm_size_t BUF_SIZE = MTE_GRANULE_SIZE;
1752 uint64_t mask;
1753
1754 T_SETUPBEGIN;
1755 char *buf_untagged = allocate_tagged_memory(BUF_SIZE, &mask);
1756 char *buf_tagged = __arm_mte_create_random_tag(buf_untagged, mask);
1757 __arm_mte_set_tag(buf_tagged);
1758 strncpy(buf_tagged, "ABCDEFG", BUF_SIZE);
1759
1760 char *buf_incorrectly_tagged = __arm_mte_increment_tag(buf_tagged, 1);
1761 int fd = fileno(tmpfile());
1762
1763 T_SETUPEND;
1764
1765 expect_sigkill(^{
1766 struct aiocb aiocb = {
1767 .aio_fildes = fd,
1768 .aio_offset = 0,
1769 .aio_buf = buf_incorrectly_tagged,
1770 .aio_nbytes = strlen(buf_tagged),
1771 };
1772 int ret = aio_write(&aiocb);
1773 T_ASSERT_POSIX_SUCCESS(ret, "aio_write");
1774
1775 /* wait for the kernel to handle our async I/O */
1776 /* we should be killed at some point while this happens */
1777 const struct aiocb *aio_list[1] = { &aiocb };
1778 (void)aio_suspend(aio_list, 1, NULL);
1779
1780 /* we were not killed: */
1781 close(fd);
1782 T_ASSERT_FAIL("aio write with untagged pointer completed successfully");
1783 }, "asynchronous I/O write from tagged buffer with incorrect MTE tags");
1784
1785 char read_buf[BUF_SIZE];
1786 ssize_t bytes_read = read(fd, read_buf, sizeof(read_buf));
1787 T_ASSERT_POSIX_SUCCESS(bytes_read, "read from tmpfile");
1788
1789 T_EXPECT_EQ(bytes_read, 0L, "no bytes sent over tmpfile");
1790
1791 T_SETUPBEGIN;
1792 kern_return_t kr = vm_deallocate(mach_task_self(), (vm_address_t) buf_untagged, BUF_SIZE);
1793 T_ASSERT_MACH_SUCCESS(kr, "deallocate tagged buffer");
1794
1795 close(fd);
1796 T_SETUPEND;
1797 }
1798
1799 T_HELPER_DECL(mte_tag_violate_aio, "child process to trigger an asynchronous MTE violation via AIO") {
1800 const mach_vm_size_t BUF_SIZE = MTE_GRANULE_SIZE;
1801 uint64_t mask;
1802
1803 char *buf_untagged = allocate_tagged_memory(BUF_SIZE, &mask);
1804 char *buf_tagged = __arm_mte_create_random_tag(buf_untagged, mask);
1805 __arm_mte_set_tag(buf_tagged);
1806
1807 strncpy(buf_tagged, "ABCDEFG", BUF_SIZE);
1808 size_t length = strlen(buf_tagged);
1809
1810 char *buf_incorrectly_tagged = __arm_mte_increment_tag(buf_tagged, 1);
1811 int fd = fileno(tmpfile());
1812
1813 struct aiocb aiocb = {
1814 .aio_fildes = fd,
1815 .aio_offset = 0,
1816 .aio_buf = buf_incorrectly_tagged,
1817 .aio_nbytes = length,
1818 };
1819 int ret = aio_write(&aiocb);
1820 T_ASSERT_POSIX_SUCCESS(ret, "aio_write");
1821
1822 /* wait for the kernel to handle our async I/O */
1823 const struct aiocb *aio_list[1] = { &aiocb };
1824 ret = aio_suspend(aio_list, 1, NULL);
1825 T_ASSERT_POSIX_SUCCESS(ret, "aio_suspend");
1826
1827 char read_buf[BUF_SIZE];
1828 ssize_t bytes_read = read(fd, read_buf, sizeof(read_buf));
1829 T_ASSERT_POSIX_SUCCESS(bytes_read, "read from tmpfile");
1830
1831 /* these have to be "may fail" instead of "expect fail" due to rdar://136258500 */
1832 T_MAYFAIL_WITH_RADAR(136300841);
1833 T_EXPECT_EQ(bytes_read, (ssize_t)length, "bytes sent over tmpfile");
1834
1835 for (size_t i = 0; i < length; i++) {
1836 T_MAYFAIL_WITH_RADAR(136300841);
1837 T_EXPECT_EQ(buf_tagged[i], read_buf[i], "character %lu matches", i);
1838 }
1839
1840 kern_return_t kr = vm_deallocate(mach_task_self(), (vm_address_t) buf_untagged, BUF_SIZE);
1841 T_ASSERT_MACH_SUCCESS(kr, "deallocate tagged buffer");
1842
1843 close(fd);
1844 }
1845
1846 T_DECL(mte_aio_tag_bypass,
1847 "Test nonfatal MTE asynchronous access faults with tag check bypass",
1848 T_META_REQUIRES_SYSCTL_EQ("hw.optional.arm.FEAT_MTE2", 1),
1849 XNU_T_META_SOC_SPECIFIC) {
1850 run_helper_with_sec_bypass("mte_tag_violate_aio");
1851 }
1852 #endif /* __arm64__ */
1853
1854 static void
run_iokit_sysctl_test(int vector)1855 run_iokit_sysctl_test(int vector)
1856 {
1857 int ret = sysctlbyname("kern.iokittest", NULL, 0, &vector, sizeof(vector));
1858 T_EXPECT_POSIX_ZERO(ret, "sysctl kern.iokittest(%d)", vector);
1859 }
1860
1861 T_DECL(mte_iomd_cpu_map,
1862 "Test that IOMemoryDescriptor::map() of userspace memory is mapped as untagged in the kernel",
1863 T_META_ENABLED(TARGET_CPU_ARM64),
1864 T_META_REQUIRES_SYSCTL_EQ("hw.optional.arm.FEAT_MTE2", 1),
1865 T_META_ASROOT(true),
1866 XNU_T_META_SOC_SPECIFIC)
1867 {
1868 run_iokit_sysctl_test(333);
1869 }
1870
1871 T_DECL(mte_iomd_read_write_bytes,
1872 "Test that IOMemoryDescriptor::read/writeBytes() of tagged memory works",
1873 T_META_ENABLED(TARGET_CPU_ARM64),
1874 T_META_REQUIRES_SYSCTL_EQ("hw.optional.arm.FEAT_MTE2", 1),
1875 T_META_ASROOT(true),
1876 XNU_T_META_SOC_SPECIFIC) {
1877 run_iokit_sysctl_test(334);
1878 }
1879
1880 T_DECL(iomd_read_write_bytes_non_mte,
1881 "Test that IOMemoryDescriptor::read/writeBytes() of untagged memory works",
1882 T_META_ENABLED(TARGET_CPU_ARM64),
1883 T_META_REQUIRES_SYSCTL_EQ("hw.optional.arm.FEAT_MTE2", 1),
1884 T_META_ASROOT(true),
1885 XNU_T_META_SOC_SPECIFIC) {
1886 run_iokit_sysctl_test(335);
1887 }
1888
1889 T_DECL(iomd_read_bytes_with_tcf,
1890 "Test that tag mismatches during IOMemoryDescriptor::readBytes() get detected",
1891 T_META_ENABLED(TARGET_CPU_ARM64),
1892 T_META_REQUIRES_SYSCTL_EQ("hw.optional.arm.FEAT_MTE2", 1),
1893 T_META_ASROOT(true),
1894 XNU_T_META_SOC_SPECIFIC) {
1895 /* The iokit test will generate an artificial tag check mismatch midway through the buffer */
1896 expect_sigkill(^{
1897 run_iokit_sysctl_test(336);
1898 T_ASSERT_FAIL("Expected this process to get killed");
1899 }, "asynchronous TCF in readBytes()");
1900 }
1901
1902 T_DECL(iomd_write_bytes_with_tcf,
1903 "Test that tag mismatches during IOMemoryDescriptor::writeBytes() continue to work out of the box",
1904 T_META_ENABLED(TARGET_CPU_ARM64),
1905 T_META_REQUIRES_SYSCTL_EQ("hw.optional.arm.FEAT_MTE2", 1),
1906 T_META_ASROOT(true),
1907 XNU_T_META_SOC_SPECIFIC) {
1908 /* The iokit test will generate an artificial tag check mismatch midway through the buffer */
1909 expect_sigkill(^{
1910 run_iokit_sysctl_test(337);
1911 T_ASSERT_FAIL("Expected this process to get killed");
1912 }, "asynchronous TCF in writeBytes()");
1913 }
1914
1915 T_DECL(iomd_create_alias_mapping_in_this_map,
1916 "Test that IOMemoryDescriptor::createMappingInTask() of tagged memory in the current task works",
1917 T_META_ENABLED(TARGET_CPU_ARM64),
1918 T_META_REQUIRES_SYSCTL_EQ("hw.optional.arm.FEAT_MTE2", 1),
1919 T_META_ASROOT(true),
1920 XNU_T_META_SOC_SPECIFIC) {
1921 run_iokit_sysctl_test(340);
1922 }
1923
1924 T_DECL(iomd_create_alias_mapping_in_kernel_map,
1925 "Test that IOMemoryDescriptor::createMappingInTask() of tagged memory in the kernel is allowed",
1926 T_META_ENABLED(TARGET_CPU_ARM64),
1927 T_META_REQUIRES_SYSCTL_EQ("hw.optional.arm.FEAT_MTE2", 1),
1928 T_META_ASROOT(true),
1929 XNU_T_META_SOC_SPECIFIC) {
1930 run_iokit_sysctl_test(342);
1931 }
1932
1933 T_DECL(mte_cpu_map_pageout,
1934 "Test correct behavior of kernel CPU mapping after userspace mapping is paged out",
1935 T_META_ENABLED(TARGET_CPU_ARM64),
1936 T_META_REQUIRES_SYSCTL_EQ("hw.optional.arm.FEAT_MTE2", 1),
1937 T_META_ASROOT(true),
1938 XNU_T_META_SOC_SPECIFIC)
1939 {
1940 mach_vm_size_t alloc_size = PAGE_SIZE;
1941 char *ptr = (char*)(allocate_and_tag_range(alloc_size, TAG_RANDOM_EXCLUDE(0xF)));
1942 char value = 'A';
1943 memset(ptr, value, alloc_size);
1944
1945 struct {
1946 mach_vm_size_t size;
1947 char *ptr;
1948 char value;
1949 } args = { alloc_size, ptr, value };
1950 run_sysctl_test("vm_cpu_map_pageout", (int64_t)(&args));
1951 }
1952
1953 T_DECL(vm_region_recurse_mte_info,
1954 "Ensure metadata returned by vm_region_recurse correct reflects MTE status",
1955 T_META_ENABLED(TARGET_CPU_ARM64),
1956 T_META_REQUIRES_SYSCTL_EQ("hw.optional.arm.FEAT_MTE2", 1),
1957 XNU_T_META_SOC_SPECIFIC,
1958 T_META_ASROOT(true))
1959 {
1960 T_SETUPBEGIN;
1961
1962 /* Given an MTE-enabled region */
1963 const mach_vm_size_t alloc_size = PAGE_SIZE;
1964 vm_address_t tagged_buffer_addr = allocate_and_tag_range(alloc_size, 0xa);
1965 vm_address_t untagged_handle_to_tagged_address = tagged_buffer_addr & ~MTE_TAG_MASK;
1966
1967 /* And a non-MTE-enabled region */
1968 /* (Manually select an address to be sure we're placed in a new region from the tagged region) */
1969 mach_vm_address_t untagged_buffer_addr = untagged_handle_to_tagged_address + (32 * 1024);
1970 kern_return_t kr = mach_vm_allocate(
1971 mach_task_self(),
1972 &untagged_buffer_addr,
1973 alloc_size,
1974 VM_FLAGS_FIXED );
1975 T_ASSERT_MACH_SUCCESS(kr, "Allocated untagged page");
1976 /* (And write to it to be sure we populate a VM object) */
1977 memset((uint8_t*)untagged_buffer_addr, 0, alloc_size);
1978
1979 T_SETUPEND;
1980
1981 /* When we query the attributes of the region covering the MTE-enabled buffer */
1982 mach_vm_address_t addr = untagged_handle_to_tagged_address;
1983 mach_vm_size_t addr_size = alloc_size;
1984 uint32_t nesting_depth = UINT_MAX;
1985 mach_msg_type_number_t count = VM_REGION_SUBMAP_INFO_V2_COUNT_64;
1986 vm_region_submap_info_data_64_t region_info;
1987 kr = vm_region_recurse_64(mach_task_self(), (vm_address_t*)&addr, (vm_size_t*)&addr_size, &nesting_depth, (vm_region_recurse_info_t)®ion_info, &count);
1988
1989 /* Then our metadata confirms that the region contains an MTE-mappable object */
1990 T_ASSERT_MACH_SUCCESS(kr, "Query MTE-enabled region");
1991 T_ASSERT_TRUE(region_info.flags & VM_REGION_FLAG_MTE_ENABLED, "Expected metadata to reflect an MTE mappable object");
1992
1993 /* And when we query the same thing via the 'short' info */
1994 addr = untagged_handle_to_tagged_address;
1995 addr_size = alloc_size;
1996 nesting_depth = UINT_MAX;
1997 count = VM_REGION_SUBMAP_SHORT_INFO_COUNT_64;
1998 vm_region_submap_short_info_data_64_t short_info;
1999 kr = mach_vm_region_recurse(mach_task_self(), (mach_vm_address_t*)&addr, (mach_vm_size_t*)&addr_size, &nesting_depth, (vm_region_info_t)&short_info, &count);
2000
2001 /* Then the short metadata also confirms that the region contains an MTE-mappable object */
2002 T_ASSERT_MACH_SUCCESS(kr, "Query MTE-enabled region");
2003 T_ASSERT_TRUE(short_info.flags & VM_REGION_FLAG_MTE_ENABLED, "Expected metadata to reflect an MTE mappable object");
2004
2005 /* And when we query the attributes of the region covering the non-MTE-enabled buffer */
2006 addr = untagged_buffer_addr;
2007 addr_size = alloc_size;
2008 nesting_depth = UINT_MAX;
2009 count = VM_REGION_SUBMAP_INFO_V2_COUNT_64;
2010 memset(®ion_info, 0, sizeof(region_info));
2011 kr = mach_vm_region_recurse(mach_task_self(), (mach_vm_address_t*)&addr, (mach_vm_size_t*)&addr_size, &nesting_depth, (vm_region_info_t)®ion_info, &count);
2012
2013 /* Then our metadata confirm that the region does not contain an MTE-mappable object */
2014 T_ASSERT_MACH_SUCCESS(kr, "Query MTE-disabled region");
2015 T_ASSERT_FALSE(region_info.flags & VM_REGION_FLAG_MTE_ENABLED, "Expected metadata to reflect no MTE mappable object");
2016
2017 /* And when we query the same thing via the 'short' info */
2018 addr = untagged_buffer_addr;
2019 addr_size = alloc_size;
2020 nesting_depth = UINT_MAX;
2021 count = VM_REGION_SUBMAP_SHORT_INFO_COUNT_64;
2022 memset(&short_info, 0, sizeof(short_info));
2023 kr = mach_vm_region_recurse(mach_task_self(), (mach_vm_address_t*)&addr, (mach_vm_size_t*)&addr_size, &nesting_depth, (vm_region_info_t)&short_info, &count);
2024
2025 /* Then the short metadata also confirms that the region does not contain an MTE-mappable object */
2026 T_ASSERT_MACH_SUCCESS(kr, "Query MTE-disabled region");
2027 T_ASSERT_FALSE(short_info.flags & VM_REGION_FLAG_MTE_ENABLED, "Expected metadata to reflect no MTE mappable object");
2028
2029 /* Cleanup */
2030 kr = mach_vm_deallocate(mach_task_self(), untagged_handle_to_tagged_address, alloc_size);
2031 T_ASSERT_MACH_SUCCESS(kr, "deallocate tagged memory");
2032 kr = mach_vm_deallocate(mach_task_self(), untagged_buffer_addr, alloc_size);
2033 T_ASSERT_MACH_SUCCESS(kr, "deallocate untagged memory");
2034 }
2035
2036 T_DECL(mach_vm_read_of_remote_proc,
2037 "Verify that mach_vm_read of a remote MTE-enabled process works",
2038 T_META_ENABLED(TARGET_CPU_ARM64),
2039 T_META_REQUIRES_SYSCTL_EQ("hw.optional.arm.FEAT_MTE2", 1),
2040 XNU_T_META_SOC_SPECIFIC,
2041 /* rdar://151142487: gcore won't work on iOS without unrestricting task_read_for_pid */
2042 T_META_BOOTARGS_SET("amfi_unrestrict_task_for_pid=1"),
2043 T_META_ASROOT(true))
2044 {
2045 /* Given a process that is launched as MTE-enabled */
2046 char* sleep_args[] = { "/bin/sleep", "5000", NULL};
2047 posix_spawnattr_t attr;
2048 errno_t ret = posix_spawnattr_init(&attr);
2049 T_ASSERT_POSIX_ZERO(ret, "posix_spawnattr_init");
2050 ret = posix_spawnattr_set_use_sec_transition_shims_np(&attr, POSIX_SPAWN_SECFLAG_EXPLICIT_ENABLE);
2051 T_ASSERT_POSIX_ZERO(ret, "posix_spawnattr_set_use_sec_transition_shims_np");
2052 pid_t child_pid = 0;
2053 ret = posix_spawn(&child_pid, sleep_args[0], NULL, &attr, sleep_args, NULL);
2054 T_ASSERT_POSIX_ZERO(ret, "posix_spawn");
2055 T_ASSERT_NE(child_pid, 0, "posix_spawn");
2056 ret = posix_spawnattr_destroy(&attr);
2057 T_ASSERT_POSIX_ZERO(ret, "posix_spawnattr_destroy");
2058
2059 /* And it's MTE-enabled as expected */
2060 validate_proc_pidinfo_mte_status(child_pid, true);
2061
2062 /* And gcore attempts to mach_vm_read some of its memory */
2063 char pid_buf[64];
2064 snprintf(pid_buf, sizeof(pid_buf), "%d", child_pid);
2065 char* gcore_args[] = { "/usr/bin/gcore", pid_buf, NULL};
2066 /* Then gcore (and its implicit mach_vm_read()) succeeds */
2067 posix_spawn_with_flags_and_assert_successful_exit(gcore_args, POSIX_SPAWN_SECFLAG_EXPLICIT_DISABLE, false, false);
2068
2069 kill_child(child_pid);
2070 }
2071
2072 void
do_local_vm_copyin_with_invalid_tag_test(vm_size_t size)2073 do_local_vm_copyin_with_invalid_tag_test(vm_size_t size)
2074 {
2075 T_SETUPBEGIN;
2076
2077 /* Given an MTE-enabled region */
2078 vm_address_t mte_region = 0;
2079 kern_return_t kr = vm_allocate(mach_task_self(), &mte_region, size, VM_FLAGS_ANYWHERE | VM_FLAGS_MTE);
2080 T_ASSERT_MACH_SUCCESS(kr, "vm_allocate(MTE)");
2081 memset((void *)mte_region, 0, size);
2082
2083 /* And an MTE-disabled region */
2084 vm_address_t non_mte_region = 0;
2085 kr = vm_allocate(mach_task_self(), &non_mte_region, size, VM_FLAGS_ANYWHERE);
2086 T_ASSERT_MACH_SUCCESS(kr, "vm_allocate(non-MTE)");
2087
2088 /* And the MTE region has tag 0x4, but our pointer is incorrectly tagged 0x5 */
2089 mte_region |= 0x0400000000000000;
2090 __arm_mte_set_tag((void *)mte_region);
2091 mte_region |= 0x0500000000000000;
2092
2093 T_SETUPEND;
2094
2095 /* When we use `vm_read_overwrite` */
2096 /* Then the system terminates us due to our incorrectly tagged request */
2097 vm_size_t out_size;
2098 vm_read_overwrite(mach_task_self(), mte_region, size, non_mte_region, &out_size);
2099 T_FAIL("Expected to be SIGKILLED");
2100 }
2101
2102 T_DECL(local_vm_copyin_with_invalid_tag,
2103 "Verify that copyin of local memory with an invalid tag is denied",
2104 T_META_ENABLED(TARGET_CPU_ARM64),
2105 T_META_REQUIRES_SYSCTL_EQ("hw.optional.arm.FEAT_MTE2", 1),
2106 XNU_T_META_SOC_SPECIFIC,
2107 T_META_ASROOT(true))
2108 {
2109 /*
2110 * We go down different code paths depending on the size,
2111 * so test both and ensure they're handled consistently.
2112 */
2113 expect_sigkill(^{
2114 do_local_vm_copyin_with_invalid_tag_test(PAGE_SIZE);
2115 }, "local_vm_copyin(PAGE_SIZE)");
2116 expect_sigkill(^{
2117 do_local_vm_copyin_with_invalid_tag_test(PAGE_SIZE * 10);
2118 }, "local_vm_copyin(PAGE_SIZE * 10)");
2119 }
2120
2121 T_DECL(local_vm_copyin_with_large_non_mte_object_with_adjacent_mte_object,
2122 "Ensure a large copyin with a non-MTE object and adjacent MTE object fails",
2123 T_META_ENABLED(TARGET_CPU_ARM64),
2124 T_META_REQUIRES_SYSCTL_EQ("hw.optional.arm.FEAT_MTE2", 1),
2125 XNU_T_META_SOC_SPECIFIC,
2126 T_META_ASROOT(true))
2127 {
2128 expect_sigkill(^{
2129 /* Given a non-MTE-enabled object */
2130 vm_address_t non_mte_object_address = 0;
2131 vm_size_t non_mte_object_size = PAGE_SIZE;
2132 kern_return_t kr = vm_allocate(mach_task_self(), &non_mte_object_address, non_mte_object_size, VM_FLAGS_ANYWHERE);
2133 T_ASSERT_MACH_SUCCESS(kr, "vm_allocate(non-MTE)");
2134 /* And ensure it's present */
2135 memset((void *)non_mte_object_address, 0, non_mte_object_size);
2136
2137 /* And an adjacent MTE object (which is large enough that the total region will definitely be above `msg_ool_size_small`) */
2138 vm_address_t mte_object_address = non_mte_object_address + non_mte_object_size;
2139 vm_size_t mte_object_size = PAGE_SIZE * 2;
2140 kr = vm_allocate(mach_task_self(), &mte_object_address, mte_object_size, VM_FLAGS_FIXED | VM_FLAGS_MTE);
2141 if (kr == KERN_NO_SPACE) {
2142 /*
2143 * Skip gracefully if we fail to grab the VA space we need.
2144 * Note that we send ourselves a SIGKILL so the expect_sigkill() wrapper
2145 * is happy. We can't use T_SKIP or the like because that would elide the
2146 * SIGKILL.
2147 */
2148 T_LOG("Cannot grab required VA space, skipping...");
2149 kill(getpid(), SIGKILL);
2150 return;
2151 }
2152 T_ASSERT_MACH_SUCCESS(kr, "vm_allocate(adjacent MTE)");
2153 /* And ensure it's present */
2154 memset((void *)mte_object_address, 0, mte_object_size);
2155 /* And the MTE object has a non-zero tag (so we TCF when crossing it) */
2156 mte_object_address |= 0x0400000000000000;
2157 for (mach_vm_size_t offset = 0; offset < mte_object_size; offset += MTE_GRANULE_SIZE) {
2158 __arm_mte_set_tag(&((uint8_t*)mte_object_address)[offset]);
2159 }
2160
2161 /* When we try to copyin the entire region, spanning both objects */
2162 vm_size_t total_region_size = mte_object_size + non_mte_object_size;
2163 vm_address_t region_to_overwrite = 0;
2164 kr = vm_allocate(mach_task_self(), ®ion_to_overwrite, total_region_size, VM_FLAGS_ANYWHERE);
2165 T_ASSERT_MACH_SUCCESS(kr, "vm_allocate(scribble region)");
2166
2167 vm_size_t out_size;
2168 /* Then we take a TCF during the copyin */
2169 vm_read_overwrite(mach_task_self(), non_mte_object_address, total_region_size, region_to_overwrite, &out_size);
2170 }, "Trigger a TCF during copyin");
2171 }
2172
2173 T_DECL(local_vm_copyin_with_large_mte_object_with_invalid_size,
2174 "Ensure a large copyin with a non-MTE object but an invalid size fails",
2175 T_META_ENABLED(TARGET_CPU_ARM64),
2176 T_META_REQUIRES_SYSCTL_EQ("hw.optional.arm.FEAT_MTE2", 1),
2177 XNU_T_META_SOC_SPECIFIC,
2178 T_META_ASROOT(true))
2179 {
2180 /* Given an MTE-enabled object (which is large enough that it exceeds `msg_ool_size_small`) */
2181 vm_address_t mte_object_address = 0;
2182 vm_size_t mte_object_size = PAGE_SIZE * 3;
2183 kern_return_t kr = vm_allocate(mach_task_self(), &mte_object_address, mte_object_size, VM_FLAGS_ANYWHERE | VM_FLAGS_MTE);
2184 T_ASSERT_MACH_SUCCESS(kr, "vm_allocate(MTE)");
2185 /* And ensure it's present */
2186 memset((void *)mte_object_address, 0, mte_object_size);
2187
2188 /* When we try to copyin the region, but specify a size that's too large */
2189 /* And we ensure this object is not coalesced with the above object */
2190 vm_size_t invalid_size = mte_object_size + PAGE_SIZE * 16;
2191 vm_address_t region_to_overwrite = mte_object_address + (PAGE_SIZE * 8);
2192 kr = vm_allocate(mach_task_self(), ®ion_to_overwrite, invalid_size, VM_FLAGS_FIXED);
2193 if (kr == KERN_NO_SPACE) {
2194 /* Skip gracefully if we fail to grab the VA space we need */
2195 T_SKIP("Cannot grab required VA space, skipping...");
2196 return;
2197 }
2198 T_ASSERT_MACH_SUCCESS(kr, "vm_allocate(scribble region)");
2199
2200 vm_size_t out_size;
2201 kr = vm_read_overwrite(mach_task_self(), mte_object_address, invalid_size, region_to_overwrite, &out_size);
2202 /* Then it fails */
2203 T_ASSERT_MACH_ERROR(kr, KERN_INVALID_ADDRESS, "copyin fails");
2204 }
2205
2206 T_DECL(local_vm_copyin_with_large_mte_object_with_hole_in_region,
2207 "Ensure a large copyin with an MTE object, but with a hole in the middle, is rejected",
2208 T_META_ENABLED(TARGET_CPU_ARM64),
2209 T_META_REQUIRES_SYSCTL_EQ("hw.optional.arm.FEAT_MTE2", 1),
2210 XNU_T_META_SOC_SPECIFIC,
2211 T_META_ASROOT(true))
2212 {
2213 /* Given an MTE-enabled object (which is large enough that it exceeds `msg_ool_size_small`) */
2214 vm_address_t mte_object_address = 0;
2215 vm_size_t mte_object_size = PAGE_SIZE * 3;
2216 kern_return_t kr = vm_allocate(mach_task_self(), &mte_object_address, mte_object_size, VM_FLAGS_ANYWHERE | VM_FLAGS_MTE);
2217 T_ASSERT_MACH_SUCCESS(kr, "vm_allocate(MTE)");
2218 /* And ensure it's present */
2219 memset((void *)mte_object_address, 0, mte_object_size);
2220
2221 /* And a nearby non-MTE object, but we leave a hole in the middle */
2222 vm_size_t padding = PAGE_SIZE;
2223 vm_address_t non_mte_object_address = mte_object_address + mte_object_size + padding;
2224 vm_size_t non_mte_object_size = PAGE_SIZE;
2225 kr = vm_allocate(mach_task_self(), &non_mte_object_address, non_mte_object_size, VM_FLAGS_FIXED);
2226 if (kr == KERN_NO_SPACE) {
2227 /* Skip gracefully if we fail to grab the VA space we need */
2228 T_SKIP("Cannot grab required VA space, skipping...");
2229 return;
2230 }
2231
2232 T_ASSERT_MACH_SUCCESS(kr, "vm_allocate(nearby non-MTE)");
2233 /* And ensure it's present */
2234 memset((void *)non_mte_object_address, 0, non_mte_object_size);
2235
2236 /* When we try to copyin the whole region, including the hole */
2237 vm_size_t region_size = mte_object_size + padding + non_mte_object_size;
2238 vm_address_t region_to_overwrite = 0;
2239 kr = vm_allocate(mach_task_self(), ®ion_to_overwrite, region_size, VM_FLAGS_ANYWHERE);
2240 T_ASSERT_MACH_SUCCESS(kr, "vm_allocate(scribble region)");
2241
2242 vm_size_t out_size;
2243 kr = vm_read_overwrite(mach_task_self(), mte_object_address, region_size, region_to_overwrite, &out_size);
2244 /* Then it fails */
2245 T_ASSERT_MACH_ERROR(kr, KERN_INVALID_ADDRESS, "copyin fails");
2246 }
2247
2248 T_DECL(local_vm_copyin_with_large_mte_object_with_adjacent_large_mte_object_same_tags,
2249 "Ensure a large copyin with two MTE objects with the same tag succeeds",
2250 T_META_ENABLED(TARGET_CPU_ARM64),
2251 T_META_REQUIRES_SYSCTL_EQ("hw.optional.arm.FEAT_MTE2", 1),
2252 XNU_T_META_SOC_SPECIFIC,
2253 T_META_ASROOT(true))
2254 {
2255 /* Given an MTE-enabled object */
2256 vm_address_t mte_object1_address = 0;
2257 vm_size_t mte_object1_size = PAGE_SIZE;
2258 kern_return_t kr = vm_allocate(mach_task_self(), &mte_object1_address, mte_object1_size, VM_FLAGS_ANYWHERE | VM_FLAGS_MTE);
2259 T_ASSERT_MACH_SUCCESS(kr, "vm_allocate(MTE)");
2260 /* And ensure it's present */
2261 memset((void *)mte_object1_address, 0, mte_object1_size);
2262
2263 /* And an adjacent MTE object (which is large enough that the total region will definitely be above `msg_ool_size_small`) */
2264 vm_address_t mte_object2_address = mte_object1_address + mte_object1_size;
2265 vm_size_t mte_object2_size = PAGE_SIZE * 2;
2266 kr = vm_allocate(mach_task_self(), &mte_object2_address, mte_object2_size, VM_FLAGS_FIXED | VM_FLAGS_MTE);
2267 if (kr == KERN_NO_SPACE) {
2268 /* Skip gracefully if we fail to grab the VA space we need */
2269 T_SKIP("Cannot grab required VA space, skipping...");
2270 return;
2271 }
2272 T_ASSERT_MACH_SUCCESS(kr, "vm_allocate(MTE)");
2273 /* And ensure it's present */
2274 memset((void *)mte_object2_address, 0, mte_object2_size);
2275
2276 /* And both objects share the same tag */
2277 vm_size_t total_region_size = mte_object1_size + mte_object2_size;
2278 mte_object1_address |= 0x0400000000000000;
2279 for (mach_vm_size_t offset = 0; offset < total_region_size; offset += MTE_GRANULE_SIZE) {
2280 __arm_mte_set_tag(&((uint8_t*)mte_object1_address)[offset]);
2281 }
2282
2283 /* When we try to copyin the entire region, spanning both objects */
2284 vm_address_t region_to_overwrite = 0;
2285 kr = vm_allocate(mach_task_self(), ®ion_to_overwrite, total_region_size, VM_FLAGS_ANYWHERE);
2286 T_ASSERT_MACH_SUCCESS(kr, "vm_allocate(scribble region)");
2287
2288 vm_size_t out_size;
2289 kr = vm_read_overwrite(mach_task_self(), mte_object1_address, total_region_size, region_to_overwrite, &out_size);
2290 /* Then it succeeds */
2291 T_ASSERT_MACH_SUCCESS(kr, "copyin");
2292 }
2293
2294 T_DECL(local_vm_copyin_with_large_mte_object_with_adjacent_large_mte_object_different_tags,
2295 "Ensure a large copyin with two MTE objects with a different tag in the second object fails",
2296 T_META_ENABLED(TARGET_CPU_ARM64),
2297 T_META_REQUIRES_SYSCTL_EQ("hw.optional.arm.FEAT_MTE2", 1),
2298 XNU_T_META_SOC_SPECIFIC,
2299 T_META_ASROOT(true))
2300 {
2301 expect_sigkill(^{
2302 /* Given an MTE-enabled object */
2303 vm_address_t mte_object1_address = 0;
2304 vm_size_t mte_object1_size = PAGE_SIZE;
2305 kern_return_t kr = vm_allocate(mach_task_self(), &mte_object1_address, mte_object1_size, VM_FLAGS_ANYWHERE | VM_FLAGS_MTE);
2306 T_ASSERT_MACH_SUCCESS(kr, "vm_allocate(MTE)");
2307 /* And ensure it's present */
2308 memset((void *)mte_object1_address, 0, mte_object1_size);
2309
2310 /* And an adjacent MTE object (which is large enough that the total region will definitely be above `msg_ool_size_small`) */
2311 vm_address_t mte_object2_address = mte_object1_address + mte_object1_size;
2312 vm_size_t mte_object2_size = PAGE_SIZE * 2;
2313 kr = vm_allocate(mach_task_self(), &mte_object2_address, mte_object2_size, VM_FLAGS_FIXED | VM_FLAGS_MTE);
2314 if (kr == KERN_NO_SPACE) {
2315 /*
2316 * Skip gracefully if we fail to grab the VA space we need.
2317 * Note that we send ourselves a SIGKILL so the expect_sigkill() wrapper
2318 * is happy. We can't use T_SKIP or the like because that would elide the
2319 * SIGKILL.
2320 */
2321 T_LOG("Cannot grab required VA space, skipping...");
2322 kill(getpid(), SIGKILL);
2323 return;
2324 }
2325 T_ASSERT_MACH_SUCCESS(kr, "vm_allocate(adjacent MTE)");
2326 /* And ensure it's present */
2327 memset((void *)mte_object2_address, 0, mte_object2_size);
2328
2329 /* And the objects have different tags */
2330 mte_object1_address |= 0x0400000000000000;
2331 for (mach_vm_size_t offset = 0; offset < mte_object1_size; offset += MTE_GRANULE_SIZE) {
2332 __arm_mte_set_tag(&((uint8_t*)mte_object1_address)[offset]);
2333 }
2334 mte_object2_address |= 0x0500000000000000;
2335 for (mach_vm_size_t offset = 0; offset < mte_object2_size; offset += MTE_GRANULE_SIZE) {
2336 __arm_mte_set_tag(&((uint8_t*)mte_object2_address)[offset]);
2337 }
2338
2339 /* When we try to copyin the entire region, spanning both objects */
2340 vm_address_t region_to_overwrite = 0;
2341 vm_size_t total_region_size = mte_object1_size + mte_object2_size;
2342 kr = vm_allocate(mach_task_self(), ®ion_to_overwrite, total_region_size, VM_FLAGS_ANYWHERE);
2343 T_ASSERT_MACH_SUCCESS(kr, "vm_allocate(scribble region)");
2344
2345 /* And we use a pointer that only has a valid tag for the first object */
2346 /* Then we get a SIGKILL (because we take a TCF) */
2347 vm_size_t out_size;
2348 vm_read_overwrite(mach_task_self(), mte_object1_address, total_region_size, region_to_overwrite, &out_size);
2349 }, "Trigger a TCF during copyin");
2350 }
2351
2352 T_DECL(local_vm_copyin_with_large_mte_object_with_adjacent_non_mte_object,
2353 "Ensure a large copyin with an MTE object and adjacent non-MTE object fails",
2354 T_META_ENABLED(TARGET_CPU_ARM64),
2355 T_META_REQUIRES_SYSCTL_EQ("hw.optional.arm.FEAT_MTE2", 1),
2356 XNU_T_META_SOC_SPECIFIC,
2357 T_META_ASROOT(true))
2358 {
2359 expect_sigkill(^{
2360 /* Given an MTE-enabled object */
2361 vm_address_t mte_object_address = 0;
2362 vm_size_t mte_object_size = PAGE_SIZE;
2363 kern_return_t kr = vm_allocate(mach_task_self(), &mte_object_address, mte_object_size, VM_FLAGS_ANYWHERE | VM_FLAGS_MTE);
2364 T_ASSERT_MACH_SUCCESS(kr, "vm_allocate(MTE)");
2365 /* And ensure it's present */
2366 memset((void *)mte_object_address, 0, mte_object_size);
2367 /* And the MTE object has a non-zero tag (so we CTCF when crossing to an untagged region) */
2368 vm_address_t tagged_mte_object_address = mte_object_address | 0x0400000000000000;
2369 for (mach_vm_size_t offset = 0; offset < mte_object_size; offset += MTE_GRANULE_SIZE) {
2370 __arm_mte_set_tag(&((uint8_t*)tagged_mte_object_address)[offset]);
2371 }
2372
2373 /* And an adjacent non-MTE object (which is large enough that the total region will definitely be above `msg_ool_size_small`) */
2374 vm_address_t non_mte_object_address = mte_object_address + mte_object_size;
2375 vm_size_t non_mte_object_size = PAGE_SIZE * 2;
2376 kr = vm_allocate(mach_task_self(), &non_mte_object_address, non_mte_object_size, VM_FLAGS_FIXED);
2377 if (kr == KERN_NO_SPACE) {
2378 /*
2379 * Skip gracefully if we fail to grab the VA space we need.
2380 * Note that we send ourselves a SIGKILL so the expect_sigkill() wrapper
2381 * is happy. We can't use T_SKIP or the like because that would elide the
2382 * SIGKILL.
2383 */
2384 T_LOG("Cannot grab required VA space, skipping...");
2385 kill(getpid(), SIGKILL);
2386 return;
2387 }
2388 T_ASSERT_MACH_SUCCESS(kr, "vm_allocate(adjacent non-MTE)");
2389 /* And ensure it's present */
2390 memset((void *)non_mte_object_address, 0, non_mte_object_size);
2391
2392 /* When we try to copyin the entire region, spanning both objects */
2393 vm_size_t total_region_size = mte_object_size + non_mte_object_size;
2394 vm_address_t region_to_overwrite = 0;
2395 kr = vm_allocate(mach_task_self(), ®ion_to_overwrite, total_region_size, VM_FLAGS_ANYWHERE);
2396 T_ASSERT_MACH_SUCCESS(kr, "vm_allocate(scribble region)");
2397
2398 vm_size_t out_size;
2399 vm_read_overwrite(mach_task_self(), mte_object_address, total_region_size, region_to_overwrite, &out_size);
2400 /* Then we're killed due to a CTCF */
2401 }, "Trigger a CTCF during copyin");
2402 }
2403
2404 T_DECL(make_memory_entry_handles_kernel_buffers,
2405 "Ensure mach_make_memory_entry does not panic when handed an MTE copy",
2406 T_META_ENABLED(TARGET_CPU_ARM64),
2407 T_META_REQUIRES_SYSCTL_EQ("hw.optional.arm.FEAT_MTE2", 1),
2408 XNU_T_META_SOC_SPECIFIC,
2409 T_META_ASROOT(true))
2410 {
2411 /* Given an MTE-enabled object */
2412 vm_address_t mte_object_address = 0;
2413 vm_size_t mte_object_size = PAGE_SIZE;
2414 kern_return_t kr = vm_allocate(mach_task_self(), &mte_object_address, mte_object_size, VM_FLAGS_ANYWHERE | VM_FLAGS_MTE);
2415 T_ASSERT_MACH_SUCCESS(kr, "vm_allocate(MTE)");
2416 /* And ensure it's present */
2417 memset((void *)mte_object_address, 0, mte_object_size);
2418 /* And assign a non-zero tag just for authenticity */
2419 vm_address_t tagged_mte_object_address = mte_object_address | 0x0400000000000000;
2420 for (mach_vm_size_t offset = 0; offset < mte_object_size; offset += MTE_GRANULE_SIZE) {
2421 __arm_mte_set_tag(&((uint8_t*)tagged_mte_object_address)[offset]);
2422 }
2423
2424 /* When I use mach_make_memory_entry_64(MAP_MEM_VM_COPY) */
2425 mach_vm_size_t size = mte_object_size;
2426 mach_port_t memory_entry_port;
2427 kr = mach_make_memory_entry_64(mach_task_self(),
2428 &size,
2429 tagged_mte_object_address,
2430 VM_PROT_DEFAULT | MAP_MEM_VM_COPY | MAP_MEM_USE_DATA_ADDR,
2431 &memory_entry_port, MEMORY_OBJECT_NULL);
2432 /* Then the system does not panic... */
2433 T_ASSERT_MACH_SUCCESS(kr, "mach_make_memory_entry_64(MTE object)");
2434 }
2435