]> git.saurik.com Git - apple/libpthread.git/blame - tests/pthread_jit_write_protection.c
libpthread-454.40.3.tar.gz
[apple/libpthread.git] / tests / pthread_jit_write_protection.c
CommitLineData
c1f56ec9
A
1#include <darwintest.h>
2#include <darwintest_perf.h>
3
4#include <sys/mman.h>
5#include <errno.h>
6#include <fcntl.h>
7#include <stdint.h>
8#include <libkern/OSCacheControl.h>
9#include <unistd.h>
10#include <signal.h>
11#include <stdlib.h>
12
13#include <mach/vm_param.h>
14#include <pthread.h>
15
16#if __has_include(<ptrauth.h>)
17#include <ptrauth.h>
18#endif
19
20T_GLOBAL_META(T_META_RUN_CONCURRENTLY(true));
21
22/* Enumerations */
23typedef enum _access_type {
24 ACCESS_WRITE,
25 ACCESS_EXECUTE,
26} access_type_t;
27
28typedef enum _fault_strategy {
29 FAULT_STRAT_NONE,
30 FAULT_STRAT_RX,
31 FAULT_STRAT_RW
32} fault_strategy_t;
33
34/* Structures */
35typedef struct {
36 uint64_t fault_count;
37 fault_strategy_t fault_strategy;
38 bool fault_expected;
39} fault_state_t;
40
41/* Globals */
42static void * rwx_addr = NULL;
43static pthread_key_t jit_test_fault_state_key;
44
45/*
46 * Return instruction encodings; a default value is given so that this test can
47 * be built for an architecture that may not support the tested feature.
48 */
49#ifdef __arm__
50static uint32_t ret_encoding = 0xe12fff1e;
51#elif defined(__arm64__)
52static uint32_t ret_encoding = 0xd65f03c0;
53#elif defined(__x86_64__)
54static uint32_t ret_encoding = 0x909090c3;;
55#else
56#error "Unsupported architecture"
57#endif
58
59/* Allocate a fault_state_t, and associate it with the current thread. */
60static fault_state_t *
61fault_state_create(void)
62{
63 fault_state_t * fault_state = malloc(sizeof(fault_state_t));
64
65 if (fault_state) {
66 fault_state->fault_count = 0;
67 fault_state->fault_strategy = FAULT_STRAT_NONE;
68 fault_state->fault_expected = false;
69
70 if (pthread_setspecific(jit_test_fault_state_key, fault_state)) {
71 free(fault_state);
72 fault_state = NULL;
73 }
74 }
75
76 return fault_state;
77}
78
79/* Disassociate the given fault state from the current thread, and destroy it. */
80static void
81fault_state_destroy(void * fault_state)
82{
83 if (fault_state == NULL) {
84 T_ASSERT_FAIL("Attempted to fault_state_destroy NULL");
85 }
86
87 free(fault_state);
88}
89
90/*
91 * A signal handler that attempts to resolve anticipated faults through use of
92 * the pthread_jit_write_protect functions.
93 */
94static void
95access_failed_handler(int signum)
96{
97 fault_state_t * fault_state;
98
99 /* This handler should ONLY handle SIGBUS. */
100 if (signum != SIGBUS) {
101 T_ASSERT_FAIL("Unexpected signal sent to handler");
102 }
103
104 if (!(fault_state = pthread_getspecific(jit_test_fault_state_key))) {
105 T_ASSERT_FAIL("Failed to retrieve fault state");
106 }
107
108 if (!(fault_state->fault_expected)) {
109 T_ASSERT_FAIL("Unexpected fault taken");
110 }
111
112 /* We should not see a second fault. */
113 fault_state->fault_expected = false;
114
115 switch (fault_state->fault_strategy) {
116 case FAULT_STRAT_NONE:
117 T_ASSERT_FAIL("No fault strategy");
118
119 /* Just in case we try to do something different. */
120 break;
121 case FAULT_STRAT_RX:
122 pthread_jit_write_protect_np(TRUE);
123 break;
124 case FAULT_STRAT_RW:
125 pthread_jit_write_protect_np(FALSE);
126 break;
127 }
128
129 fault_state->fault_count++;
130}
131
132/*
133 * Attempt the specified access; if the access faults, this will return true;
134 * otherwise, it will return false.
135 */
136static bool
137does_access_fault(access_type_t access_type, void * addr)
138{
139 uint64_t old_fault_count;
140 uint64_t new_fault_count;
141
142 fault_state_t * fault_state;
143
144 struct sigaction old_action; /* Save area for any existing action. */
145 struct sigaction new_action; /* The action we wish to install for SIGBUS. */
146
147 bool retval = false;
148
149 void (*func)(void);
150
151 new_action.sa_handler = access_failed_handler; /* A handler for write failures. */
152 new_action.sa_mask = 0; /* Don't modify the mask. */
153 new_action.sa_flags = 0; /* Flags? Who needs those? */
154
155 if (addr == NULL) {
156 T_ASSERT_FAIL("Access attempted against NULL");
157 }
158
159 if (!(fault_state = pthread_getspecific(jit_test_fault_state_key))) {
160 T_ASSERT_FAIL("Failed to retrieve fault state");
161 }
162
163 old_fault_count = fault_state->fault_count;
164
165 /* Install a handler so that we can catch SIGBUS. */
166 sigaction(SIGBUS, &new_action, &old_action);
167
168 /* Perform the requested operation. */
169 switch (access_type) {
170 case ACCESS_WRITE:
171 fault_state->fault_strategy = FAULT_STRAT_RW;
172 fault_state->fault_expected = true;
173
174 __sync_synchronize();
175
176 /* Attempt to scrawl a return instruction to the given address. */
177 *((volatile uint32_t *)addr) = ret_encoding;
178
179 __sync_synchronize();
180
181 fault_state->fault_expected = false;
182 fault_state->fault_strategy = FAULT_STRAT_NONE;
183
184 /* Invalidate the instruction cache line that we modified. */
185 sys_cache_control(kCacheFunctionPrepareForExecution, addr, sizeof(ret_encoding));
186
187 break;
188 case ACCESS_EXECUTE:
189 /* This is a request to branch to the given address. */
190#if __has_feature(ptrauth_calls)
191 func = ptrauth_sign_unauthenticated((void *)addr, ptrauth_key_function_pointer, 0);
192#else
193 func = (void (*)(void))addr;
194#endif
195
196
197 fault_state->fault_strategy = FAULT_STRAT_RX;
198 fault_state->fault_expected = true;
199
200 __sync_synchronize();
201
202 /* Branch. */
203 func();
204
205 __sync_synchronize();
206
207 fault_state->fault_expected = false;
208 fault_state->fault_strategy = FAULT_STRAT_NONE;
209
210 break;
211 }
212
213 /* Restore the old SIGBUS handler. */
214 sigaction(SIGBUS, &old_action, NULL);
215
216 new_fault_count = fault_state->fault_count;
217
218 if (new_fault_count > old_fault_count) {
219 /* Indicate that we took a fault. */
220 retval = true;
221 }
222
223 return retval;
224}
225
226static void *
227expect_write_fail_thread(__unused void * arg)
228{
229 fault_state_create();
230
231 if (does_access_fault(ACCESS_WRITE, rwx_addr)) {
232 pthread_exit((void *)0);
233 } else {
234 pthread_exit((void *)1);
235 }
236}
237
238T_DECL(pthread_jit_write_protect,
239 "Verify that the pthread_jit_write_protect interfaces work correctly")
240{
241 void * addr = NULL;
242 size_t alloc_size = PAGE_SIZE;
243 fault_state_t * fault_state = NULL;
244 int err = 0;
245 bool key_created = false;
246 void * join_value = NULL;
247 pthread_t pthread;
248 bool expect_fault = pthread_jit_write_protect_supported_np();
249
250 T_SETUPBEGIN;
251
252 /* Set up the necessary state for the test. */
253 err = pthread_key_create(&jit_test_fault_state_key, fault_state_destroy);
254
255 T_ASSERT_POSIX_ZERO(err, 0, "Create pthread key");
256
257 key_created = true;
258
259 fault_state = fault_state_create();
260
261 T_ASSERT_NOTNULL(fault_state, "Create fault state");
262
263 /*
264 * Create a JIT enabled mapping that we can use to test restriction of
265 * RWX mappings.
266 */
267 rwx_addr = mmap(addr, alloc_size, PROT_READ | PROT_WRITE | PROT_EXEC, MAP_ANON | MAP_PRIVATE | MAP_JIT, -1, 0);
268
269 T_ASSERT_NE_PTR(rwx_addr, MAP_FAILED, "Map range as MAP_JIT");
270
271 T_SETUPEND;
272
273 /*
274 * Validate that we fault when we should, and that we do not fault when
275 * we should not fault.
276 */
277 pthread_jit_write_protect_np(FALSE);
278
279 T_EXPECT_EQ(does_access_fault(ACCESS_WRITE, rwx_addr), 0, "Write with RWX->RW");
280
281 pthread_jit_write_protect_np(TRUE);
282
283 T_EXPECT_EQ(does_access_fault(ACCESS_EXECUTE, rwx_addr), 0, "Execute with RWX->RX");
284
285 pthread_jit_write_protect_np(TRUE);
286
287 T_EXPECT_EQ(does_access_fault(ACCESS_WRITE, rwx_addr), expect_fault, "Write with RWX->RX");
288
289 pthread_jit_write_protect_np(FALSE);
290
291 T_EXPECT_EQ(does_access_fault(ACCESS_EXECUTE, rwx_addr), expect_fault, "Execute with RWX->RW");
292
293 pthread_jit_write_protect_np(FALSE);
294
295 if (expect_fault) {
296 /*
297 * Create another thread for testing multithreading; mark this as setup
298 * as this test is not targeted towards the pthread create/join APIs.
299 */
300 T_SETUPBEGIN;
301
302 T_ASSERT_POSIX_ZERO(pthread_create(&pthread, NULL, expect_write_fail_thread, NULL), "pthread_create expect_write_fail_thread");
303
304 T_ASSERT_POSIX_ZERO(pthread_join(pthread, &join_value), "pthread_join expect_write_fail_thread");
305
306 T_SETUPEND;
307
308 /*
309 * Validate that the other thread was unable to write to the JIT region
310 * without independently using the pthread_jit_write_protect code.
311 */
312 T_ASSERT_NULL((join_value), "Write on other thread with RWX->RX, "
313 "RWX->RW on parent thread");
314 }
315
316 /* We're done with the test; tear down our extra state. */
317 /*
318 * This would be better dealt with using T_ATEND, but this would require
319 * making many variables global. This can be changed in the future.
320 * For now, mark this as SETUP (even though this is really teardown).
321 */
322 T_SETUPBEGIN;
323
324 T_ASSERT_POSIX_SUCCESS(munmap(rwx_addr, alloc_size), "Unmap MAP_JIT mapping");
325
326 if (fault_state) {
327 T_ASSERT_POSIX_ZERO(pthread_setspecific(jit_test_fault_state_key, NULL), "Remove fault_state");
328
329 fault_state_destroy(fault_state);
330 }
331
332 if (key_created) {
333 T_ASSERT_POSIX_ZERO(pthread_key_delete(jit_test_fault_state_key), "Delete fault state key");
334 }
335
336 T_SETUPEND;
337}
338
339T_DECL(thread_self_restrict_rwx_perf,
340 "Test the performance of the thread_self_restrict_rwx interfaces",
341 T_META_TAG_PERF, T_META_CHECK_LEAKS(false))
342{
343 dt_stat_time_t dt_stat_time;
344
345 dt_stat_time = dt_stat_time_create("rx->rw->rx time");
346
347 T_STAT_MEASURE_LOOP(dt_stat_time) {
348 pthread_jit_write_protect_np(FALSE);
349 pthread_jit_write_protect_np(TRUE);
350 }
351
352 dt_stat_finalize(dt_stat_time);
353}