1 #include <darwintest.h>
2 #include <darwintest_perf.h>
8 #include <libkern/OSCacheControl.h>
13 #include <mach/vm_param.h>
16 #if __has_include(<ptrauth.h>)
20 T_GLOBAL_META(T_META_RUN_CONCURRENTLY(true));
23 typedef enum _access_type
{
28 typedef enum _fault_strategy
{
37 fault_strategy_t fault_strategy
;
42 static void * rwx_addr
= NULL
;
43 static pthread_key_t jit_test_fault_state_key
;
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.
50 static uint32_t ret_encoding
= 0xe12fff1e;
51 #elif defined(__arm64__)
52 static uint32_t ret_encoding
= 0xd65f03c0;
53 #elif defined(__x86_64__)
54 static uint32_t ret_encoding
= 0x909090c3;;
56 #error "Unsupported architecture"
59 /* Allocate a fault_state_t, and associate it with the current thread. */
60 static fault_state_t
*
61 fault_state_create(void)
63 fault_state_t
* fault_state
= malloc(sizeof(fault_state_t
));
66 fault_state
->fault_count
= 0;
67 fault_state
->fault_strategy
= FAULT_STRAT_NONE
;
68 fault_state
->fault_expected
= false;
70 if (pthread_setspecific(jit_test_fault_state_key
, fault_state
)) {
79 /* Disassociate the given fault state from the current thread, and destroy it. */
81 fault_state_destroy(void * fault_state
)
83 if (fault_state
== NULL
) {
84 T_ASSERT_FAIL("Attempted to fault_state_destroy NULL");
91 * A signal handler that attempts to resolve anticipated faults through use of
92 * the pthread_jit_write_protect functions.
95 access_failed_handler(int signum
)
97 fault_state_t
* fault_state
;
99 /* This handler should ONLY handle SIGBUS. */
100 if (signum
!= SIGBUS
) {
101 T_ASSERT_FAIL("Unexpected signal sent to handler");
104 if (!(fault_state
= pthread_getspecific(jit_test_fault_state_key
))) {
105 T_ASSERT_FAIL("Failed to retrieve fault state");
108 if (!(fault_state
->fault_expected
)) {
109 T_ASSERT_FAIL("Unexpected fault taken");
112 /* We should not see a second fault. */
113 fault_state
->fault_expected
= false;
115 switch (fault_state
->fault_strategy
) {
116 case FAULT_STRAT_NONE
:
117 T_ASSERT_FAIL("No fault strategy");
119 /* Just in case we try to do something different. */
122 pthread_jit_write_protect_np(TRUE
);
125 pthread_jit_write_protect_np(FALSE
);
129 fault_state
->fault_count
++;
133 * Attempt the specified access; if the access faults, this will return true;
134 * otherwise, it will return false.
137 does_access_fault(access_type_t access_type
, void * addr
)
139 uint64_t old_fault_count
;
140 uint64_t new_fault_count
;
142 fault_state_t
* fault_state
;
144 struct sigaction old_action
; /* Save area for any existing action. */
145 struct sigaction new_action
; /* The action we wish to install for SIGBUS. */
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? */
156 T_ASSERT_FAIL("Access attempted against NULL");
159 if (!(fault_state
= pthread_getspecific(jit_test_fault_state_key
))) {
160 T_ASSERT_FAIL("Failed to retrieve fault state");
163 old_fault_count
= fault_state
->fault_count
;
165 /* Install a handler so that we can catch SIGBUS. */
166 sigaction(SIGBUS
, &new_action
, &old_action
);
168 /* Perform the requested operation. */
169 switch (access_type
) {
171 fault_state
->fault_strategy
= FAULT_STRAT_RW
;
172 fault_state
->fault_expected
= true;
174 __sync_synchronize();
176 /* Attempt to scrawl a return instruction to the given address. */
177 *((volatile uint32_t *)addr
) = ret_encoding
;
179 __sync_synchronize();
181 fault_state
->fault_expected
= false;
182 fault_state
->fault_strategy
= FAULT_STRAT_NONE
;
184 /* Invalidate the instruction cache line that we modified. */
185 sys_cache_control(kCacheFunctionPrepareForExecution
, addr
, sizeof(ret_encoding
));
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);
193 func
= (void (*)(void))addr
;
197 fault_state
->fault_strategy
= FAULT_STRAT_RX
;
198 fault_state
->fault_expected
= true;
200 __sync_synchronize();
205 __sync_synchronize();
207 fault_state
->fault_expected
= false;
208 fault_state
->fault_strategy
= FAULT_STRAT_NONE
;
213 /* Restore the old SIGBUS handler. */
214 sigaction(SIGBUS
, &old_action
, NULL
);
216 new_fault_count
= fault_state
->fault_count
;
218 if (new_fault_count
> old_fault_count
) {
219 /* Indicate that we took a fault. */
227 expect_write_fail_thread(__unused
void * arg
)
229 fault_state_create();
231 if (does_access_fault(ACCESS_WRITE
, rwx_addr
)) {
232 pthread_exit((void *)0);
234 pthread_exit((void *)1);
238 T_DECL(pthread_jit_write_protect
,
239 "Verify that the pthread_jit_write_protect interfaces work correctly")
242 size_t alloc_size
= PAGE_SIZE
;
243 fault_state_t
* fault_state
= NULL
;
245 bool key_created
= false;
246 void * join_value
= NULL
;
248 bool expect_fault
= pthread_jit_write_protect_supported_np();
252 /* Set up the necessary state for the test. */
253 err
= pthread_key_create(&jit_test_fault_state_key
, fault_state_destroy
);
255 T_ASSERT_POSIX_ZERO(err
, 0, "Create pthread key");
259 fault_state
= fault_state_create();
261 T_ASSERT_NOTNULL(fault_state
, "Create fault state");
264 * Create a JIT enabled mapping that we can use to test restriction of
267 rwx_addr
= mmap(addr
, alloc_size
, PROT_READ
| PROT_WRITE
| PROT_EXEC
, MAP_ANON
| MAP_PRIVATE
| MAP_JIT
, -1, 0);
269 T_ASSERT_NE_PTR(rwx_addr
, MAP_FAILED
, "Map range as MAP_JIT");
274 * Validate that we fault when we should, and that we do not fault when
275 * we should not fault.
277 pthread_jit_write_protect_np(FALSE
);
279 T_EXPECT_EQ(does_access_fault(ACCESS_WRITE
, rwx_addr
), 0, "Write with RWX->RW");
281 pthread_jit_write_protect_np(TRUE
);
283 T_EXPECT_EQ(does_access_fault(ACCESS_EXECUTE
, rwx_addr
), 0, "Execute with RWX->RX");
285 pthread_jit_write_protect_np(TRUE
);
287 T_EXPECT_EQ(does_access_fault(ACCESS_WRITE
, rwx_addr
), expect_fault
, "Write with RWX->RX");
289 pthread_jit_write_protect_np(FALSE
);
291 T_EXPECT_EQ(does_access_fault(ACCESS_EXECUTE
, rwx_addr
), expect_fault
, "Execute with RWX->RW");
293 pthread_jit_write_protect_np(FALSE
);
297 * Create another thread for testing multithreading; mark this as setup
298 * as this test is not targeted towards the pthread create/join APIs.
302 T_ASSERT_POSIX_ZERO(pthread_create(&pthread
, NULL
, expect_write_fail_thread
, NULL
), "pthread_create expect_write_fail_thread");
304 T_ASSERT_POSIX_ZERO(pthread_join(pthread
, &join_value
), "pthread_join expect_write_fail_thread");
309 * Validate that the other thread was unable to write to the JIT region
310 * without independently using the pthread_jit_write_protect code.
312 T_ASSERT_NULL((join_value
), "Write on other thread with RWX->RX, "
313 "RWX->RW on parent thread");
316 /* We're done with the test; tear down our extra state. */
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).
324 T_ASSERT_POSIX_SUCCESS(munmap(rwx_addr
, alloc_size
), "Unmap MAP_JIT mapping");
327 T_ASSERT_POSIX_ZERO(pthread_setspecific(jit_test_fault_state_key
, NULL
), "Remove fault_state");
329 fault_state_destroy(fault_state
);
333 T_ASSERT_POSIX_ZERO(pthread_key_delete(jit_test_fault_state_key
), "Delete fault state key");
339 T_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))
343 dt_stat_time_t dt_stat_time
;
345 dt_stat_time
= dt_stat_time_create("rx->rw->rx time");
347 T_STAT_MEASURE_LOOP(dt_stat_time
) {
348 pthread_jit_write_protect_np(FALSE
);
349 pthread_jit_write_protect_np(TRUE
);
352 dt_stat_finalize(dt_stat_time
);