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 #include <TargetConditionals.h>
23 #error "These tests are only expected to run on macOS"
24 #endif // TARGET_OS_OSX
26 T_GLOBAL_META(T_META_RUN_CONCURRENTLY(true));
29 typedef enum _access_type
{
34 typedef enum _fault_strategy
{
43 fault_strategy_t fault_strategy
;
48 static void * rwx_addr
= NULL
;
49 static pthread_key_t jit_test_fault_state_key
;
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.
56 static uint32_t ret_encoding
= 0xe12fff1e;
57 #elif defined(__arm64__)
58 static uint32_t ret_encoding
= 0xd65f03c0;
59 #elif defined(__x86_64__)
60 static uint32_t ret_encoding
= 0x909090c3;;
62 #error "Unsupported architecture"
65 /* Allocate a fault_state_t, and associate it with the current thread. */
66 static fault_state_t
*
67 fault_state_create(void)
69 fault_state_t
* fault_state
= malloc(sizeof(fault_state_t
));
72 fault_state
->fault_count
= 0;
73 fault_state
->fault_strategy
= FAULT_STRAT_NONE
;
74 fault_state
->fault_expected
= false;
76 if (pthread_setspecific(jit_test_fault_state_key
, fault_state
)) {
85 /* Disassociate the given fault state from the current thread, and destroy it. */
87 fault_state_destroy(void * fault_state
)
89 if (fault_state
== NULL
) {
90 T_ASSERT_FAIL("Attempted to fault_state_destroy NULL");
97 * A signal handler that attempts to resolve anticipated faults through use of
98 * the pthread_jit_write_protect functions.
101 access_failed_handler(int signum
)
103 fault_state_t
* fault_state
;
105 /* This handler should ONLY handle SIGBUS. */
106 if (signum
!= SIGBUS
) {
107 T_ASSERT_FAIL("Unexpected signal sent to handler");
110 if (!(fault_state
= pthread_getspecific(jit_test_fault_state_key
))) {
111 T_ASSERT_FAIL("Failed to retrieve fault state");
114 if (!(fault_state
->fault_expected
)) {
115 T_ASSERT_FAIL("Unexpected fault taken");
118 /* We should not see a second fault. */
119 fault_state
->fault_expected
= false;
121 switch (fault_state
->fault_strategy
) {
122 case FAULT_STRAT_NONE
:
123 T_ASSERT_FAIL("No fault strategy");
125 /* Just in case we try to do something different. */
128 pthread_jit_write_protect_np(TRUE
);
131 pthread_jit_write_protect_np(FALSE
);
135 fault_state
->fault_count
++;
139 * Attempt the specified access; if the access faults, this will return true;
140 * otherwise, it will return false.
143 does_access_fault(access_type_t access_type
, void * addr
)
145 uint64_t old_fault_count
;
146 uint64_t new_fault_count
;
148 fault_state_t
* fault_state
;
150 struct sigaction old_action
; /* Save area for any existing action. */
151 struct sigaction new_action
; /* The action we wish to install for SIGBUS. */
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? */
162 T_ASSERT_FAIL("Access attempted against NULL");
165 if (!(fault_state
= pthread_getspecific(jit_test_fault_state_key
))) {
166 T_ASSERT_FAIL("Failed to retrieve fault state");
169 old_fault_count
= fault_state
->fault_count
;
171 /* Install a handler so that we can catch SIGBUS. */
172 sigaction(SIGBUS
, &new_action
, &old_action
);
174 /* Perform the requested operation. */
175 switch (access_type
) {
177 fault_state
->fault_strategy
= FAULT_STRAT_RW
;
178 fault_state
->fault_expected
= true;
180 __sync_synchronize();
182 /* Attempt to scrawl a return instruction to the given address. */
183 *((volatile uint32_t *)addr
) = ret_encoding
;
185 __sync_synchronize();
187 fault_state
->fault_expected
= false;
188 fault_state
->fault_strategy
= FAULT_STRAT_NONE
;
190 /* Invalidate the instruction cache line that we modified. */
191 sys_cache_control(kCacheFunctionPrepareForExecution
, addr
, sizeof(ret_encoding
));
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);
199 func
= (void (*)(void))addr
;
203 fault_state
->fault_strategy
= FAULT_STRAT_RX
;
204 fault_state
->fault_expected
= true;
206 __sync_synchronize();
211 __sync_synchronize();
213 fault_state
->fault_expected
= false;
214 fault_state
->fault_strategy
= FAULT_STRAT_NONE
;
219 /* Restore the old SIGBUS handler. */
220 sigaction(SIGBUS
, &old_action
, NULL
);
222 new_fault_count
= fault_state
->fault_count
;
224 if (new_fault_count
> old_fault_count
) {
225 /* Indicate that we took a fault. */
233 expect_write_fail_thread(__unused
void * arg
)
235 fault_state_create();
237 if (does_access_fault(ACCESS_WRITE
, rwx_addr
)) {
238 pthread_exit((void *)0);
240 pthread_exit((void *)1);
244 T_DECL(pthread_jit_write_protect
,
245 "Verify that the pthread_jit_write_protect interfaces work correctly")
248 size_t alloc_size
= PAGE_SIZE
;
249 fault_state_t
* fault_state
= NULL
;
251 bool key_created
= false;
252 void * join_value
= NULL
;
254 bool expect_fault
= pthread_jit_write_protect_supported_np();
258 /* Set up the necessary state for the test. */
259 err
= pthread_key_create(&jit_test_fault_state_key
, fault_state_destroy
);
261 T_ASSERT_POSIX_ZERO(err
, 0, "Create pthread key");
265 fault_state
= fault_state_create();
267 T_ASSERT_NOTNULL(fault_state
, "Create fault state");
270 * Create a JIT enabled mapping that we can use to test restriction of
273 rwx_addr
= mmap(addr
, alloc_size
, PROT_READ
| PROT_WRITE
| PROT_EXEC
, MAP_ANON
| MAP_PRIVATE
| MAP_JIT
, -1, 0);
275 T_ASSERT_NE_PTR(rwx_addr
, MAP_FAILED
, "Map range as MAP_JIT");
280 * Validate that we fault when we should, and that we do not fault when
281 * we should not fault.
283 pthread_jit_write_protect_np(FALSE
);
285 T_EXPECT_EQ(does_access_fault(ACCESS_WRITE
, rwx_addr
), 0, "Write with RWX->RW");
287 pthread_jit_write_protect_np(TRUE
);
289 T_EXPECT_EQ(does_access_fault(ACCESS_EXECUTE
, rwx_addr
), 0, "Execute with RWX->RX");
291 pthread_jit_write_protect_np(TRUE
);
293 T_EXPECT_EQ(does_access_fault(ACCESS_WRITE
, rwx_addr
), expect_fault
, "Write with RWX->RX");
295 pthread_jit_write_protect_np(FALSE
);
297 T_EXPECT_EQ(does_access_fault(ACCESS_EXECUTE
, rwx_addr
), expect_fault
, "Execute with RWX->RW");
299 pthread_jit_write_protect_np(FALSE
);
303 * Create another thread for testing multithreading; mark this as setup
304 * as this test is not targeted towards the pthread create/join APIs.
308 T_ASSERT_POSIX_ZERO(pthread_create(&pthread
, NULL
, expect_write_fail_thread
, NULL
), "pthread_create expect_write_fail_thread");
310 T_ASSERT_POSIX_ZERO(pthread_join(pthread
, &join_value
), "pthread_join expect_write_fail_thread");
315 * Validate that the other thread was unable to write to the JIT region
316 * without independently using the pthread_jit_write_protect code.
318 T_ASSERT_NULL((join_value
), "Write on other thread with RWX->RX, "
319 "RWX->RW on parent thread");
322 /* We're done with the test; tear down our extra state. */
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).
330 T_ASSERT_POSIX_SUCCESS(munmap(rwx_addr
, alloc_size
), "Unmap MAP_JIT mapping");
333 T_ASSERT_POSIX_ZERO(pthread_setspecific(jit_test_fault_state_key
, NULL
), "Remove fault_state");
335 fault_state_destroy(fault_state
);
339 T_ASSERT_POSIX_ZERO(pthread_key_delete(jit_test_fault_state_key
), "Delete fault state key");
345 T_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))
349 dt_stat_time_t dt_stat_time
;
351 dt_stat_time
= dt_stat_time_create("rx->rw->rx time");
353 T_STAT_MEASURE_LOOP(dt_stat_time
) {
354 pthread_jit_write_protect_np(FALSE
);
355 pthread_jit_write_protect_np(TRUE
);
358 dt_stat_finalize(dt_stat_time
);