| #define TEST_NAME "Stack tests, exhaust thread stack" | |
| #include "stack_tests.h" | |
| #include "tests.hh" | |
| #include <cheri.hh> | |
| #include <errno.h> | |
| using namespace CHERI; | |
| /* | |
| * Define a macro that gets a __cheri_callback capability and calls it, while | |
| * support adding instruction before the call. This is used to avoid code | |
| * duplication, in cases we want to call a __cheri_callback in multiple | |
| * places while adding additional functionalities. | |
| * | |
| * handle: a sealed capability to a __cheri_callback to call | |
| * instruction: additional instruction(s) to add before the call, | |
| * with an operand. | |
| * additional_input: the operand the additional instruction refers to. | |
| */ | |
| #define CALL_CHERI_CALLBACK(handle, instructions, additional_input) \ | |
| ({ \ | |
| register auto rfn asm("ct1") = handle; \ | |
| __asm__ volatile( \ | |
| "1:\n" \ | |
| "auipcc ct2, %%cheriot_compartment_hi(.compartment_switcher)\n" \ | |
| "clc ct2, %%cheriot_compartment_lo_i(1b)(ct2)\n" \ | |
| "" instructions "\n" \ | |
| "cjalr ct2\n" \ | |
| : /* no outputs; we're jumping and probably not coming back */ \ | |
| : "C"(rfn), "r"(additional_input)); \ | |
| \ | |
| TEST(false, "Should be unreachable"); \ | |
| }) | |
| namespace | |
| { | |
| /// Is the error handler expected? | |
| bool expectedHandler = false; | |
| /// Pointer to the value in the caller used to report failure. | |
| bool *threadStackTestFailed; | |
| } // namespace | |
| extern "C" ErrorRecoveryBehaviour | |
| compartment_error_handler(ErrorState *frame, size_t mcause, size_t mtval) | |
| { | |
| bool leakedSwitcherCapabilities = holds_switcher_capability(frame); | |
| *threadStackTestFailed = !expectedHandler && !leakedSwitcherCapabilities; | |
| // This needs to store pointers on the stack, so may fault. | |
| debug_log("Error handler called in callee"); | |
| TEST(!leakedSwitcherCapabilities, | |
| "Switcher leaked privileged capabilities"); | |
| TEST(expectedHandler, | |
| "Error handler in compartment that exhausts/invalidated its stack " | |
| "should not be called"); | |
| return ErrorRecoveryBehaviour::ForceUnwind; | |
| } | |
| /** | |
| * Set up the handler expectations. Takes the caller's error flag and | |
| * whether the handler is expected as arguments. | |
| */ | |
| void set_expected_behaviour(bool *outTestFailed, bool handlerExpected) | |
| { | |
| expectedHandler = handlerExpected; | |
| threadStackTestFailed = outTestFailed; | |
| *outTestFailed = handlerExpected; | |
| } | |
| void exhaust_thread_stack() | |
| { | |
| /* Move the compartment's stack near its end, in order to | |
| * trigger stack exhaustion while the switcher handles | |
| * faults from the compartment. | |
| * | |
| * The correct behavior here is NOT executing the rest of this | |
| * function NOR the error handler. Because the thread's stack is | |
| * exhausted, we can't do neither. Therefore, we should enter | |
| * the error handler of the caller. | |
| * | |
| * We use the boolean thread_stack_test_failed to indicate | |
| * to the parent if any unexpected error occured. | |
| */ | |
| __asm__ volatile("cgetbase t1, csp\n" | |
| "addi t1, t1, 16\n" | |
| "csetaddr csp, csp, t1\n" | |
| "csh zero, 0(cnull)\n"); | |
| *threadStackTestFailed = true; | |
| TEST(false, "Should be unreachable"); | |
| } | |
| void set_csp_permissions_on_fault(PermissionSet newPermissions) | |
| { | |
| __asm__ volatile( | |
| "candperm csp, csp, %0\n" | |
| "csh zero, 0(cnull)\n" ::"r"(newPermissions.as_raw())); | |
| TEST(false, "Should be unreachable"); | |
| } | |
| void set_csp_permissions_on_call(PermissionSet newPermissions, | |
| __cheri_callback void (*fn)()) | |
| { | |
| CALL_CHERI_CALLBACK(fn, "candperm csp, csp, %1\n", newPermissions.as_raw()); | |
| TEST(false, "Should be unreachable"); | |
| } | |
| void test_stack_invalid_on_fault() | |
| { | |
| __asm__ volatile("ccleartag csp, csp\n" | |
| "csh zero, 0(cnull)\n"); | |
| *threadStackTestFailed = true; | |
| TEST(false, "Should be unreachable"); | |
| } | |
| void test_stack_invalid_on_call(__cheri_callback void (*fn)()) | |
| { | |
| // the `move zero, %1` is a no-op, just to have an operand | |
| CALL_CHERI_CALLBACK(fn, "move zero, %1\nccleartag csp, csp\n", 0); | |
| *threadStackTestFailed = true; | |
| TEST(false, "Should be unreachable"); | |
| } | |
| void self_recursion(__cheri_callback void (*fn)()) | |
| { | |
| (*fn)(); | |
| } | |
| void exhaust_trusted_stack(__cheri_callback void (*fn)(), | |
| bool *outLeakedSwitcherCapability) | |
| { | |
| self_recursion(fn); | |
| } | |
| int test_stack_requirement() | |
| { | |
| return 0; | |
| } |