pthread_jit_write_test_inline.h [plain text]
#ifndef __PTHREAD_JIT_WRITE_TEST_INLINE_H__
#define __PTHREAD_JIT_WRITE_TEST_INLINE_H__
#include <darwintest.h>
#include <sys/mman.h>
#include <errno.h>
#include <fcntl.h>
#include <stdint.h>
#include <libkern/OSCacheControl.h>
#include <unistd.h>
#include <signal.h>
#include <stdlib.h>
#include <setjmp.h>
#include <mach/vm_param.h>
#include <pthread.h>
#include <pthread/jit_private.h>
#if __has_include(<ptrauth.h>)
#include <ptrauth.h>
#endif
#include <TargetConditionals.h>
#if !TARGET_OS_OSX
#error "These tests are only expected to run on macOS"
#endif // TARGET_OS_OSX
typedef enum _access_type {
ACCESS_WRITE,
ACCESS_EXECUTE,
} access_type_t;
typedef struct {
bool fault_expected;
jmp_buf fault_jmp;
} fault_state_t;
static void * rwx_addr = NULL;
static pthread_key_t jit_test_fault_state_key;
#ifdef __arm__
static uint32_t ret_encoding = 0xe12fff1e;
#elif defined(__arm64__)
static uint32_t ret_encoding = 0xd65f03c0;
#elif defined(__x86_64__)
static uint32_t ret_encoding = 0x909090c3;
#else
#error "Unsupported architecture"
#endif
static fault_state_t *
fault_state_create(void)
{
fault_state_t * fault_state = malloc(sizeof(fault_state_t));
if (fault_state) {
fault_state->fault_expected = false;
if (pthread_setspecific(jit_test_fault_state_key, fault_state)) {
free(fault_state);
fault_state = NULL;
}
}
return fault_state;
}
static void
fault_state_destroy(void * fault_state)
{
if (fault_state == NULL) {
T_ASSERT_FAIL("Attempted to fault_state_destroy NULL");
}
free(fault_state);
}
static fault_state_t *g_fault_state = NULL;
static bool key_created = false;
static void
pthread_jit_test_setup(void)
{
T_SETUPBEGIN;
int err = pthread_key_create(&jit_test_fault_state_key, fault_state_destroy);
T_ASSERT_POSIX_ZERO(err, 0, "Create pthread key");
key_created = true;
g_fault_state = fault_state_create();
T_ASSERT_NOTNULL(g_fault_state, "Create fault state");
rwx_addr = mmap(NULL, PAGE_SIZE, PROT_READ | PROT_WRITE | PROT_EXEC, MAP_ANON | MAP_PRIVATE | MAP_JIT, -1, 0);
T_ASSERT_NE_PTR(rwx_addr, MAP_FAILED, "Map range as MAP_JIT");
T_SETUPEND;
}
static void
pthread_jit_test_teardown(void)
{
T_SETUPBEGIN;
T_ASSERT_POSIX_SUCCESS(munmap(rwx_addr, PAGE_SIZE), "Unmap MAP_JIT mapping");
if (g_fault_state) {
T_ASSERT_POSIX_ZERO(pthread_setspecific(jit_test_fault_state_key, NULL), "Remove fault_state");
fault_state_destroy(g_fault_state);
}
if (key_created) {
T_ASSERT_POSIX_ZERO(pthread_key_delete(jit_test_fault_state_key), "Delete fault state key");
}
T_SETUPEND;
}
static void
access_failed_handler(int signum)
{
fault_state_t * fault_state;
if (signum != SIGBUS) {
T_ASSERT_FAIL("Unexpected signal sent to handler");
}
if (!(fault_state = pthread_getspecific(jit_test_fault_state_key))) {
T_ASSERT_FAIL("Failed to retrieve fault state");
}
if (!(fault_state->fault_expected)) {
T_ASSERT_FAIL("Unexpected fault taken");
}
longjmp(fault_state->fault_jmp, 1);
}
static bool
does_access_fault(access_type_t access_type, void * addr)
{
fault_state_t * fault_state;
struct sigaction old_action;
struct sigaction new_action;
bool faulted = false;
void (*func)(void);
new_action.sa_handler = access_failed_handler;
new_action.sa_mask = 0;
new_action.sa_flags = 0;
if (addr == NULL) {
T_ASSERT_FAIL("Access attempted against NULL");
}
if (!(fault_state = pthread_getspecific(jit_test_fault_state_key))) {
T_ASSERT_FAIL("Failed to retrieve fault state");
}
sigaction(SIGBUS, &new_action, &old_action);
int jmprc = setjmp(fault_state->fault_jmp);
if (jmprc) {
faulted = true;
goto done;
}
switch (access_type) {
case ACCESS_WRITE:
fault_state->fault_expected = true;
__sync_synchronize();
*((volatile uint32_t *)addr) = ret_encoding;
__sync_synchronize();
fault_state->fault_expected = false;
sys_cache_control(kCacheFunctionPrepareForExecution, addr, sizeof(ret_encoding));
break;
case ACCESS_EXECUTE:
#if __has_feature(ptrauth_calls)
func = ptrauth_sign_unauthenticated((void *)addr, ptrauth_key_function_pointer, 0);
#else
func = (void (*)(void))addr;
#endif
fault_state->fault_expected = true;
__sync_synchronize();
func();
__sync_synchronize();
fault_state->fault_expected = false;
break;
}
done:
sigaction(SIGBUS, &old_action, NULL);
return faulted;
}
#endif // __PTHREAD_JIT_WRITE_TEST_INLINE_H__