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