#include <assert.h>
#include <stdlib.h>
#include <pthread.h>
#include <unistd.h>
#include <dispatch/dispatch.h>
#include <sys/mman.h>
#include <darwintest.h>
#define T_LOG_VERBOSE(...)
#ifdef __LP64__
#define STACK_LOCATIONS 16
#else
#define STACK_LOCATIONS 8
#endif
static void*
thread_routine(void *loc)
{
int foo;
*(uintptr_t*)loc = (uintptr_t)&foo;
return NULL;
}
static int
pointer_compare(const void *ap, const void *bp)
{
uintptr_t a = *(const uintptr_t*)ap;
uintptr_t b = *(const uintptr_t*)bp;
return a > b ? 1 : a < b ? -1 : 0;
}
static void
test_stack_aslr(bool workqueue_thread)
{
const int attempts = 128;
int attempt_round = 0;
uintptr_t *addr_array = mmap(NULL, sizeof(uintptr_t) * attempts,
PROT_READ|PROT_WRITE, MAP_SHARED|MAP_ANON, -1, 0);
T_QUIET; T_ASSERT_NOTNULL(addr_array, NULL);
again:
bzero(addr_array, sizeof(uintptr_t) * attempts);
for (int i = 0; i < attempts; i++) {
pid_t pid = fork();
T_QUIET; T_ASSERT_POSIX_SUCCESS(pid, "[%d] fork()", i);
if (pid) { pid = waitpid(pid, NULL, 0);
T_QUIET; T_ASSERT_POSIX_SUCCESS(pid, "[%d] waitpid()", i);
} else if (workqueue_thread) { dispatch_async(dispatch_get_global_queue(0,0), ^{
int foo;
addr_array[i] = (uintptr_t)&foo;
exit(0);
});
while (true) sleep(1);
} else { pthread_t th;
int ret = pthread_create(&th, NULL, thread_routine, &addr_array[i]);
assert(ret == 0);
ret = pthread_join(th, NULL);
assert(ret == 0);
exit(0);
}
}
qsort(addr_array, attempts, sizeof(uintptr_t), pointer_compare);
T_LOG("Stack address range: %p - %p (+%lx)", (void*)addr_array[0], (void*)addr_array[attempts-1],
addr_array[attempts-1] - addr_array[0]);
int unique_values = 0;
T_LOG_VERBOSE("[%p]", (void*)addr_array[0]);
for (int i = 1; i < attempts; i++) {
T_LOG_VERBOSE("[%p]", (void*)addr_array[i]);
if (addr_array[i-1] != addr_array[i]) {
unique_values++;
}
}
if (attempt_round < 3) T_MAYFAIL;
T_EXPECT_GE(unique_values, STACK_LOCATIONS, "Should have more than %d unique stack locations", STACK_LOCATIONS);
if (attempt_round++ < 3 && unique_values < STACK_LOCATIONS) goto again;
}
T_DECL(pthread_stack_aslr, "Confirm that stacks are ASLRed", T_META_CHECK_LEAKS(NO),
T_META_ALL_VALID_ARCHS(YES))
{
test_stack_aslr(false);
}
T_DECL(wq_stack_aslr, "Confirm that workqueue stacks are ASLRed", T_META_CHECK_LEAKS(NO),
T_META_ALL_VALID_ARCHS(YES))
{
test_stack_aslr(true);
}