Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for creating threads on ARM #355

Open
wants to merge 15 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,7 @@ jobs:
libclang-15-dev
zlib1g-dev
libcapstone-dev
qemu-user-static
version: 1.0 # version of cache to load
- name: Check out code
uses: actions/checkout@v4
Expand Down Expand Up @@ -181,7 +182,7 @@ jobs:
-I${rtlibs_dir}/include/c++/v1"
export LDFLAGS="-L/usr/aarch64-linux-gnu/lib"
cmake .. \
-DCMAKE_CROSSCOMPILING_EMULATOR=${{ github.workspace }}/qemu/build/qemu-aarch64 \
-DCMAKE_CROSSCOMPILING_EMULATOR=qemu-aarch64-static \
-DCMAKE_TOOLCHAIN_FILE=../cmake/aarch64-toolchain.cmake \
-DCMAKE_C_COMPILER=`pwd`/../llvm-project/build/bin/clang-19 \
-DCMAKE_CXX_COMPILER=`pwd`/../llvm-project/build/bin/clang-19 \
Expand Down
3 changes: 1 addition & 2 deletions cmake/define-test.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -143,12 +143,11 @@ function(define_test)
# unless natively AArch64, default to running tests with qemu-aarch64 and a custom LD_LIBRARY_PATH
if (NOT ${CMAKE_HOST_SYSTEM_PROCESSOR} STREQUAL aarch64)
if (NOT DEFINED CMAKE_CROSSCOMPILING_EMULATOR)
set(CMAKE_CROSSCOMPILING_EMULATOR qemu-aarch64 -E LD_LIBRARY_PATH=/usr/aarch64-linux-gnu/lib:/usr/aarch64-linux-gnu/lib64)
set(CMAKE_CROSSCOMPILING_EMULATOR qemu-aarch64-static -E LD_LIBRARY_PATH=/usr/aarch64-linux-gnu/lib:/usr/aarch64-linux-gnu/lib64)
endif()
endif()
add_test(NAME ${TEST_NAME}
COMMAND ${CMAKE_CROSSCOMPILING_EMULATOR}
"-one-insn-per-tb"
"-L" "${CMAKE_BINARY_DIR}/external/glibc/sysroot/usr/"
"-E" "LD_LIBRARY_PATH=${CMAKE_BINARY_DIR}/external/glibc/sysroot/usr/lib:/usr/aarch64-linux-gnu/lib:/usr/aarch64-linux-gnu/lib64"
${CMAKE_CURRENT_BINARY_DIR}/${TEST_NAME}
Expand Down
19 changes: 12 additions & 7 deletions runtime/libia2/ia2.c
Original file line number Diff line number Diff line change
Expand Up @@ -10,15 +10,16 @@

#if defined(__x86_64__)

__attribute__((__used__)) uint32_t ia2_get_pkru() {
__attribute__((__used__)) static uint32_t ia2_get_pkru() {
uint32_t pkru = 0;
__asm__ volatile("rdpkru" : "=a"(pkru) : "a"(0), "d"(0), "c"(0));
return pkru;
}

size_t ia2_get_pkey() {
uint32_t pkru;
__asm__("rdpkru" : "=a"(pkru) : "a"(0), "d"(0), "c"(0));
size_t ia2_get_tag(void) __attribute__((alias("ia2_get_pkru")));

size_t ia2_get_compartment() {
uint32_t pkru = ia2_get_pkru();
switch (pkru) {
case 0xFFFFFFFC: {
return 0;
Expand Down Expand Up @@ -79,17 +80,21 @@ size_t ia2_get_pkey() {
}
}
}
size_t ia2_get_tag(void) __attribute__((alias("ia2_get_pkey")));

#elif defined(__aarch64__)

size_t ia2_get_x18(void) {
static size_t ia2_get_x18(void) {
size_t x18;
asm("mov %0, x18" : "=r"(x18));
return x18 >> 56;
return (x18 >> 56) & 0xF;
}

size_t ia2_get_tag(void) __attribute__((alias("ia2_get_x18")));

size_t ia2_get_compartment(void) {
return ia2_get_tag();
}

// TODO: insert_tag could probably be cleaned up a bit, but I'm not sure if the
// generated code could be simplified since addg encodes the tag as an imm field
#define _addg(out_ptr, in_ptr, tag) \
Expand Down
11 changes: 7 additions & 4 deletions runtime/libia2/include/ia2.h
Original file line number Diff line number Diff line change
Expand Up @@ -151,11 +151,14 @@
extern "C" {
#endif

/// Returns the raw PKRU register value
uint32_t ia2_get_pkru();
/// Returns the arch-specific compartment tag value.
///
/// On x86-64 this is the PKRU while on aarch64 it's the 4-bit tag in the upper
/// byte of x18.
size_t ia2_get_tag();

/// Returns the current compartment pkey
size_t ia2_get_pkey();
/// Returns the current compartment number
size_t ia2_get_compartment();

#ifdef __cplusplus
}
Expand Down
19 changes: 15 additions & 4 deletions runtime/libia2/include/ia2_internal.h
Original file line number Diff line number Diff line change
Expand Up @@ -298,7 +298,7 @@ asm(".macro movz_shifted_tag_x18 tag\n"

#if defined(__x86_64__)
#define return_stackptr_if_compartment(compartment) \
if (pkru == PKRU(compartment)) { \
if (tag == PKRU(compartment)) { \
register void *out asm("rax"); \
__asm__ volatile( \
"mov %%fs:(0), %%rax\n" \
Expand All @@ -309,8 +309,19 @@ asm(".macro movz_shifted_tag_x18 tag\n"
return out; \
}
#elif defined(__aarch64__)
#warning "libia2 does not implement return_stackptr_if_compartment yet"
#define return_stackptr_if_compartment(compartment)
#define return_stackptr_if_compartment(compartment) \
if (tag == compartment) { \
void *out; \
__asm__ volatile( \
"mrs x9, tpidr_el0\n" \
"adrp %0, :gottprel:ia2_stackptr_" #compartment "\n" \
"ldr %0, [%0, #:gottprel_lo12:ia2_stackptr_" #compartment "]\n" \
"add %0, %0, x9\n" \
: "=r"(out) \
: \
: "x9"); \
return out; \
}
#endif

/* Pass to mmap to signal end of program init */
Expand Down Expand Up @@ -421,7 +432,7 @@ __attribute__((__noreturn__)) void ia2_reinit_stack_err(int i);
REPEATB(n, declare_init_tls_fn, nop_macro); \
\
/* Returns `&ia2_stackptr_N` given a pkru value for the Nth compartment. */ \
__attribute__((visibility("default"))) void **ia2_stackptr_for_pkru(uint32_t pkru) { \
__attribute__((visibility("default"))) void **ia2_stackptr_for_tag(size_t tag) { \
REPEATB(n, return_stackptr_if_compartment, \
return_stackptr_if_compartment); \
return NULL; \
Expand Down
50 changes: 36 additions & 14 deletions runtime/libia2/threads.c
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
#include "ia2.h"

__attribute__((visibility("default"))) void init_stacks_and_setup_tls(void);
__attribute__((visibility("default"))) void **ia2_stackptr_for_pkru(uint32_t pkey);
__attribute__((visibility("default"))) void **ia2_stackptr_for_tag(size_t tag);

struct ia2_thread_thunk {
void *(*fn)(void *);
Expand All @@ -29,19 +29,13 @@ void *ia2_thread_begin(void *arg) {
* data. */
/* sigaltstack(&alt_stack, NULL); */

#if defined(__x86_64__)
/* Determine the current compartment so know which stack to use. */
uint32_t pkru = 0;
__asm__ volatile(
/* clang-format off */
"xor %%ecx,%%ecx\n"
"rdpkru\n"
/* clang-format on */
: "=a"(pkru)::"ecx", "edx");
void **new_sp_addr = ia2_stackptr_for_pkru(pkru);
size_t tag = ia2_get_tag();
void **new_sp_addr = ia2_stackptr_for_tag(tag);

/* Switch to the stack for this compartment, then call `fn(data)`. */
void *result;
#if defined(__x86_64__)
__asm__ volatile(
/* clang-format off */
// Copy stack pointer to rdi.
Expand All @@ -67,12 +61,40 @@ void *ia2_thread_begin(void *arg) {
: "=a"(result)
: [fn] "r"(fn), [data] "r"(data), [new_sp_addr] "r"(new_sp_addr)
: "rdi");
/* clang-format on */
return result;
#elif defined(__aarch64__)
#warning "libia2 does not implement ia2_thread_begin yet"
__builtin_trap();
__asm__ volatile(
// Copy stack pointer to x10
"mov x10, sp\n"
// Load the stack pointer for this compartment's stack
"ldr x0, [%[new_sp_addr]]\n"
"mov sp, x0\n"
// Push the old stack pointer
"str x10, [sp, #-8]!\n"
// Align the stack
//"movn x10, #0x000f\n"
//"and sp, sp, x10, #0\n"
// Prologue (necessary?)
//"str x29, [sp, #-8]!\n"
//"mov x29, sp\n"
// Load argument
"ldr x0, [%[data]]\n"
// Call fn(data)
"blr %[fn]\n"
// x0 now contains ret value
"mov %[result], x0\n"
// Pop the old stack pointer
"ldr x10, [sp], #8\n"
// Switch stacks back
"mov sp, x10\n"
: [result] "=r"(result)
: [fn] "r"(fn), [data] "r"(&data), [new_sp_addr] "r"(new_sp_addr)
: "x0", "x10");
#else
#error "unknown architecture"
#endif
/* clang-format on */

return result;
}

int __real_pthread_create(pthread_t *restrict thread,
Expand Down
9 changes: 4 additions & 5 deletions tests/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,8 @@ add_subdirectory(sighandler)
add_subdirectory(simple1)
add_subdirectory(static_addr_taken)
add_subdirectory(structs)

add_subdirectory(threads)
add_subdirectory(protected_threads)
# The following tests are not supported on ARM64 yet
if (NOT LIBIA2_AARCH64)
# Expected to have compartment violations, but we aren't enforcing yet:
Expand All @@ -83,12 +84,10 @@ if (NOT LIBIA2_AARCH64)

# test calls PKRU-specific functions
add_subdirectory(mmap_loop)

# MTE permissions aren't enforced for file-backed mappings
add_subdirectory(tls_protected)

# ARM does not support threads yet
add_subdirectory(threads)
add_subdirectory(protected_threads)

# no permissive mode on ARM
add_subdirectory(permissive_mode)
endif()
4 changes: 2 additions & 2 deletions tests/permissive_mode/permissive_mode.c
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ INIT_RUNTIME(1);

Test(permissive_mode, main) {
char* buffer = NULL;
cr_assert(ia2_get_pkru() == 0xFFFFFFF0);
cr_assert(ia2_get_tag() == 0xFFFFFFF0);

/* allocate an extra pkey */
cr_assert(pkey_alloc(0, PKEY_DISABLE_ACCESS | PKEY_DISABLE_WRITE) == 2);
Expand All @@ -24,5 +24,5 @@ Test(permissive_mode, main) {
pkey_mprotect(buffer, 4096, PROT_READ | PROT_WRITE, 2);
buffer[0] = 'b';

cr_assert(ia2_get_pkru() == 0xFFFFFFF0);
cr_assert(ia2_get_tag() == 0xFFFFFFF0);
}
10 changes: 7 additions & 3 deletions tests/threads/library.c
Original file line number Diff line number Diff line change
Expand Up @@ -36,9 +36,13 @@ void library_memset(void *ptr, uint8_t byte, size_t n) {

// LINKARGS: --wrap=library_showpkru
void library_showpkru() {
uint32_t actual_pkru = ia2_get_pkru();
cr_log_info("library pkru %08x", actual_pkru);
cr_assert_eq(0xfffffffc, actual_pkru);
uint32_t actual_tag = ia2_get_tag();
cr_log_info("library tag %08x", actual_tag);
#if defined(__aarch64__)
cr_assert_eq(0, actual_tag);
#else
cr_assert_eq(0xfffffffc, actual_tag);
#endif
}

static void *library_showpkru_thread_main(void *unused) {
Expand Down
18 changes: 13 additions & 5 deletions tests/threads/main.c
Original file line number Diff line number Diff line change
Expand Up @@ -31,8 +31,12 @@ void *thread_fn(void *ptr);
void *thread_fn(void *ptr) {
cr_log_info("tid %d ptr=%p\n", gettid(), ptr);

cr_log_info("main-module thread pkru=%08x\n", ia2_get_pkru());
cr_assert_eq(ia2_get_pkru(), 0xfffffff0);
cr_log_info("main-module thread pkru=%08zx\n", ia2_get_tag());
#if defined(__aarch64__)
cr_assert_eq(ia2_get_tag(), 1);
#else
cr_assert_eq(ia2_get_tag(), 0xfffffff0);
#endif

library_showpkru();

Expand Down Expand Up @@ -62,10 +66,14 @@ void *access_ptr_thread_fn(void *ptr) {
}

Test(threads, main) {
cr_log_info("main-module main pkru=%08x\n", ia2_get_pkru());
cr_assert_eq(ia2_get_pkru(), 0xfffffff0);
cr_log_info("main-module main pkru=%08zx\n", ia2_get_tag());
#if defined(__aarch64__)
cr_assert_eq(ia2_get_tag(), 1);
#else
cr_assert_eq(ia2_get_tag(), 0xfffffff0);
#endif
library_showpkru();
cr_log_info("main-module main pkru=%08x\n", ia2_get_pkru());
cr_log_info("main-module main pkru=%08zx\n", ia2_get_tag());

pthread_t lib_thread = library_spawn_thread();

Expand Down
2 changes: 2 additions & 0 deletions tests/tls_protected/library.c
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,9 @@ void lib_print_main_secret() {
cr_log_info("library: going to access main secret\n");
cr_log_info("library: accessing main secret at %p\n", &main_secret);
cr_log_info("library: main secret is %x\n", CHECK_VIOLATION(main_secret));
#if defined(__x86_64__)
cr_assert(false); // should not reach here
#endif
}

void lib_print_lib_secret() {
Expand Down
12 changes: 7 additions & 5 deletions tests/tls_protected/main.c
Original file line number Diff line number Diff line change
Expand Up @@ -24,19 +24,19 @@ volatile void *addr;
// CHECK_VIOLATION prints a different message.
void run_test(bool access_lib_secret) {
errno = 5;
cr_log_info("errno=%d, pkru=%08x\n", errno, ia2_get_pkru());
cr_log_info("errno=%d, pkru=%08zx\n", errno, ia2_get_tag());

lib_print_lib_secret();

// Access to thread-local from the same compartment should work.
cr_log_info("main: main secret is %x\n", main_secret);
cr_log_info("errno=%d, pkru=%08x\n", errno, ia2_get_pkru());
cr_log_info("errno=%d, pkru=%08zx\n", errno, ia2_get_tag());
lib_print_lib_secret();

cr_log_info("errno=%d, pkru=%08x\n", errno, ia2_get_pkru());
cr_log_info("errno=%d, pkru=%08zx\n", errno, ia2_get_tag());

errno = 5;
cr_log_info("pkru=%08x\n", ia2_get_pkru());
cr_log_info("pkru=%08zx\n", ia2_get_tag());
cr_log_info("errno=%d\n", errno);

// Perform forbidden access.
Expand All @@ -47,7 +47,9 @@ void run_test(bool access_lib_secret) {
cr_log_info("main: accessing lib secret at %p\n", addr);
}
cr_log_info("main: lib secret is %x\n", CHECK_VIOLATION(lib_secret));
#if defined(__x86_64__)
cr_assert(false); // Should not reach here
#endif
} else {
lib_print_main_secret();
}
Expand All @@ -59,4 +61,4 @@ Test(tls_protected, no_access_lib_secret) {

Test(tls_protected, access_lib_secret) {
run_test(true);
}
}
Loading