Allow the error invocation handler count to be reset. This lets long-running compartment invocations (for example, thread entry points) determine that they are in a known-valid state. Fixes #299
diff --git a/sdk/core/switcher/entry.S b/sdk/core/switcher/entry.S index 5b6a5cb..ff5d3e7 100644 --- a/sdk/core/switcher/entry.S +++ b/sdk/core/switcher/entry.S
@@ -992,6 +992,25 @@ csrr a0, CSR_MSHWM cret + .section .text, "ax", @progbits + .p2align 2 + .type __Z39switcher_handler_invocation_count_resetv,@function +__Z39switcher_handler_invocation_count_resetv: + // Trusted stack pointer in ca1 + cspecialr ca1, mtdc + // Offset of the current trusted stack frame to a1 + clhu a0, TrustedStack_offset_frameoffset(ca1) + addi a0, a0, -TrustedStackFrame_size + // Current trusted stack frame to ca1, a0 is dead + cincoffset ca1, ca1, a0 + // Current invocation count (for return) in a0 + clh a0, TrustedStackFrame_offset_errorHandlerCount(ca1) + // Reset invocation count + csh zero, TrustedStackFrame_offset_errorHandlerCount(ca1) + // Zero trusted stack frame pointer register + li a1, 0 + cret + // The linker expects export tables to start with space for cgp and pcc, then // the compartment error handler. We should eventually remove that requirement // for library export tables, but since they don't consume RAM after loading @@ -1026,3 +1045,4 @@ export __Z28switcher_thread_hazard_slotsv export __Z13thread_id_getv export __Z25stack_lowest_used_addressv +export __Z39switcher_handler_invocation_count_resetv
diff --git a/sdk/include/switcher.h b/sdk/include/switcher.h index a1088b6..5bfa480 100644 --- a/sdk/include/switcher.h +++ b/sdk/include/switcher.h
@@ -1,6 +1,7 @@ #pragma once #include <cdefs.h> #include <stddef.h> +#include <stdint.h> /** * Returns true if the trusted stack contains at least `requiredFrames` frames @@ -56,3 +57,26 @@ * compartment invocation. */ __cheri_libcall ptraddr_t stack_lowest_used_address(void); + +/** + * Resets the switcher's count of invocations. + * + * Switcher place a limit on the number of times a compartment invocation may + * fault (default 512). This prevents a compartment from getting stuck + * partially recovering from errors. This function resets the count to zero. + * It should be called from outer compartments' run loops and from places where + * the caller is certain that error handling is making forward progress. + * + * Note: If this is called from an error handler that subsequently returns with + * with install-context, the switcher will subtract one from this and set the + * switcher error count to -1. Because this is odd, the switcher will detect + * the next invocation of the error handler as a double fault and will force + * unwind. Do not call this if you are currently in an error handler unless + * you are jumping out via some mechanism that does *not* involve returning to + * the switcher. + * + * Returns the previous value of the invocation count. The low bit is set if + * an error handler is currently running, the remaining bits are the count. + */ +__cheri_libcall uint16_t switcher_handler_invocation_count_reset(void); +
diff --git a/tests/crash_recovery-test.cc b/tests/crash_recovery-test.cc index 9048335..884c8d5 100644 --- a/tests/crash_recovery-test.cc +++ b/tests/crash_recovery-test.cc
@@ -3,27 +3,35 @@ #define TEST_NAME "Crash recovery (main runner)" #include "crash_recovery.h" +#include <atomic> #include <cheri.hh> #include <errno.h> -int crashes = 0; +int crashes = 0; +std::atomic<bool> expectFault; extern "C" enum ErrorRecoveryBehaviour compartment_error_handler(struct ErrorState *frame, size_t mcause, size_t mtval) { - debug_log("Test saw error for PCC {}", frame->pcc); - debug_log("Error cause: {}, mtval: {}", mcause, mtval); + crashes++; if (mcause == 0x2) { + if (expectFault) + { + expectFault = false; + frame->pcc = static_cast<char *>(frame->pcc) + 2; + return ErrorRecoveryBehaviour::InstallContext; + } debug_log("Test hit assertion failure, unwinding"); return ErrorRecoveryBehaviour::ForceUnwind; } + debug_log("Test saw error for PCC {}", frame->pcc); + debug_log("Error cause: {}, mtval: {}", mcause, mtval); TEST((mcause == 0x1c) && (mtval == 0), "mcause should be 0x1c (CHERI), is {}, mtval should be 0 (force " "unwind), is {})", mcause, mtval); - crashes++; debug_log("Resuming test at failure location"); return ErrorRecoveryBehaviour::InstallContext; } @@ -62,4 +70,25 @@ check_stack(); debug_log("Calling crashy compartment returned (crashes: {})", crashes); TEST(crashes == 3, "Failed to notice crash"); + + ptraddr_t handlerCount = switcher_handler_invocation_count_reset(); + TEST(handlerCount == crashes * 2, + "Should have called handler 3 times (3 entries, 3 exits giving a " + "total of {}), was {}", + crashes * 2, + handlerCount); + + // By default, we will be force unwound if we exceed 512 error-handler + // invocations. + constexpr int MaxCrashes = 600; + for (int i = 0; i < MaxCrashes; i++) + { + switcher_handler_invocation_count_reset(); + expectFault = true; + // Crash with a guaranteed 16-bit instruction. This cannot use + // `__builtin_trap` because the compiler knows that `__builtin_trap` + // does not return and so will not generate code following it. + asm volatile("c.unimp"); + } + TEST(crashes == 3 + MaxCrashes, "Failed to notice crash"); }