xref: /xnu-12377.41.6/tests/try_read_write_test.c (revision bbb1b6f9e71b8cdde6e5cd6f4841f207dee3d828)
1 /*
2  * Copyright (c) 2024 Apple Inc. All rights reserved.
3  *
4  * @APPLE_OSREFERENCE_LICENSE_HEADER_START@
5  *
6  * This file contains Original Code and/or Modifications of Original Code
7  * as defined in and that are subject to the Apple Public Source License
8  * Version 2.0 (the 'License'). You may not use this file except in
9  * compliance with the License. The rights granted to you under the License
10  * may not be used to create, or enable the creation or redistribution of,
11  * unlawful or unlicensed copies of an Apple operating system, or to
12  * circumvent, violate, or enable the circumvention or violation of, any
13  * terms of an Apple operating system software license agreement.
14  *
15  * Please obtain a copy of the License at
16  * http://www.opensource.apple.com/apsl/ and read it before using this file.
17  *
18  * The Original Code and all software distributed under the License are
19  * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
20  * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
21  * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
22  * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT.
23  * Please see the License for the specific language governing rights and
24  * limitations under the License.
25  *
26  * @APPLE_OSREFERENCE_LICENSE_HEADER_END@
27  */
28 
29 /*
30  * try_read_write_test.c
31  *
32  * Test the testing helper functions in try_read_write.h.
33  */
34 
35 #include <stdlib.h>
36 #include <stdint.h>
37 #include <stdbool.h>
38 #include <darwintest.h>
39 #include <mach/mach.h>
40 #include <mach/mach_vm.h>
41 #include <ptrauth.h>
42 
43 #include "try_read_write.h"
44 
45 T_GLOBAL_META(
46 	T_META_NAMESPACE("xnu"),
47 	T_META_RADAR_COMPONENT_NAME("xnu"),
48 	T_META_RADAR_COMPONENT_VERSION("vm"),
49 	T_META_RUN_CONCURRENTLY(true),
50 	T_META_ALL_VALID_ARCHS(true)
51 	);
52 
53 #define MAYBE_QUIET(quiet) \
54 	do {                                    \
55 	        if (quiet) {                    \
56 	                T_QUIET;                \
57 	        }                               \
58 	} while (0)
59 
60 static void
test_try_read_byte_maybe_quietly(mach_vm_address_t addr,uint8_t expected_byte,kern_return_t expected_error,bool quiet,const char * message)61 test_try_read_byte_maybe_quietly(
62 	mach_vm_address_t addr,
63 	uint8_t expected_byte,
64 	kern_return_t expected_error,
65 	bool quiet,
66 	const char *message)
67 {
68 	bool expected_result = (expected_error == 0);
69 	bool actual_result;
70 	uint8_t actual_byte;
71 	kern_return_t actual_error;
72 
73 	actual_result = try_read_byte(addr, &actual_byte, &actual_error);
74 
75 	MAYBE_QUIET(quiet); T_EXPECT_EQ(expected_result, actual_result, "%s: try_read_byte return value", message);
76 	MAYBE_QUIET(quiet); T_EXPECT_EQ(expected_error, actual_error, "%s: try_read_byte error code", message);
77 	if (expected_error == 0 && actual_error == 0) {
78 		MAYBE_QUIET(quiet); T_EXPECT_EQ(expected_byte, actual_byte, "%s: try_read_byte value read", message);
79 	}
80 }
81 
82 static void
test_try_read_byte(mach_vm_address_t addr,uint8_t expected_byte,kern_return_t expected_error,const char * message)83 test_try_read_byte(
84 	mach_vm_address_t addr,
85 	uint8_t expected_byte,
86 	kern_return_t expected_error,
87 	const char *message)
88 {
89 	test_try_read_byte_maybe_quietly(addr, expected_byte, expected_error, false /* quiet */, message);
90 }
91 
92 static void
test_try_read_byte_quietly(mach_vm_address_t addr,uint8_t expected_byte,kern_return_t expected_error,const char * message)93 test_try_read_byte_quietly(
94 	mach_vm_address_t addr,
95 	uint8_t expected_byte,
96 	kern_return_t expected_error,
97 	const char *message)
98 {
99 	test_try_read_byte_maybe_quietly(addr, expected_byte, expected_error, true /* quiet */, message);
100 }
101 
102 static void
test_try_write_byte_maybe_quietly(mach_vm_address_t addr,uint8_t expected_byte,kern_return_t expected_error,bool quiet,const char * message)103 test_try_write_byte_maybe_quietly(
104 	mach_vm_address_t addr,
105 	uint8_t expected_byte,
106 	kern_return_t expected_error,
107 	bool quiet,
108 	const char *message)
109 {
110 	bool expected_result = (expected_error == 0);
111 	bool actual_result;
112 	uint8_t actual_byte;
113 	kern_return_t actual_error;
114 
115 	actual_result = try_write_byte(addr, expected_byte, &actual_error);
116 
117 	MAYBE_QUIET(quiet); T_EXPECT_EQ(expected_result, actual_result, "%s: try_write_byte return value", message);
118 	MAYBE_QUIET(quiet); T_EXPECT_EQ(expected_error, actual_error, "%s: try_write_byte error code", message);
119 	if (expected_error == 0 && actual_error == 0) {
120 		actual_byte = *(volatile uint8_t *)addr;
121 		MAYBE_QUIET(quiet); T_EXPECT_EQ(expected_byte, actual_byte, "%s: try_write_byte value written", message);
122 	}
123 }
124 
125 static void
test_try_write_byte(mach_vm_address_t addr,uint8_t expected_byte,kern_return_t expected_error,const char * message)126 test_try_write_byte(
127 	mach_vm_address_t addr,
128 	uint8_t expected_byte,
129 	kern_return_t expected_error,
130 	const char *message)
131 {
132 	test_try_write_byte_maybe_quietly(addr, expected_byte, expected_error, false /* quiet */, message);
133 }
134 
135 static void
test_try_write_byte_quietly(mach_vm_address_t addr,uint8_t expected_byte,kern_return_t expected_error,const char * message)136 test_try_write_byte_quietly(
137 	mach_vm_address_t addr,
138 	uint8_t expected_byte,
139 	kern_return_t expected_error,
140 	const char *message)
141 {
142 	test_try_write_byte_maybe_quietly(addr, expected_byte, expected_error, true /* quiet */, message);
143 }
144 
145 static mach_vm_address_t
allocate_page_with_prot(vm_prot_t prot)146 allocate_page_with_prot(vm_prot_t prot)
147 {
148 	mach_vm_address_t addr;
149 	kern_return_t kr;
150 
151 	kr = mach_vm_allocate(mach_task_self(), &addr, PAGE_SIZE, VM_FLAGS_ANYWHERE);
152 	T_QUIET; T_ASSERT_MACH_SUCCESS(kr, "vm_allocate");
153 	kr = mach_vm_protect(mach_task_self(), addr, PAGE_SIZE, false /* set max */, prot);
154 	T_QUIET; T_ASSERT_MACH_SUCCESS(kr, "vm_protect");
155 	return addr;
156 }
157 
158 static void
deallocate_page(mach_vm_address_t addr)159 deallocate_page(mach_vm_address_t addr)
160 {
161 	kern_return_t kr = mach_vm_deallocate(mach_task_self(), addr, PAGE_SIZE);
162 	T_QUIET; T_ASSERT_MACH_SUCCESS(kr, "vm_deallocate");
163 }
164 
165 /*
166  * Generate some r-x memory with a known value.
167  */
168 static void __attribute__((naked))
instruction_byte_ff(void)169 instruction_byte_ff(void)
170 {
171 	asm(".quad 0xffffffff");
172 }
173 
174 T_DECL(try_read_write_test,
175     "test the test helper functions try_read_byte and try_write_byte")
176 {
177 	mach_vm_address_t addr;
178 
179 	/* read and write an unmapped address */
180 	test_try_read_byte(0, 0, KERN_INVALID_ADDRESS, "read unmapped address");
181 	test_try_write_byte(0, 0, KERN_INVALID_ADDRESS, "write unmapped address");
182 
183 	/* read and write --- */
184 	addr = allocate_page_with_prot(VM_PROT_NONE);
185 	test_try_read_byte(addr, 0, KERN_PROTECTION_FAILURE, "read prot ---");
186 	test_try_write_byte(addr, 1, KERN_PROTECTION_FAILURE, "write prot ---");
187 	deallocate_page(addr);
188 
189 	/* read and write r-- */
190 	addr = allocate_page_with_prot(VM_PROT_READ);
191 	test_try_read_byte(addr, 0, KERN_SUCCESS, "read prot r--");
192 	test_try_write_byte(addr, 1, KERN_PROTECTION_FAILURE, "write prot r--");
193 	deallocate_page(addr);
194 
195 	/* read and write -w- */
196 	addr = allocate_page_with_prot(VM_PROT_WRITE);
197 	test_try_read_byte(addr, 0, KERN_PROTECTION_FAILURE, "read prot -w-");
198 	test_try_write_byte(addr, 1, KERN_PROTECTION_FAILURE, "write prot -w-");
199 	deallocate_page(addr);
200 
201 	/* read and write rw- */
202 	addr = allocate_page_with_prot(VM_PROT_READ | VM_PROT_WRITE);
203 	*(uint8_t *)addr = 1;
204 	test_try_read_byte(addr, 1, KERN_SUCCESS, "read prot rw-");
205 	test_try_write_byte(addr, 2, KERN_SUCCESS, "write prot rw-");
206 	test_try_read_byte(addr, 2, KERN_SUCCESS, "read prot rw- again");
207 	deallocate_page(addr);
208 
209 	/* read and write r-x */
210 	addr = (mach_vm_address_t)ptrauth_strip(&instruction_byte_ff, ptrauth_key_function_pointer);
211 	test_try_read_byte(addr, 0xff, KERN_SUCCESS, "read prot r-x");
212 	test_try_write_byte(addr, 1, KERN_PROTECTION_FAILURE, "write prot r-x");
213 }
214 
215 
216 /* this test provokes THREAD_COUNT * REP_COUNT * PAGE_SIZE exceptions */
217 #define THREAD_COUNT 10
218 #define REP_COUNT 5
219 
220 struct test_alloc {
221 	mach_vm_address_t addr;
222 	vm_prot_t prot;
223 	kern_return_t expected_read_error;
224 	kern_return_t expected_write_error;
225 };
226 
227 static struct test_alloc
allocate_page_with_random_prot(void)228 allocate_page_with_random_prot(void)
229 {
230 	struct test_alloc result;
231 
232 	switch (random() % 4) {
233 	case 0:
234 		result.prot = VM_PROT_NONE;
235 		result.expected_read_error  = KERN_PROTECTION_FAILURE;
236 		result.expected_write_error = KERN_PROTECTION_FAILURE;
237 		break;
238 	case 1:
239 		result.prot = VM_PROT_READ;
240 		result.expected_read_error  = KERN_SUCCESS;
241 		result.expected_write_error = KERN_PROTECTION_FAILURE;
242 		break;
243 	case 2:
244 		result.prot = VM_PROT_WRITE;
245 		result.expected_read_error  = KERN_PROTECTION_FAILURE;
246 		result.expected_write_error = KERN_PROTECTION_FAILURE;
247 		break;
248 	case 3:
249 		result.prot = VM_PROT_READ | VM_PROT_WRITE;
250 		result.expected_read_error  = KERN_SUCCESS;
251 		result.expected_write_error = KERN_SUCCESS;
252 		break;
253 	}
254 
255 	result.addr = allocate_page_with_prot(result.prot);
256 	return result;
257 }
258 
259 static void *
multithreaded_test(void * arg)260 multithreaded_test(void *arg)
261 {
262 	struct test_alloc alloc = *(struct test_alloc *)arg;
263 
264 	/* Read and write a lot from our page. */
265 	for (int reps = 0; reps < REP_COUNT; reps++) {
266 		for (int offset = 0; offset < PAGE_SIZE; offset++) {
267 			test_try_read_byte_quietly(alloc.addr + offset, 0, alloc.expected_read_error, "thread read");
268 			test_try_write_byte_quietly(alloc.addr + offset, 0, alloc.expected_write_error, "thread write");
269 		}
270 	}
271 
272 	return NULL;
273 }
274 
275 T_DECL(try_read_write_test_multithreaded,
276     "test try_read_byte and try_write_byte from multiple threads")
277 {
278 	verbose_exc_helper = false;
279 
280 	pthread_t threads[THREAD_COUNT];
281 	struct test_alloc allocs[THREAD_COUNT];
282 
283 	/* each thread gets a page with a random prot to read and write on */
284 
285 	for (int i = 0; i < THREAD_COUNT; i++) {
286 		allocs[i] = allocate_page_with_random_prot();
287 	}
288 
289 	T_LOG("running %d threads each %d times", THREAD_COUNT, REP_COUNT);
290 
291 	for (int i = 0; i < THREAD_COUNT; i++) {
292 		pthread_create(&threads[i], NULL, multithreaded_test, &allocs[i]);
293 	}
294 
295 	for (int i = 0; i < THREAD_COUNT; i++) {
296 		pthread_join(threads[i], NULL);
297 		deallocate_page(allocs[i].addr);
298 	}
299 }
300