From 8966afa1c4b143f021b9d0a868391a97aaadfe7d Mon Sep 17 00:00:00 2001 From: Ayrton Munoz Date: Tue, 15 Oct 2024 14:33:23 -0400 Subject: [PATCH] Move test_fault_handler.h into test_runner.c --- misc/test_runner/include/ia2_test_runner.h | 28 +++++ misc/test_runner/test_runner.c | 70 +++++++++++++ runtime/libia2/include/test_fault_handler.h | 107 -------------------- tests/destructors/main.c | 2 +- tests/destructors/plugin.c | 2 +- tests/heap_two_keys/main.c | 2 +- tests/heap_two_keys/plugin.c | 2 +- tests/mmap_loop/main.c | 2 +- tests/protected_threads/main.c | 2 +- tests/read_config/main.c | 2 +- tests/recursion/main.c | 2 +- tests/ro_sharing/main.c | 2 +- tests/ro_sharing/plugin.c | 2 +- tests/should_segfault/main.c | 2 +- tests/should_segfault/print_secret.c | 2 +- tests/sighandler/lib.c | 2 +- tests/sighandler/main.c | 2 +- tests/threads/main.c | 2 +- tests/three_keys_minimal/main.c | 2 +- tests/tls_protected/library.c | 2 +- tests/tls_protected/main.c | 2 +- tests/trusted_direct/main.c | 2 +- tests/trusted_direct/plugin.c | 2 +- tests/trusted_indirect/main.c | 2 +- tests/trusted_indirect/rand_op.c | 2 +- tests/two_keys_minimal/main.c | 2 +- tests/two_keys_minimal/plugin.c | 2 +- tests/two_shared_ranges/main.c | 2 +- tests/two_shared_ranges/plugin.c | 2 +- tests/untrusted_indirect/foo.c | 2 +- tests/untrusted_indirect/main.c | 2 +- 31 files changed, 126 insertions(+), 135 deletions(-) delete mode 100644 runtime/libia2/include/test_fault_handler.h diff --git a/misc/test_runner/include/ia2_test_runner.h b/misc/test_runner/include/ia2_test_runner.h index 6b1dc8d7e2..d3bb7b85d7 100644 --- a/misc/test_runner/include/ia2_test_runner.h +++ b/misc/test_runner/include/ia2_test_runner.h @@ -1,5 +1,6 @@ #pragma once #include +#include struct fake_criterion_test { void (*test)(void); @@ -25,3 +26,30 @@ struct fake_criterion_test { fprintf(stderr, s "\n"); \ exit(1); \ } while (0) + +/* + * This header defines a test framework for detecting MPK violations using + * signal handlers. This file must be included exactly once from a source file + * in the main binary with IA2_DEFINE_TEST_HANDLER defined by the preprocessor. + * This will define the functions and variables used by the test handler, ensure + * it is initialized before main and provide access to the LOG and + * CHECK_VIOLATION macros. Other files which need CHECK_VIOLATION or LOG may + * include the header without defining IA2_DEFINE_TEST_HANDLER. Using + * CHECK_VIOLATION without defining the test handler will trigger a linker error + * when building the shared object. + */ + +// Configure the signal handler to expect an mpk violation when `expr` is +// evaluated. If `expr` doesn't trigger a fault, this macro manually raises a +// fault with a different message. +#define CHECK_VIOLATION(expr) \ + ({ \ + expect_fault = true; \ + asm volatile("" : : : "memory"); \ + volatile typeof(expr) _tmp = expr; \ + printf("CHECK_VIOLATION: did not seg fault as expected\n"); \ + _exit(1); \ + _tmp; \ + }) + +extern bool expect_fault; diff --git a/misc/test_runner/test_runner.c b/misc/test_runner/test_runner.c index e346ee1036..85b5cec490 100644 --- a/misc/test_runner/test_runner.c +++ b/misc/test_runner/test_runner.c @@ -1,6 +1,7 @@ #include "include/ia2_test_runner.h" #include #include +#include #include #include @@ -41,3 +42,72 @@ int main() { } return 0; } + +// This is shared data to allow checking for violations in multiple +// compartments. We avoid using IA2_SHARED_DATA here to avoid including ia2.h +// since that would pull in libia2 as a dependency (the libia2 build generates a +// header included in ia2.h). +bool expect_fault __attribute__((section("ia2_shared_data"))) = false; + +// Create a stack for the signal handler to use +char sighandler_stack[4 * 1024] __attribute__((section("ia2_shared_data"))) +__attribute__((aligned(16))) = {0}; +char *sighandler_sp __attribute__((section("ia2_shared_data"))) = + &sighandler_stack[(4 * 1024) - 8]; + +// This function must be declared naked because it's not necessarily safe for it +// to write to the stack in its prelude (the stack isn't written to when the +// function itself is called because it's only invoked as a signal handler). +#if defined(__x86_64__) +__attribute__((naked)) void handle_segfault(int sig) { + // This asm must preserve %rdi which contains the argument since + // print_mpk_message reads it + __asm__( + // Signal handlers are defined in the main binary, but they don't run with + // the same pkru state as the interrupted context. This means we have to + // remove all MPK restrictions to ensure can run it correctly. + "xorl %ecx, %ecx\n" + "xorl %edx, %edx\n" + "xorl %eax, %eax\n" + "wrpkru\n" + // Switch the stack to a shared buffer. There's only one u32 argument and + // no returns so we don't need a full wrapper here. + "movq sighandler_sp@GOTPCREL(%rip), %rsp\n" + "movq (%rsp), %rsp\n" + "callq print_mpk_message"); +} +#elif defined(__aarch64__) +#warning "Review test_fault_handler implementation after enabling x18 switching" +void print_mpk_message(int sig); +void handle_segfault(int sig) { + print_mpk_message(sig); +} +#endif + +// The test output should be checked to see that the segfault occurred at the +// expected place. +void print_mpk_message(int sig) { + if (sig == SIGSEGV) { + // Write directly to stdout since printf is not async-signal-safe + const char *ok_msg = "CHECK_VIOLATION: seg faulted as expected\n"; + const char *early_fault_msg = "CHECK_VIOLATION: unexpected seg fault\n"; + const char *msg; + if (expect_fault) { + msg = ok_msg; + } else { + msg = early_fault_msg; + } + write(1, msg, strlen(msg)); + if (!expect_fault) { + _exit(-1); + } + } + _exit(0); +} + +// Installs the previously defined signal handler and disables buffering on +// stdout to allow using printf prior to the sighandler +__attribute__((constructor)) void install_segfault_handler(void) { + setbuf(stdout, NULL); + signal(SIGSEGV, handle_segfault); +} diff --git a/runtime/libia2/include/test_fault_handler.h b/runtime/libia2/include/test_fault_handler.h deleted file mode 100644 index 329442cc51..0000000000 --- a/runtime/libia2/include/test_fault_handler.h +++ /dev/null @@ -1,107 +0,0 @@ -#include -#include -#include -#include -#include -#include - -/* - * This header defines a test framework for detecting MPK violations using - * signal handlers. This file must be included exactly once from a source file - * in the main binary with IA2_DEFINE_TEST_HANDLER defined by the preprocessor. - * This will define the functions and variables used by the test handler, ensure - * it is initialized before main and provide access to the LOG and - * CHECK_VIOLATION macros. Other files which need CHECK_VIOLATION or LOG may - * include the header without defining IA2_DEFINE_TEST_HANDLER. Using - * CHECK_VIOLATION without defining the test handler will trigger a linker error - * when building the shared object. - */ - -#define VA_ARGS(...) , ##__VA_ARGS__ -#define LOG(msg, ...) printf("%s: " msg "\n", __func__ VA_ARGS(__VA_ARGS__)) - -// Configure the signal handler to expect an mpk violation when `expr` is -// evaluated. If `expr` doesn't trigger a fault, this macro manually raises a -// fault with a different message. -#define CHECK_VIOLATION(expr) \ - ({ \ - expect_fault = true; \ - asm volatile("" : : : "memory"); \ - volatile typeof(expr) _tmp = expr; \ - printf("CHECK_VIOLATION: did not seg fault as expected\n"); \ - _exit(1); \ - _tmp; \ - }) - -#ifndef IA2_DEFINE_TEST_HANDLER -extern bool expect_fault; -#else -// This is shared data to allow checking for violations in multiple -// compartments. We avoid using IA2_SHARED_DATA here to avoid including ia2.h -// since that would pull in libia2 as a dependency (the libia2 build generates a -// header included in ia2.h). -bool expect_fault __attribute__((section("ia2_shared_data"))) = false; - -// Create a stack for the signal handler to use -char sighandler_stack[4 * 1024] __attribute__((section("ia2_shared_data"))) -__attribute__((aligned(16))) = {0}; -char *sighandler_sp __attribute__((section("ia2_shared_data"))) = - &sighandler_stack[(4 * 1024) - 8]; - -// This function must be declared naked because it's not necessarily safe for it -// to write to the stack in its prelude (the stack isn't written to when the -// function itself is called because it's only invoked as a signal handler). -#if defined(__x86_64__) -__attribute__((naked)) void handle_segfault(int sig) { - // This asm must preserve %rdi which contains the argument since - // print_mpk_message reads it - __asm__( - // Signal handlers are defined in the main binary, but they don't run with - // the same pkru state as the interrupted context. This means we have to - // remove all MPK restrictions to ensure can run it correctly. - "xorl %ecx, %ecx\n" - "xorl %edx, %edx\n" - "xorl %eax, %eax\n" - "wrpkru\n" - // Switch the stack to a shared buffer. There's only one u32 argument and - // no returns so we don't need a full wrapper here. - "movq sighandler_sp@GOTPCREL(%rip), %rsp\n" - "movq (%rsp), %rsp\n" - "callq print_mpk_message"); -} -#elif defined(__aarch64__) -#warning "Review test_fault_handler implementation after enabling x18 switching" -void print_mpk_message(int sig); -void handle_segfault(int sig) { - print_mpk_message(sig); -} -#endif - -// The test output should be checked to see that the segfault occurred at the -// expected place. -void print_mpk_message(int sig) { - if (sig == SIGSEGV) { - // Write directly to stdout since printf is not async-signal-safe - const char *ok_msg = "CHECK_VIOLATION: seg faulted as expected\n"; - const char *early_fault_msg = "CHECK_VIOLATION: unexpected seg fault\n"; - const char *msg; - if (expect_fault) { - msg = ok_msg; - } else { - msg = early_fault_msg; - } - write(1, msg, strlen(msg)); - if (!expect_fault) { - _exit(-1); - } - } - _exit(0); -} - -// Installs the previously defined signal handler and disables buffering on -// stdout to allow using printf prior to the sighandler -__attribute__((constructor)) void install_segfault_handler(void) { - setbuf(stdout, NULL); - signal(SIGSEGV, handle_segfault); -} -#endif diff --git a/tests/destructors/main.c b/tests/destructors/main.c index 92f86c11a0..6d6ba2d887 100644 --- a/tests/destructors/main.c +++ b/tests/destructors/main.c @@ -6,7 +6,7 @@ #include #include "plugin.h" #define IA2_DEFINE_TEST_HANDLER -#include "test_fault_handler.h" + // This test uses two protection keys INIT_RUNTIME(2); diff --git a/tests/destructors/plugin.c b/tests/destructors/plugin.c index a3b2dcfa84..ab2d33173f 100644 --- a/tests/destructors/plugin.c +++ b/tests/destructors/plugin.c @@ -3,7 +3,7 @@ #include #include #include "exported_fn.h" -#include "test_fault_handler.h" + #define IA2_COMPARTMENT 2 #include diff --git a/tests/heap_two_keys/main.c b/tests/heap_two_keys/main.c index 37f82a5edd..d902075a7c 100644 --- a/tests/heap_two_keys/main.c +++ b/tests/heap_two_keys/main.c @@ -10,7 +10,7 @@ RUN: sh -c 'if [ ! -s "heap_two_keys_call_gates_0.ld" ]; then echo "No link args #include #include "plugin.h" #define IA2_DEFINE_TEST_HANDLER -#include "test_fault_handler.h" + // This test uses two protection keys INIT_RUNTIME(2); diff --git a/tests/heap_two_keys/plugin.c b/tests/heap_two_keys/plugin.c index 29e67c4c81..4e8a5955b4 100644 --- a/tests/heap_two_keys/plugin.c +++ b/tests/heap_two_keys/plugin.c @@ -4,7 +4,7 @@ RUN: cat heap_two_keys_call_gates_1.ld | FileCheck --check-prefix=LINKARGS %s #include #include #include "exported_fn.h" -#include "test_fault_handler.h" + #define IA2_COMPARTMENT 2 #include diff --git a/tests/mmap_loop/main.c b/tests/mmap_loop/main.c index 0d8cbc3036..48eda97b71 100644 --- a/tests/mmap_loop/main.c +++ b/tests/mmap_loop/main.c @@ -9,7 +9,7 @@ RUN: sh -c 'if [ ! -s "mmap_loop_call_gates_0.ld" ]; then echo "No link args as #include #define IA2_DEFINE_TEST_HANDLER -#include "test_fault_handler.h" + /* This program tests that mmap and heap allocations are handled properly. diff --git a/tests/protected_threads/main.c b/tests/protected_threads/main.c index be702a97e4..906f5e9928 100644 --- a/tests/protected_threads/main.c +++ b/tests/protected_threads/main.c @@ -9,7 +9,7 @@ RUN: sh -c 'if [ ! -s "protected_threads_call_gates_0.ld" ]; then echo "No link #include #define IA2_DEFINE_TEST_HANDLER #include -#include + #include INIT_RUNTIME(2); diff --git a/tests/read_config/main.c b/tests/read_config/main.c index 14c174cc8f..9a37af6216 100644 --- a/tests/read_config/main.c +++ b/tests/read_config/main.c @@ -15,7 +15,7 @@ RUN: cat main.c | FileCheck --match-full-lines --check-prefix=REWRITER %s // including plugin.h (which does include the output header) before core.h. #include "core.h" #define IA2_DEFINE_TEST_HANDLER -#include + /* This test is modeled after nginx's function pointer usage. In this test, diff --git a/tests/recursion/main.c b/tests/recursion/main.c index 5d97334c47..6ea3451163 100644 --- a/tests/recursion/main.c +++ b/tests/recursion/main.c @@ -8,7 +8,7 @@ RUN: cat recursion_call_gates_2.ld | FileCheck --check-prefix=LINKARGS %s #include #include #define IA2_DEFINE_TEST_HANDLER -#include "test_fault_handler.h" + INIT_RUNTIME(2); #define IA2_COMPARTMENT 1 diff --git a/tests/ro_sharing/main.c b/tests/ro_sharing/main.c index ccb6521a30..987279c7c4 100644 --- a/tests/ro_sharing/main.c +++ b/tests/ro_sharing/main.c @@ -7,7 +7,7 @@ RUN: sh -c 'if [ ! -s "ro_sharing_call_gates_0.ld" ]; then echo "No link args as #include #include #define IA2_DEFINE_TEST_HANDLER -#include "test_fault_handler.h" + // This test checks that all RO data mapped in from executable files is shared. // This is needed so that the dynamic linker can read ELF metadata. Read-only diff --git a/tests/ro_sharing/plugin.c b/tests/ro_sharing/plugin.c index 667c3c9128..88802f9272 100644 --- a/tests/ro_sharing/plugin.c +++ b/tests/ro_sharing/plugin.c @@ -1,7 +1,7 @@ /* RUN: cat ro_sharing_call_gates_1.ld | FileCheck --check-prefix=LINKARGS %s */ -#include "test_fault_handler.h" + #include #include diff --git a/tests/should_segfault/main.c b/tests/should_segfault/main.c index 555be2a1e9..b526e194ce 100644 --- a/tests/should_segfault/main.c +++ b/tests/should_segfault/main.c @@ -8,7 +8,7 @@ RUN: sh -c 'if [ ! -s "should_segfault_call_gates_0.ld" ]; then echo "No link ar #include #include #define IA2_DEFINE_TEST_HANDLER -#include "test_fault_handler.h" + INIT_RUNTIME(1); #define IA2_COMPARTMENT 1 diff --git a/tests/should_segfault/print_secret.c b/tests/should_segfault/print_secret.c index 5b84bce643..92d31b2500 100644 --- a/tests/should_segfault/print_secret.c +++ b/tests/should_segfault/print_secret.c @@ -6,7 +6,7 @@ RUN: cat should_segfault_call_gates_1.ld | FileCheck --check-prefix=LINKARGS %s #include #include #include "print_secret.h" -#include "test_fault_handler.h" + static bool early_fault = false; diff --git a/tests/sighandler/lib.c b/tests/sighandler/lib.c index 06b8094073..f9d46941a6 100644 --- a/tests/sighandler/lib.c +++ b/tests/sighandler/lib.c @@ -8,7 +8,7 @@ RUN: cat sighandler_call_gates_1.ld | FileCheck --check-prefix=LINKARGS %s #include #define IA2_DEFINE_TEST_HANDLER -#include + #define IA2_COMPARTMENT 2 #include diff --git a/tests/sighandler/main.c b/tests/sighandler/main.c index 56b72c138c..ac5b98c905 100644 --- a/tests/sighandler/main.c +++ b/tests/sighandler/main.c @@ -8,7 +8,7 @@ We need this because lib.c uses LINKARGS checks but not this file. #include #include #define IA2_DEFINE_TEST_HANDLER -#include + #include INIT_RUNTIME(2); diff --git a/tests/threads/main.c b/tests/threads/main.c index e6bcd2ec79..a5f4bc092a 100644 --- a/tests/threads/main.c +++ b/tests/threads/main.c @@ -10,7 +10,7 @@ RUN: sh -c 'if [ ! -s "threads_call_gates_0.ld" ]; then echo "No link args as ex #include #include #define IA2_DEFINE_TEST_HANDLER -#include + #include INIT_RUNTIME(1); diff --git a/tests/three_keys_minimal/main.c b/tests/three_keys_minimal/main.c index d7d1675656..3ec379acf8 100644 --- a/tests/three_keys_minimal/main.c +++ b/tests/three_keys_minimal/main.c @@ -11,7 +11,7 @@ INIT_RUNTIME(3); #include #define IA2_DEFINE_TEST_HANDLER -#include "test_fault_handler.h" + void main_noop(void) { } diff --git a/tests/tls_protected/library.c b/tests/tls_protected/library.c index 85ea74ced0..d238ebd8d0 100644 --- a/tests/tls_protected/library.c +++ b/tests/tls_protected/library.c @@ -3,7 +3,7 @@ // Check that readelf shows exactly one executable segment #include "library.h" -#include "test_fault_handler.h" + #include #include diff --git a/tests/tls_protected/main.c b/tests/tls_protected/main.c index 7397c63d24..800959ec1a 100644 --- a/tests/tls_protected/main.c +++ b/tests/tls_protected/main.c @@ -6,7 +6,7 @@ #include #include #define IA2_DEFINE_TEST_HANDLER -#include "test_fault_handler.h" + #include INIT_RUNTIME(2); diff --git a/tests/trusted_direct/main.c b/tests/trusted_direct/main.c index 5db88b67f4..420a5edf20 100644 --- a/tests/trusted_direct/main.c +++ b/tests/trusted_direct/main.c @@ -10,7 +10,7 @@ RUN: cat trusted_direct_call_gates_0.ld | FileCheck --check-prefix=LINKARGS %s #include #include "plugin.h" #define IA2_DEFINE_TEST_HANDLER -#include "test_fault_handler.h" + // This test checks that an untrusted library can call a trusted main binary. An // MPK violation is triggered from the untrusted library if no arguments are diff --git a/tests/trusted_direct/plugin.c b/tests/trusted_direct/plugin.c index fcb4fb6add..0cc9d1407a 100644 --- a/tests/trusted_direct/plugin.c +++ b/tests/trusted_direct/plugin.c @@ -4,7 +4,7 @@ RUN: cat trusted_direct_call_gates_1.ld | FileCheck --check-prefix=LINKARGS %s #include #include "exported_fn.h" -#include "test_fault_handler.h" + // LINKARGS: --wrap=start_plugin void start_plugin(void) { diff --git a/tests/trusted_indirect/main.c b/tests/trusted_indirect/main.c index 9dd42811fd..a081743f46 100644 --- a/tests/trusted_indirect/main.c +++ b/tests/trusted_indirect/main.c @@ -7,7 +7,7 @@ RUN: sh -c 'if [ ! -s "trusted_indirect_call_gates_0.ld" ]; then echo "No link a #include "rand_op.h" #include #define IA2_DEFINE_TEST_HANDLER -#include "test_fault_handler.h" + /* This program tests that a trusted binary can receive and call function pointers from an diff --git a/tests/trusted_indirect/rand_op.c b/tests/trusted_indirect/rand_op.c index e52dcbe5af..cc7e542867 100644 --- a/tests/trusted_indirect/rand_op.c +++ b/tests/trusted_indirect/rand_op.c @@ -7,7 +7,7 @@ RUN: cat trusted_indirect_call_gates_1.ld | FileCheck --check-prefix=LINKARGS %s #include #include "rand_op.h" -#include "test_fault_handler.h" + // This library either returns a pointer to `add` or to `sub`. One of the functions is static and // the other is part of the public API to ensure that both types of cross-compartment function diff --git a/tests/two_keys_minimal/main.c b/tests/two_keys_minimal/main.c index 273976f301..f45bda9409 100644 --- a/tests/two_keys_minimal/main.c +++ b/tests/two_keys_minimal/main.c @@ -12,7 +12,7 @@ RUN: cat two_keys_minimal_call_gates_2.ld | FileCheck --check-prefix=LINKARGS %s #include #include "plugin.h" #define IA2_DEFINE_TEST_HANDLER -#include "test_fault_handler.h" + // This test uses two protection keys INIT_RUNTIME(2); diff --git a/tests/two_keys_minimal/plugin.c b/tests/two_keys_minimal/plugin.c index 2413a9b315..b8b84c929e 100644 --- a/tests/two_keys_minimal/plugin.c +++ b/tests/two_keys_minimal/plugin.c @@ -9,7 +9,7 @@ RUN: cat two_keys_minimal_call_gates_1.ld | FileCheck --check-prefix=LINKARGS %s #include #include #include "exported_fn.h" -#include "test_fault_handler.h" + #define IA2_COMPARTMENT 2 #include diff --git a/tests/two_shared_ranges/main.c b/tests/two_shared_ranges/main.c index b794a5dd37..f28500de64 100644 --- a/tests/two_shared_ranges/main.c +++ b/tests/two_shared_ranges/main.c @@ -10,7 +10,7 @@ RUN: cat two_shared_ranges_call_gates_2.ld | FileCheck --check-prefix=LINKARGS % #include #include "plugin.h" #define IA2_DEFINE_TEST_HANDLER -#include "test_fault_handler.h" + // This test uses two protection keys INIT_RUNTIME(2); diff --git a/tests/two_shared_ranges/plugin.c b/tests/two_shared_ranges/plugin.c index ca75bc6f6c..16faff0e0a 100644 --- a/tests/two_shared_ranges/plugin.c +++ b/tests/two_shared_ranges/plugin.c @@ -8,7 +8,7 @@ RUN: cat two_shared_ranges_call_gates_1.ld | FileCheck --check-prefix=LINKARGS % #include #include #include "exported_fn.h" -#include "test_fault_handler.h" + #define IA2_COMPARTMENT 2 #include diff --git a/tests/untrusted_indirect/foo.c b/tests/untrusted_indirect/foo.c index 483f232e35..2b0606dcc9 100644 --- a/tests/untrusted_indirect/foo.c +++ b/tests/untrusted_indirect/foo.c @@ -5,7 +5,7 @@ RUN: cat untrusted_indirect_call_gates_1.ld | FileCheck --check-prefix=LINKARGS #include #include "foo.h" -#include "test_fault_handler.h" + extern bool clean_exit; diff --git a/tests/untrusted_indirect/main.c b/tests/untrusted_indirect/main.c index 6483cdfa2e..5aef334945 100644 --- a/tests/untrusted_indirect/main.c +++ b/tests/untrusted_indirect/main.c @@ -7,7 +7,7 @@ RUN: sh -c 'if [ ! -s "untrusted_indirect_call_gates_0.ld" ]; then echo "No link #include "foo.h" #include #define IA2_DEFINE_TEST_HANDLER -#include "test_fault_handler.h" + INIT_RUNTIME(1); #define IA2_COMPARTMENT 1