#include <assert.h>
#include <stdlib.h>
#include <pthread.h>
#include <unistd.h>
#include <dispatch/dispatch.h>
#include <sys/mman.h>
#include <stdatomic.h>
#include "darwintest_defaults.h"
#include <darwintest_multiprocess.h>
#include <darwintest_utils.h>
#define T_LOG_VERBOSE(...)
#ifndef T_MAYFAIL_WITH_REASON
#define T_MAYFAIL_WITH_REASON(x) T_MAYFAIL
#endif
#ifdef __LP64__
#define STACK_LOCATIONS 16
#else
#define STACK_LOCATIONS 8
#endif
static const int attempts = 128, attempt_rounds = 3;
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;
}
typedef struct shmem_s {
_Atomic int ctr, done;
uintptr_t addr_array[attempts];
} *shmem_t;
static shmem_t
test_shmem_open(const char* shmem_name, int creatflags)
{
int fd = open(shmem_name, O_RDWR | creatflags, 0600);
T_QUIET; T_ASSERT_POSIX_SUCCESS(fd, "open temp file");
if (creatflags) {
T_QUIET; T_ASSERT_POSIX_SUCCESS(ftruncate(fd,
sizeof(struct shmem_s)), "resize temp file");
}
shmem_t shmem = mmap(NULL, sizeof(struct shmem_s),
PROT_READ|PROT_WRITE, MAP_SHARED, fd, 0);
T_QUIET; T_ASSERT_NOTNULL(shmem, "mmap shmem");
T_QUIET; T_ASSERT_POSIX_SUCCESS(close(fd), "close temp file");
return shmem;
}
static uintptr_t*
test_shmem_start(shmem_t shmem)
{
int idx = atomic_fetch_add(&shmem->ctr, 1);
return &shmem->addr_array[idx];
}
static void
test_shmem_end(shmem_t shmem)
{
atomic_fetch_add(&shmem->done, 1);
}
T_HELPER_DECL(wq_stack_aslr_helper,
"Confirm that workqueue stacks are ASLRed (Helper)")
{
shmem_t shmem = test_shmem_open(argv[0], 0);
uintptr_t *addr = test_shmem_start(shmem);
dispatch_group_t g = dispatch_group_create();
dispatch_group_async_f(g, dispatch_get_global_queue(0,0), addr,
(dispatch_function_t)thread_routine);
dispatch_group_wait(g, DISPATCH_TIME_FOREVER);
dispatch_release(g);
test_shmem_end(shmem);
}
T_HELPER_DECL(pthread_stack_aslr_helper,
"Confirm that stacks are ASLRed (Helper)")
{
shmem_t shmem = test_shmem_open(argv[0], 0);
uintptr_t *addr = test_shmem_start(shmem);
pthread_t th;
int ret = pthread_create(&th, NULL, thread_routine, addr);
assert(ret == 0);
ret = pthread_join(th, NULL);
assert(ret == 0);
test_shmem_end(shmem);
}
static void
test_stack_aslr(bool workqueue_thread)
{
const char *tmpdir = dt_tmpdir();
char *tmp;
asprintf(&tmp, "%s/pthread_stack_aslr_XXXXX", tmpdir);
T_QUIET; T_ASSERT_NOTNULL(mkdtemp(tmp), "mkdtemp");
char *shmem_name;
asprintf(&shmem_name, "%s/shmem", tmp);
shmem_t shmem = test_shmem_open(shmem_name, O_CREAT|O_EXCL);
uintptr_t *addr_array = shmem->addr_array;
dt_helper_t helpers[attempts * attempt_rounds];
const char* helper = workqueue_thread ? "wq_stack_aslr_helper" :
"pthread_stack_aslr_helper";
char *helper_args[] = {shmem_name, NULL};
size_t helper_idx = 0;
struct rlimit l;
if (!getrlimit(RLIMIT_NOFILE, &l)) {
l.rlim_cur += 3 * attempts * attempt_rounds; T_QUIET; T_ASSERT_POSIX_SUCCESS(setrlimit(RLIMIT_NOFILE, &l),
"setrlimit");
}
signal(SIGCHLD, SIG_IGN);
int attempt_round = attempt_rounds;
again:
bzero(shmem, sizeof(struct shmem_s));
for (int i = 0; i < attempts; i++) {
char *t;
asprintf(&t, "%s/%d", tmp, i);
T_QUIET; T_ASSERT_POSIX_SUCCESS(mkdir(t, 0700), "mkdir");
setenv("BATS_TMP_DIR", t, 1); free(t);
helpers[helper_idx++] = dt_child_helper_args(helper, helper_args);
int w = 100;
do {
if (!w--) {
T_QUIET; T_FAIL("Helper should complete in <.1s");
goto timeout;
}
usleep(1000 * 100);
} while (shmem->done <= i);
}
setenv("BATS_TMP_DIR", tmpdir, 1);
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) T_MAYFAIL_WITH_REASON("ASLR");
T_EXPECT_GE(unique_values, STACK_LOCATIONS,
"Should have more than %d unique stack locations", STACK_LOCATIONS);
if (attempt_round && unique_values < STACK_LOCATIONS) goto again;
timeout:
T_QUIET; T_EXPECT_POSIX_SUCCESS(unlink(shmem_name), "unlink temp file");
free(shmem_name);
free(tmp);
dt_run_helpers(helpers, helper_idx, 5);
}
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);
}