| // 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 | |
| */ | |
| void test_stack() | |
| { | |
| int ret = test_with_small_stack(128); | |
| TEST(ret == 0, | |
| "test_with_small_stack failed, returned {} with 128-byte stack", | |
| ret); | |
| 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(112); | |
| TEST(ret == -ENOTENOUGHSTACK, | |
| "test_with_small_stack failed, returned {} with 112-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("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); | |
| } |