xref: /xnu-12377.81.4/tests/arm_mte_alias_restriction.c (revision 043036a2b3718f7f0be807e2870f8f47d3fa0796)
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