blob: c897432f68cae6e2426e084bb8f29a9af9ce684d [file] [log] [blame]
#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");
}
/**
* Arrange to exhaust the stack inside the cross-compartment switcher's spill of
* callee-saved state. The result should simply be an error return, rather than
* a forced-unwind.
*/
void exhaust_thread_stack_spill(__cheri_callback void (*fn)())
{
register auto rfn asm("ct1") = fn;
register uintptr_t res asm("ca0") = 0;
__asm__ volatile(
// Save the stack to put back later
"cmove cs0, csp\n"
// Shrink the available stack space
"cgetbase s1, csp\n"
"addi s1, s1, %[stackleft]\n"
"csetaddr csp, csp, s1\n"
// Make the call
"1:\n"
"auipcc ct2, %%cheriot_compartment_hi(.compartment_switcher)\n"
"clc ct2, %%cheriot_compartment_lo_i(1b)(ct2)\n"
"cjalr ct2\n"
"cmove csp, cs0\n"
: /* outs */ "+C"(res)
: /* ins */[stackleft] "i"(sizeof(void *))
: /* clobbers */ "ct2", "cs0", "cs1");
*threadStackTestFailed = false;
TEST(res == -ENOTENOUGHSTACK, "Bad return {}", res);
}
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;
}