1 #if __arm64__ 2 #include <arm_acle.h> 3 #include <darwintest.h> 4 #include <mach/mach.h> 5 #include <mach/vm_map.h> 6 #include <mach/mach_vm.h> 7 #include <sys/mman.h> 8 #include <spawn.h> 9 #include <unistd.h> 10 #include <stdlib.h> 11 #include <assert.h> 12 #include <stdio.h> 13 #include <sys/ptrace.h> 14 #include <fcntl.h> 15 16 #include "arm_mte_utilities.h" 17 #include "test_utils.h" 18 19 T_GLOBAL_META( 20 T_META_NAMESPACE("xnu.vm"), 21 T_META_RADAR_COMPONENT_NAME("xnu"), 22 T_META_RADAR_COMPONENT_VERSION("VM"), 23 T_META_ENABLED(true), 24 XNU_T_META_SOC_SPECIFIC 25 ); 26 27 /* 28 * Processes which explicitly declare themselves as being unsafe to receive untagged aliases to tagged memory 29 * are killed by the system when the system conspires to grant them an untagged alias. 30 * This test ensure that processes that opt into restrictions on aliasing tagged memory may not receive aliases: 31 * we launch a binary signed with an ID hard-coded into AMFI to opt in to this restriction, then attempt 32 * to remap tagged memory into this target. 33 */ 34 T_DECL(process_with_alias_restricted_opt_in_cannot_receive_mte_alias, 35 "Ensure a process which opts in to alias restrictions may not receive " 36 "an alias to tagged memory, and that an attempt to do so triggers a fatal guard.", 37 T_META_REQUIRES_SYSCTL_EQ("hw.optional.arm.FEAT_MTE4", 1), 38 XNU_T_META_SOC_SPECIFIC) { 39 /* 40 * Given a binary signed in such a way that it should never be allowed to 41 * receive aliases to untagged memory from elsewhere on the system. 42 */ 43 44 /* And we fork() so that we can observe the eventual SIGKILL */ 45 expect_sigkill(^{ 46 /* When we create some tagged memory in our context */ 47 mach_vm_address_t address = 0; 48 mach_vm_size_t size = PAGE_SIZE; 49 kern_return_t kr = mach_vm_allocate(mach_task_self(), &address, size, VM_FLAGS_ANYWHERE | VM_FLAGS_MTE); 50 T_ASSERT_MACH_SUCCESS(kr, "allocate MTE memory"); 51 52 /* And we spawn the process which is not allowed to receive aliases to tagged memory */ 53 pid_t target_pid; 54 char* target_argv[] = {"arm_mte_alias_restriction_helper", NULL}; 55 int ret = posix_spawn(&target_pid, target_argv[0], NULL, NULL, target_argv, NULL); 56 T_ASSERT_POSIX_ZERO(ret, "posix_spawn(%s)", target_argv[0]); 57 T_ASSERT_NE(target_pid, 0, "posix_spawn(%s)", target_argv[0]); 58 59 /* And we fetch a task port for the target */ 60 task_port_t target_task_port; 61 kr = task_for_pid(mach_task_self(), target_pid, &target_task_port); 62 T_ASSERT_MACH_SUCCESS(kr, "task_for_pid for target"); 63 64 /* When we attempt to remap the tagged memory into the target */ 65 mach_vm_address_t remap_addr = 0; 66 vm_prot_t curprot = VM_PROT_WRITE | VM_PROT_READ; 67 vm_prot_t maxprot = VM_PROT_WRITE | VM_PROT_READ; 68 kr = mach_vm_remap_new(target_task_port, &remap_addr, size, 69 /* mask = */ 0, VM_FLAGS_ANYWHERE, mach_task_self(), address, 70 /* copy = */ FALSE, &curprot, &maxprot, VM_INHERIT_DEFAULT); 71 T_ASSERT_MACH_SUCCESS(kr, "remap tagged memory"); 72 if (kr != KERN_SUCCESS) { 73 fprintf(stderr, "failed to remap tagged memory\n"); 74 exit(1); 75 } 76 77 /* And we wire the memory in the target to trigger the policy check */ 78 mach_port_t host_priv = HOST_PRIV_NULL; 79 kr = host_get_host_priv_port(mach_host_self(), &host_priv); \ 80 T_ASSERT_MACH_SUCCESS(kr, "host_get_host_priv_port"); 81 kr = mach_vm_wire(host_priv, target_task_port, remap_addr, size, VM_PROT_READ | VM_PROT_WRITE); 82 T_ASSERT_MACH_SUCCESS(kr, "mach_vm_wire in target"); 83 84 /* Then the system should have killed the actor that attempted to enter this memory */ 85 T_FAIL("Expected the system to prevent us from receiving an alias to tagged memory"); 86 }, "Attempt to map an untagged alias to tagged memory in a restricted receiver"); 87 } 88 89 T_DECL(vm_update_pointers_with_remote_tags_without_debugger, 90 "Ensure mach_vm_update_pointers_with_remote_tags is unusable when not debugged", 91 T_META_REQUIRES_SYSCTL_EQ("hw.optional.arm.FEAT_MTE4", 1), 92 XNU_T_META_SOC_SPECIFIC) { 93 /* Given a tagged buffer */ 94 const mach_vm_size_t alloc_size = PAGE_SIZE; 95 const mach_vm_size_t halfway = alloc_size / 2; 96 vm_address_t tagged_addr = allocate_and_tag_range(alloc_size, 0xa); 97 uint8_t* tagged_ptr = (uint8_t*)((uintptr_t)tagged_addr); 98 vm_address_t untagged_addr = tagged_addr & ~MTE_TAG_MASK; 99 uint8_t* untagged_ptr = (uint8_t*)((uintptr_t)untagged_addr); 100 101 mach_vm_offset_t addresses_to_tag[1] = {(mach_vm_offset_t)untagged_ptr}; 102 103 /* And we grab a task port */ 104 task_port_t target_task_port; 105 kern_return_t kr = task_for_pid(mach_task_self(), getpid(), &target_task_port); 106 T_ASSERT_MACH_SUCCESS(kr, "task_for_pid for target"); 107 108 /* When we request the pointers be rewritten with their MTE tags */ 109 mach_vm_offset_t resigned_addresses[1] = {0}; 110 mach_vm_offset_list_t input_list = addresses_to_tag; 111 mach_vm_offset_list_t output_list = resigned_addresses; 112 113 int count = 1; 114 kr = mach_vm_update_pointers_with_remote_tags( 115 target_task_port, 116 input_list, 117 count, 118 output_list, 119 &count 120 ); 121 122 /* Then it fails, because the input task wasn't debugged */ 123 T_ASSERT_EQ(kr, KERN_INVALID_ARGUMENT, "Expected mach_vm_update_pointers_with_remote_tags to fail when map !debugged"); 124 } 125 126 T_DECL(vm_update_pointers_with_remote_tags_invalid_inputs, 127 "Ensure mach_vm_update_pointers_with_remote_tags fails when the input sizes don't match", 128 T_META_REQUIRES_SYSCTL_EQ("hw.optional.arm.FEAT_MTE4", 1), 129 T_META_REQUIRES_SYSCTL_EQ("security.mac.amfi.developer_mode_status", 1), 130 /* It's not straightforward to ptrace on platforms other than macOS, so don't bother */ 131 T_META_ENABLED(TARGET_CPU_ARM64 && TARGET_OS_OSX), 132 XNU_T_META_SOC_SPECIFIC) { 133 /* Given we fork off into a debugger and debugee (and Developer Mode is enabled) */ 134 /* And we set up a shared comms channel between parent and child */ 135 int ret; 136 const char* memory_path = "vm_update_pointers"; 137 shm_unlink(memory_path); 138 int shm_fd = shm_open(memory_path, O_RDWR | O_CREAT | O_EXCL); 139 T_ASSERT_POSIX_SUCCESS(shm_fd, "Created shared memory"); 140 141 const mach_msg_type_number_t count = 1; 142 struct shared_data { 143 bool has_parent_connected; 144 bool has_child_populated_pointers; 145 bool has_parent_finished_inspecting_child; 146 bool has_child_acked_exit; 147 uint8_t* tagged_ptr; 148 uint8_t* differently_tagged_ptr; 149 mach_vm_offset_t addresses_to_tag[count]; 150 }; 151 152 ret = ftruncate(shm_fd, sizeof(struct shared_data)); 153 T_ASSERT_POSIX_SUCCESS(ret, "ftruncate"); 154 struct shared_data* shm = (struct shared_data*)mmap(NULL, sizeof(struct shared_data), PROT_READ | PROT_WRITE, MAP_SHARED, shm_fd, 0); 155 156 const mach_vm_size_t alloc_size = PAGE_SIZE; 157 const mach_vm_size_t halfway = alloc_size / 2; 158 159 pid_t pid = fork(); 160 T_QUIET; T_ASSERT_POSIX_SUCCESS(pid, "fork"); 161 162 if (pid == 0) { 163 /* Allow the parent to attach */ 164 while (!shm->has_parent_connected) { 165 sleep(1); 166 } 167 168 /* And a tagged buffer */ 169 vm_address_t tagged_addr = allocate_and_tag_range(alloc_size, 0xa); 170 shm->tagged_ptr = (uint8_t*)((uintptr_t)tagged_addr); 171 vm_address_t untagged_addr = tagged_addr & ~MTE_TAG_MASK; 172 uint8_t* untagged_ptr = (uint8_t*)((uintptr_t)untagged_addr); 173 174 shm->addresses_to_tag[0] = (mach_vm_offset_t)&untagged_ptr[0]; 175 176 /* Let the parent know we're ready to go */ 177 shm->has_child_populated_pointers = true; 178 179 /* Allow the parent to interrogate our address space */ 180 while (!shm->has_parent_finished_inspecting_child) { 181 sleep(1); 182 } 183 shm->has_child_acked_exit = true; 184 exit(0); 185 } 186 187 /* Attach to the child so it's marked as being debugged */ 188 ret = ptrace(PT_ATTACHEXC, pid, 0, 0); 189 T_EXPECT_POSIX_SUCCESS(ret, "ptrace PT_ATTACHEXC"); 190 191 /* And let the child know that it can carry on */ 192 shm->has_parent_connected = true; 193 194 /* And ensure the child has set up the memory */ 195 while (!(shm->has_child_populated_pointers)) { 196 sleep(1); 197 } 198 199 /* And we grab a task port */ 200 task_port_t target_task_port; 201 kern_return_t kr = task_for_pid(mach_task_self(), pid, &target_task_port); 202 T_ASSERT_MACH_SUCCESS(kr, "task_for_pid for target"); 203 204 /* When we request the pointers be rewritten with their MTE tags */ 205 mach_vm_offset_t resigned_addresses[count] = {0}; 206 mach_vm_offset_list_t input_list = &shm->addresses_to_tag[0]; 207 mach_vm_offset_list_t output_list = resigned_addresses; 208 209 /* But our output array has a size mismatched from the input array */ 210 const mach_msg_type_number_t mismatched_count = 2; 211 212 kr = mach_vm_update_pointers_with_remote_tags( 213 target_task_port, 214 input_list, 215 count, 216 output_list, 217 &mismatched_count 218 ); 219 /* Then it fails, because the input task wasn't debugged */ 220 T_ASSERT_EQ(kr, KERN_INVALID_ARGUMENT, "Expected mach_vm_update_pointers_with_remote_tags to fail input array sizes mismatch"); 221 222 /* Cleanup: let the child know that it's fine to exit */ 223 shm->has_parent_finished_inspecting_child = true; 224 while (!(shm->has_child_acked_exit)) { 225 sleep(1); 226 } 227 228 T_ASSERT_POSIX_SUCCESS(close(shm_fd), "Closed shm fd"); 229 T_ASSERT_POSIX_SUCCESS(shm_unlink(memory_path), "Unlinked"); 230 } 231 232 T_DECL(vm_update_pointers_with_remote_tags, 233 "Validate the behavior of the API that allows reading remote tag info", 234 T_META_REQUIRES_SYSCTL_EQ("hw.optional.arm.FEAT_MTE4", 1), 235 T_META_REQUIRES_SYSCTL_EQ("security.mac.amfi.developer_mode_status", 1), 236 /* It's not straightforward to ptrace on platforms other than macOS, so don't bother */ 237 T_META_ENABLED(TARGET_CPU_ARM64 && TARGET_OS_OSX), 238 XNU_T_META_SOC_SPECIFIC) { 239 /* Given we fork off into a debugger and debugee (and Developer Mode is enabled) */ 240 /* And we set up a shared comms channel between parent and child */ 241 int ret; 242 const char* memory_path = "vm_update_pointers"; 243 shm_unlink(memory_path); 244 int shm_fd = shm_open(memory_path, O_RDWR | O_CREAT | O_EXCL); 245 T_ASSERT_POSIX_SUCCESS(shm_fd, "Created shared memory"); 246 247 const mach_msg_type_number_t count = 4; 248 struct shared_data { 249 bool has_parent_connected; 250 bool has_child_populated_pointers; 251 bool has_parent_finished_inspecting_child; 252 bool has_child_acked_exit; 253 uint8_t* tagged_ptr; 254 uint8_t* differently_tagged_ptr; 255 uint8_t* untagged_ptr; 256 mach_vm_offset_t addresses_to_tag[count]; 257 }; 258 259 ret = ftruncate(shm_fd, sizeof(struct shared_data)); 260 T_ASSERT_POSIX_SUCCESS(ret, "ftruncate"); 261 struct shared_data* shm = (struct shared_data*)mmap(NULL, sizeof(struct shared_data), PROT_READ | PROT_WRITE, MAP_SHARED, shm_fd, 0); 262 263 const mach_vm_size_t alloc_size = PAGE_SIZE; 264 const mach_vm_size_t halfway = alloc_size / 2; 265 266 pid_t pid = fork(); 267 T_QUIET; T_ASSERT_POSIX_SUCCESS(pid, "fork"); 268 269 if (pid == 0) { 270 /* Allow the parent to attach */ 271 while (!shm->has_parent_connected) { 272 sleep(1); 273 } 274 275 /* Given an untagged buffer */ 276 void* untagged_buffer_addr = allocate_untagged_memory(alloc_size); 277 shm->untagged_ptr = untagged_buffer_addr; 278 279 /* And a tagged buffer */ 280 vm_address_t tagged_addr = allocate_and_tag_range(alloc_size, 0xa); 281 shm->tagged_ptr = (uint8_t*)((uintptr_t)tagged_addr); 282 vm_address_t untagged_addr = tagged_addr & ~MTE_TAG_MASK; 283 uint8_t* untagged_ptr = (uint8_t*)((uintptr_t)untagged_addr); 284 285 /* And a different tag is used halfway through the tagged buffer */ 286 uint64_t different_tag = 0xb; 287 shm->differently_tagged_ptr = (uint8_t*)((uintptr_t) untagged_ptr | (different_tag << MTE_TAG_SHIFT)); 288 for (mach_vm_size_t offset = halfway; offset < alloc_size; offset += MTE_GRANULE_SIZE) { 289 __arm_mte_set_tag(&shm->differently_tagged_ptr[offset]); 290 } 291 292 /* And a pointer to a bogus address */ 293 mach_vm_offset_t bogus_pointer = 0xaaaaaaaa; 294 /* And one of the pointers points to a region that we try ensure isn't resident */ 295 T_ASSERT_POSIX_ZERO(madvise(untagged_buffer_addr, alloc_size, MADV_DONTNEED), "madvise(DONTNEED)"); 296 297 shm->addresses_to_tag[0] = (mach_vm_offset_t)&untagged_ptr[0]; 298 shm->addresses_to_tag[1] = (mach_vm_offset_t)&untagged_ptr[halfway]; 299 shm->addresses_to_tag[2] = (mach_vm_offset_t)untagged_buffer_addr; 300 shm->addresses_to_tag[3] = (mach_vm_offset_t)bogus_pointer; 301 302 /* Let the parent know we're ready to go */ 303 shm->has_child_populated_pointers = true; 304 305 /* Allow the parent to interrogate our address space */ 306 while (!shm->has_parent_finished_inspecting_child) { 307 sleep(1); 308 } 309 shm->has_child_acked_exit = true; 310 exit(0); 311 } 312 313 /* Attach to the child so it's marked as being debugged */ 314 ret = ptrace(PT_ATTACHEXC, pid, 0, 0); 315 T_EXPECT_POSIX_SUCCESS(ret, "ptrace PT_ATTACHEXC"); 316 317 /* And let the child know that it can carry on */ 318 shm->has_parent_connected = true; 319 320 /* And ensure the child has set up the memory */ 321 while (!(shm->has_child_populated_pointers)) { 322 sleep(1); 323 } 324 325 /* And we grab a task port */ 326 task_port_t target_task_port; 327 kern_return_t kr = task_for_pid(mach_task_self(), pid, &target_task_port); 328 T_ASSERT_MACH_SUCCESS(kr, "task_for_pid for target"); 329 330 /* When we request the pointers be rewritten with their MTE tags */ 331 mach_vm_offset_t resigned_addresses[count] = {0}; 332 mach_vm_offset_list_t input_list = &shm->addresses_to_tag[0]; 333 mach_vm_offset_list_t output_list = resigned_addresses; 334 335 kr = mach_vm_update_pointers_with_remote_tags( 336 target_task_port, 337 input_list, 338 count, 339 output_list, 340 &count 341 ); 342 T_ASSERT_MACH_SUCCESS(kr, "mach_vm_update_pointers_with_remote_tags"); 343 344 /* Then the pointers have been rewritten as expected */ 345 T_ASSERT_EQ_ULONG((unsigned long)output_list[0], (unsigned long)&(shm->tagged_ptr)[0], "Expected pointer 1 to be correctly rewritten"); 346 T_ASSERT_EQ_ULONG((unsigned long)output_list[1], (unsigned long)&(shm->differently_tagged_ptr)[halfway], "Expected pointer 2 to be correctly rewritten"); 347 /* A non-MTE-enabled object is returned as-is in the output list */ 348 T_ASSERT_EQ_ULONG((unsigned long)output_list[2], shm->untagged_ptr, "Expected a non-MTE address to be returned as-is"); 349 /* An invalid input pointer is returned as zero in the output list */ 350 T_ASSERT_EQ_ULONG((unsigned long)output_list[3], 0, "Expected an unmapped address to be transformed to 0"); 351 352 /* And let the child know that it's fine to exit */ 353 shm->has_parent_finished_inspecting_child = true; 354 while (!(shm->has_child_acked_exit)) { 355 sleep(1); 356 } 357 358 /* Cleanup */ 359 T_ASSERT_POSIX_SUCCESS(close(shm_fd), "Closed shm fd"); 360 T_ASSERT_POSIX_SUCCESS(shm_unlink(memory_path), "Unlinked"); 361 } 362 #endif /* __arm64__ */ 363