| // Copyright Microsoft and CHERIoT Contributors. |
| // SPDX-License-Identifier: MIT |
| |
| #define TEST_NAME "Stack" |
| #include "stack_tests.h" |
| #include "tests.hh" |
| #include <cheri.hh> |
| #include <errno.h> |
| |
| using namespace CHERI; |
| |
| bool threadStackTestFailed = false; |
| |
| extern "C" ErrorRecoveryBehaviour |
| compartment_error_handler(ErrorState *frame, size_t mcause, size_t mtval) |
| { |
| TEST(!holds_switcher_capability(frame), |
| "Leaked switcher capabilities to stack_test compartment"); |
| TEST(!threadStackTestFailed, "Thread stack test failed"); |
| |
| // If we're here because of a force unwind from the callee, just continue. |
| if ((mcause == 0x1c) && (mtval == 0)) |
| { |
| return ErrorRecoveryBehaviour::InstallContext; |
| } |
| |
| return ErrorRecoveryBehaviour::ForceUnwind; |
| } |
| |
| __cheri_callback void test_trusted_stack_exhaustion() |
| { |
| exhaust_trusted_stack(&test_trusted_stack_exhaustion, |
| &threadStackTestFailed); |
| } |
| |
| __cheri_callback void cross_compartment_call() |
| { |
| TEST(false, |
| "Cross compartment call with invalid CSP shouldn't be reachable"); |
| } |
| |
| namespace |
| { |
| PermissionSet get_stack_permissions() |
| { |
| Capability<void> csp = ({ |
| register void *cspRegister asm("csp"); |
| asm("" : "=C"(cspRegister)); |
| cspRegister; |
| }); |
| |
| return csp.permissions(); |
| } |
| |
| /** |
| * Is the stack sufficiently valid that we expect the switcher to be able |
| * to spill the register frame there? If so, it should invoke the error |
| * handler. |
| */ |
| bool stack_is_mostly_valid(const PermissionSet StackPermissions) |
| { |
| // Note: This does not include store-local, but the register frame will |
| // be corrupted if store local is missing because all pointers to the |
| // stack, including the spilled csp, will have their tags cleared. |
| static constexpr PermissionSet StackRequiredPermissions{ |
| Permission::Load, Permission::Store, Permission::LoadStoreCapability}; |
| return StackRequiredPermissions.can_derive_from(StackPermissions); |
| } |
| |
| void expect_handler(bool handlerExpected) |
| { |
| debug_log("Expected to invoke the handler? {}", handlerExpected); |
| set_expected_behaviour(&threadStackTestFailed, handlerExpected); |
| } |
| |
| __attribute__((used)) extern "C" int test_small_stack() |
| { |
| return test_stack_requirement(); |
| } |
| } // namespace |
| |
| __attribute__((used)) extern "C" int test_with_small_stack(size_t stackSize); |
| |
| asm(".section .text\n" |
| ".global test_with_small_stack\n" |
| "test_with_small_stack:\n" |
| // Preserve the old stack |
| " cmove ct0, csp\n" |
| // Add space to the requested size for the spill slots and the size of the |
| // stack that `test_small_stack` will use, plus the four capabilities that |
| // the switcher will spill for us. We use 16 bytes to store the stack |
| // pointer and return address, `test_small_stack` uses the same amount: it |
| // needs to store the return address, and the ABI requires that stacks are |
| // 16-byte aligned and so it uses a full 16 bytes. |
| " add a0, a0, 64\n" |
| // Get the base of the stack |
| " cgetbase a2, csp\n" |
| // Move the stack pointer to the current base |
| " csetaddr csp, csp, a2\n" |
| // Truncate the stack |
| " csetbounds csp, csp, a0\n" |
| // Move to the end of the stack, minus the spill-slot size |
| " cincoffset csp, csp, a0\n" |
| " cincoffset csp, csp, -16\n" |
| " csc ct0, 0(csp)\n" |
| " csc cra, 8(csp)\n" |
| // Call the test function |
| " cjal test_small_stack\n" |
| // Restore |
| " clc cra, 8(csp)\n" |
| " clc csp, 0(csp)\n" |
| " cjr cra\n"); |
| |
| // Defeat the compiler optimisation that may turn our first call to this into a |
| // call. If the compiler does this then we will fail on an even number of |
| // cross-compartment calls not an odd number. |
| __cheri_callback void (*volatile crossCompartmentCall)(); |
| |
| /* |
| * The stack tests should cover the edge-cases scenarios for both |
| * the trusted and compartment stacks. We make sure the |
| * switcher handle them correctly. We check: |
| * - trusted stack exhaustion |
| * - compartment stack exhaustion |
| * - compartment stack with incorrect permissions |
| * - invalid compartment stack |
| */ |
| int test_stack() |
| { |
| int ret = test_with_small_stack(144); |
| TEST(ret == 0, |
| "test_with_small_stack failed, returned {} with 144-byte stack", |
| ret); |
| ret = test_with_small_stack(160); |
| TEST(ret == 0, |
| "test_with_small_stack failed, returned {} with 160-byte stack", |
| ret); |
| ret = test_with_small_stack(128); |
| TEST(ret == -ENOTENOUGHSTACK, |
| "test_with_small_stack failed, returned {} with 128-byte stack", |
| ret); |
| __cheri_callback void (*callback)() = cross_compartment_call; |
| |
| crossCompartmentCall = test_trusted_stack_exhaustion; |
| debug_log("exhaust trusted stack, do self recursion with a cheri_callback"); |
| expect_handler(true); |
| (*crossCompartmentCall)(); |
| |
| debug_log("exhausting the compartment stack"); |
| expect_handler(false); |
| exhaust_thread_stack(); |
| |
| debug_log("exhausting the compartment stack during a switcher call"); |
| expect_handler(false); |
| threadStackTestFailed = true; |
| exhaust_thread_stack_spill(callback); |
| TEST(threadStackTestFailed == false, "switcher did not return error"); |
| |
| debug_log("modifying stack permissions on fault"); |
| PermissionSet compartmentStackPermissions = get_stack_permissions(); |
| for (auto permissionToRemove : compartmentStackPermissions) |
| { |
| auto permissions = |
| compartmentStackPermissions.without(permissionToRemove); |
| debug_log("Permissions: {}", permissions); |
| expect_handler(stack_is_mostly_valid(permissions)); |
| set_csp_permissions_on_fault(permissions); |
| } |
| |
| debug_log("modifying stack permissions on cross compartment call"); |
| for (auto permissionToRemove : compartmentStackPermissions) |
| { |
| auto permissions = |
| compartmentStackPermissions.without(permissionToRemove); |
| debug_log("Permissions: {}", permissions); |
| set_csp_permissions_on_call(permissions, callback); |
| } |
| |
| debug_log("invalid stack on fault"); |
| expect_handler(false); |
| test_stack_invalid_on_fault(); |
| |
| debug_log("invalid stack on cross compartment call"); |
| expect_handler(false); |
| |
| test_stack_invalid_on_call(callback); |
| return 0; |
| } |