blob: b3b894f4f29140409789b3d42d3b5a7467b3af01 [file] [log] [blame]
// 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;
}