| // Copyright Microsoft and CHERIoT Contributors. |
| // SPDX-License-Identifier: MIT |
| |
| #include "export-table-assembly.h" |
| #include "trusted-stack-assembly.h" |
| #include "misc-assembly.h" |
| #include <errno.h> |
| |
| .include "assembly-helpers.s" |
| |
| # Symbolic names for the stack high water mark registers until |
| # the assembler knows about them. |
| |
| /** |
| * Machine-mode stack high water mark CSR |
| */ |
| #define CSR_MSHWM 0xbc1 |
| /** |
| * Machine mode stack high water mark stack base CSR |
| */ |
| #define CSR_MSHWMB 0xbc2 |
| |
| #define MAX_FAULTS_PER_COMPARTMENT_CALL 1024 |
| |
| #define SPILL_SLOT_cs0 0 |
| #define SPILL_SLOT_cs1 8 |
| #define SPILL_SLOT_cgp 16 |
| #define SPILL_SLOT_pcc 24 |
| #define SPILL_SLOT_SIZE 32 |
| |
| /* |
| * The switcher uniformly speaks of registers using their RISC-V ELF psABI names |
| * and not their raw index, as, broadly speaking, we use registers in a similar |
| * way to C functions. However, it's probably convenient to have a mapping |
| * readily accessible, so here 'tis: |
| * |
| * # x0 x1 x2 x3 x4 x5 x6 x7 x8 x9 x10 x11 x12 x13 x14 x15 |
| * psABI zero ra sp gp tp t0 t1 t2 s0 s1 a0 a1 a2 a3 a4 a5 |
| * |
| * When we use the psABI name without a 'c' prefix, we are sometimes meaning to |
| * refer to the address component of the capability. |
| * |
| * Despite the use of psABI names and conformance at the interface (argument |
| * registers used for arguments, return address register used for its canonical |
| * purpose, &c), one should not read too much of the psABI calling convention |
| * into the code here. Within the switcher, the machine is a raw register |
| * machine and C is a distant, high-level language. |
| * |
| * Since this is the part of the map labeled "here be dragons", we have added |
| * "Register Atlas" comments throughout. Lines within an atlas consist of a |
| * comma-whitespace-separated list of registers, a colon, and descriptive text. |
| * In general, atlases should cover all (including dead) registers. Point |
| * changes to the atlas are denoted with "Atlas update", to emphasize that |
| * registers not named are not dead but instead retain their meaning from the |
| * last full atlas. |
| * |
| * Labels associated with interesting control flow are annotated with |
| * |
| * - "FROM:", which may be repeated once for each predecessor label or these: |
| * - "above": the immediately prior block |
| * - "cross-call": untrusted code making a cross-compartment call |
| * - "malice": untrusted code outside the switcher |
| * - "interrupt": an asynchronous external event |
| * - "error": a trap from within the switcher |
| * |
| * - "IFROM:", which indicates an indirect transfer of control (through cjalr |
| * or mepcc/mret, for example). |
| * |
| * - "ITO:", the other direction of "IFROM:" |
| * |
| * - "IRQ ASSUME:", "any", "deferred" or "enabled". This declares the state of |
| * the machine, either from explicit instructions or implicit aspects of the |
| * architecture. |
| * |
| * - "IRQ REQUIRE:", "any", "deferred" or "enabled". If not "any", then all |
| * paths into this label must have the indicated IRQ disposition. |
| * |
| * - "LIVE IN:", a list of live (in) registers at this point of the code and/or |
| * - "*": the entire general purpose register file (no CSRs or SCRs implied) |
| * - "callee-save": the psABI callee-save registers |
| * - "mcause" |
| * - "mtdc" |
| * - "mtval" |
| * - "mepcc" |
| * |
| * Control flow instructions may be annotated with "LIVE OUT:" labels. These |
| * capture the subset of live registers meant to be available to the target. |
| * |
| * For all annotations, optional commentary given in parentheses and may |
| * continue onto adjacent lines. |
| * |
| */ |
| /* |
| * Multiple points in the switcher are exposed to callers via sentries (either |
| * forward-arc sentries manufactured elsewhere or backwards-arc sentries |
| * manufactured by CJALRs herein. Sentries can have their GL(obal) permission |
| * cleared by the bearer, but nothing here relies on PCC being GL(obal): we |
| * never store anything derived from our PCC to memory, much less through an |
| * authority not bearing SL permission. |
| * |
| * Similarly, the switcher communicates with the outside world by means of |
| * sealed data capabilities (to TrustedStacks and compartment export tables). |
| * These, too, can have their GL(obal) bit cleared by bearers, but again, it |
| * does not much matter for switcher correctness; see comments marked with |
| * "LOCAL SEAL" notes in the code below. |
| * |
| * We do rely on PCC having L(oad)G(lobal) permission -- which is under seal in |
| * sentries and so not mutable by the caller, even if a sentry is loaded |
| * through an authority without LG -- so that, in particular, the sealing |
| * authorities used herein are GL(obal) and so the sealed capabilities that |
| * result are also GL(obal). |
| */ |
| |
| |
| switcher_code_start: |
| |
| # Global for the sealing key. Stored in the switcher's code section. |
| .section .text, "ax", @progbits |
| .globl compartment_switcher_sealing_key |
| .p2align 3 |
| compartment_switcher_sealing_key: |
| .Lsealing_key_trusted_stacks: |
| .long 0 |
| .long 0 |
| .Lunsealing_key_import_tables: |
| .long 0 |
| .long 0 |
| # Global for the scheduler's PCC. Stored in the switcher's code section. |
| .section .text, "ax", @progbits |
| .globl switcher_scheduler_entry_pcc |
| .p2align 3 |
| switcher_scheduler_entry_pcc: |
| .long 0 |
| .long 0 |
| # Global for the scheduler's CGP. Stored in the switcher's code section. |
| .section .text, "ax", @progbits |
| .globl switcher_scheduler_entry_cgp |
| .p2align 3 |
| switcher_scheduler_entry_cgp: |
| .long 0 |
| .long 0 |
| # Global for the scheduler's CSP. Stored in the switcher's code section. |
| .section .text, "ax", @progbits |
| .globl switcher_scheduler_entry_csp |
| .p2align 2 |
| switcher_scheduler_entry_csp: |
| .long 0 |
| .long 0 |
| |
| /** |
| * Copy a register context from `src` to `dst` using `scratch` as the register |
| * to hold loaded capabilities and `counter` as the register to hold the loop |
| * counter. All four registers are clobbered by this macro. |
| */ |
| .macro copyContext dst, src, scratch, counter |
| addi \counter, zero, 15 |
| 1: |
| clc \scratch, 0(\src) |
| csc \scratch, 0(\dst) |
| addi \counter, \counter, -1 |
| cincoffset \dst, \dst, 8 |
| cincoffset \src, \src, 8 |
| bnez \counter, 1b |
| .endm |
| |
| /// Spill a single register to a trusted stack pointed to by csp. |
| .macro trustedSpillOne, reg |
| csc \reg, TrustedStack_offset_\reg(csp) |
| .endm |
| |
| /** |
| * Spill all of the registers in the list (in order) to a trusted stack pointed |
| * to by csp. |
| */ |
| .macro trustedSpillRegisters reg1, regs:vararg |
| forall trustedSpillOne, \reg1, \regs |
| .endm |
| |
| /// Reload a single register from a trusted stack pointed to by csp. |
| .macro trustedReloadOne, reg |
| clc \reg, TrustedStack_offset_\reg(csp) |
| .endm |
| |
| /** |
| * Reload all of the registers in the list (in order) to a trusted stack pointed |
| * to by csp. |
| */ |
| .macro trustedReloadRegisters reg1, regs:vararg |
| forall trustedReloadOne, \reg1, \regs |
| .endm |
| |
| /** |
| * Zero the stack. The three operands are the base address, the top address, |
| * and a scratch register to use. The base must be a capability but it must |
| * be provided without the c prefix because it is used as both a capability |
| * and integer register. All three registers are clobbered but should not be |
| * considered safe to expose outside the TCB. |
| */ |
| .macro zero_stack base top scratch |
| addi \scratch, \top, -32 |
| addi \top, \top, -16 |
| bgt \base, \scratch, 1f |
| // Zero the stack in 32-byte chunks |
| 0: |
| csc cnull, 0(c\base) |
| csc cnull, 8(c\base) |
| csc cnull, 16(c\base) |
| csc cnull, 24(c\base) |
| cincoffset c\base, c\base, 32 |
| ble \base, \scratch, 0b |
| 1: |
| bgt \base, \top, 2f |
| // Zero any 16-byte tail |
| csc cnull, 0(c\base) |
| csc cnull, 8(c\base) |
| 2: |
| .endm |
| |
| /** |
| * Clear the hazard pointers associated with this thread. (See |
| * include/stdlib.h:/heap_claim_fast, and its implementation in |
| * lib/compartment_helpers/claim_fast.cc for more about hazard pointers.) We |
| * don't care about leaks here (they're store-only from anywhere except the |
| * allocator), so just write a 32-bit zero over half of each one to clobber the |
| * tags. |
| */ |
| .macro clear_hazard_slots trustedStack, scratch |
| clc \scratch, TrustedStack_offset_hazardPointers(\trustedStack) |
| csw zero, 0(\scratch) |
| csw zero, 8(\scratch) |
| .endm |
| |
| .section .text, "ax", @progbits |
| .globl __Z26compartment_switcher_entryz |
| .p2align 2 |
| .type __Z26compartment_switcher_entryz,@function |
| __Z26compartment_switcher_entryz: |
| /* |
| * FROM: cross-call |
| * FROM: malice |
| * IRQ ASSUME: deferred (loader/boot.cc constructs only IRQ-deferring |
| * sentries to this function; the export entry at the end |
| * of this file is somewhat fictitious) |
| * LIVE IN: mtdc, ra, sp, gp, s0, s1, t0, t1, a0, a1, a2, a3, a4, a5 |
| * (that is, all registers except tp and t2) |
| * |
| * Atlas: |
| * mtdc: pointer to this thread's TrustedStack |
| * (may be 0 from buggy/malicious scheduler thread) |
| * ra: caller return address |
| * (at the moment, this is ensured because we enter via an |
| * IRQ-disabling forward sentry, which requires ra as the destination |
| * register of the cjalr the caller used, but we are not relying on |
| * this property, and we hope to relax the switcher's IRQ posture) |
| * sp: nominally, caller's stack pointer; will check integrity below |
| * gp: caller state, to be spilled, value unused in switcher |
| * s0, s1: caller state, to be spilled, value unused in switcher |
| * t0: possible caller argument to callee, passed or zered in switcher |
| * (specifically, this is the pointer to arguments beyond a0-a5 and/or |
| * variadic arguments) |
| * t1: sealed export table entry for the target callee |
| * (see LLVM's RISCVExpandPseudo::expandCompartmentCall and, more |
| * generally, the ABI chapter of the CHERIoT ISA document, |
| * https://cheriot.org/cheriot-sail/cheriot-architecture.pdf) |
| * a0, a1, a2, a3, a4, a5: possible caller arguments to callee, passed or |
| * zeroed in switcher. |
| * tp, t2: dead |
| */ |
| /* |
| * By virtue of making a call, the caller is indicating that all caller-save |
| * registers are dead. Because we are crossing a trust boundary, the |
| * switcher must spill callee-save registers. If we find ourselves unable |
| * to do so for "plausibly accidental" reasons, we'll return an error to the |
| * caller (via the exception path; see .Lhandle_error_in_switcher). |
| * Specifically, the first spill here is to the lowest address and so |
| * guaranteed to raise a bounds fault if any of the stores here would access |
| * below the base (lowest address) of the stack capability. |
| * |
| * Certain other kinds of less plausibly accidental malice (for example, an |
| * untagged or sealed or SD-permission-less capability in sp) will also be |
| * caught by this first spill. In some sense we should forcibly unwind the |
| * caller, but it's acceptable, in the sense that no would-be-callee can be |
| * harmed, to just return an error instead. |
| * |
| * Yet other kinds of less plausibly accidental malice can survive the first |
| * spill. For example, consider a MC-permission-less capability in sp and a |
| * non-capability value in s0. While the first spill will not trap, these |
| * forms of malice will certainly be detected in a few instructions, when we |
| * scrutinize sp in detail. They might (or might not) cause an intervening |
| * (specifically, spill) instruction to trap. Either way will result in us |
| * ending up in .Lcommon_force_unwind, either directly or via the exception |
| * handler. |
| * |
| * At entry, the register file is safe to expose to the caller, and so if |
| * and when we take the "just return an error" option, no changes, beyond |
| * populating the error return values in a0 and a1, are required. |
| */ |
| /* |
| * __Z26compartment_switcher_entryz is exposed to callers directly as a |
| * forward-arc interrupt-disabling sentry via the somewhat lengthy chain |
| * of events involving... |
| * - the .compartment_import_table sections defined in |
| * compartment.ldscript, |
| * - the export table defined below (.section .compartment_export_table), |
| * - firmware.ldscript.in's use of that export table to define |
| * .switcher_export_table, |
| * - the firmware image header (loader/types.h's ImgHdr), in particular |
| * ImgHdr::switcher::exportTable and, again, firmware.ldscript.in's |
| * use of .switcher_export_table to populate that field, and |
| * - loader/boot.cc:/populate_imports and its caller's computation of |
| * switcherPCC. |
| */ |
| /* |
| * TODO: We'd like to relax the interrupt posture of the switcher where |
| * possible. Specifically, unless both the caller and callee are running |
| * and to be run with interrupts deferred, we'd like the switcher, and |
| * especially its stack-zeroing, to be preemtable. |
| */ |
| .Lswitch_entry_first_spill: |
| /* |
| * FROM: above |
| * ITO: .Lswitch_just_return (via .Lhandle_error_in_switcher) |
| */ |
| csc cs0, (SPILL_SLOT_cs0-SPILL_SLOT_SIZE)(csp) |
| cincoffset csp, csp, -SPILL_SLOT_SIZE |
| csc cs1, SPILL_SLOT_cs1(csp) |
| csc cgp, SPILL_SLOT_cgp(csp) |
| csc cra, SPILL_SLOT_pcc(csp) |
| /* |
| * Atlas update: |
| * ra, gp, s0, s1: dead (presently, redundant caller values) |
| */ |
| |
| /* |
| * Before we access any privileged state, we can verify the compartment's |
| * csp is valid. If not, force unwind. Note that these checks are purely to |
| * protect the callee, not the switcher itself, which can always bail and |
| * forcibly unwind the caller. |
| * |
| * Make sure the caller's CSP has the expected permissions (including that |
| * it is a stack pointer, by virtue of being local and bearing SL) and that |
| * its top and base are at least 16-byte aligned. We have already checked |
| * that it is tagged and unsealed and at least 8-byte aligned by virtue of |
| * surviving the stores above. |
| * |
| * TODO for formal verification: it should be the case that after these |
| * tests and the size checks below, no instruction in the switcher |
| * authorized by the capability now in sp can fault. |
| */ |
| //.Lswitch_csp_check: |
| cgetperm t2, csp |
| li tp, COMPARTMENT_STACK_PERMISSIONS |
| bne tp, t2, .Lcommon_force_unwind |
| cgetbase t2, csp |
| or t2, t2, sp |
| andi t2, t2, 0xf |
| bnez t2, .Lcommon_force_unwind |
| /* |
| * Atlas update: |
| * t2, tp: dead (again) |
| * sp: the caller's untrusted stack pointer, now validated and pointing at |
| * the callee-save register spill area we made above |
| */ |
| |
| // mtdc should always have an offset of 0. |
| cspecialr ct2, mtdc |
| // Atlas update: t2: a pointer to this thread's TrustedStack structure |
| /* |
| * This is our first access via mtdc, and so it might trap, if the scheduler |
| * tries a cross-compartment call. That will be a fairly short trip to an |
| * infinite loop (see commentary in exception_entry_asm). |
| */ |
| clear_hazard_slots /* trusted stack = */ ct2, /* scratch = */ ctp |
| // Atlas update: tp: dead (again) |
| |
| //.Lswitch_trusted_stack_push: |
| /* |
| * TrustedStack::frames[] is a flexible array member at the end of the |
| * structure, and the stack of frames it represents grows *upwards* (with |
| * [0] the initial activation, [1] the first cross-compartment call, and so |
| * on). Thus, if the frame offset points "one past the end" (or futher |
| * out), we have no more frames available, so off we go to |
| * .Lswitch_trusted_stack_exhausted . |
| */ |
| clhu tp, TrustedStack_offset_frameoffset(ct2) |
| cgetlen s0, ct2 |
| /* |
| * Atlas update: |
| * s0: scalar length of the TrustedStack structure |
| * tp: scalar offset of the next available TrustedStack::frames[] element |
| */ |
| // LIVE OUT: mtdc, sp |
| bgeu tp, s0, .Lswitch_trusted_stack_exhausted |
| // Atlas update: s0: dead |
| // we are past the stacks checks. |
| cincoffset ctp, ct2, tp |
| // Atlas update: tp: pointer to the next available TrustedStackFrame |
| /* |
| * Populate that stack frame by... |
| * 1. spilling the caller's stack pointer, as modified by the spills at the |
| * start of this function. |
| */ |
| csc csp, TrustedStackFrame_offset_csp(ctp) |
| /* |
| * 2. zeroing the number of error handler invocations (we have just entered |
| * this call, so no faults triggered during this call yet). |
| */ |
| csh zero, TrustedStackFrame_offset_errorHandlerCount(ctp) |
| /* |
| * 3. For now, store a null export entry. This is largely cosmetic; we will |
| * not attempt to access this value before it is set to the real export |
| * table entry below. Should we trap, the logic at |
| * .Lhandle_error_switcher_pcc will cause us to force unwind, popping |
| * this frame before any subsequent action. |
| * |
| * TODO for formal verification: prove that this store is dead and can |
| * be eliminated. |
| */ |
| csc cnull, TrustedStackFrame_offset_calleeExportTable(ctp) |
| /* |
| * Update the frame offset, using s1 to hold a scratch scalar. Any fault |
| * before this point (wrong target cap, unaligned stack, etc.) is seen as a |
| * fault in the caller. After writing the new TrustedSstack::frameoffset, |
| * any fault is seen as a callee fault. |
| */ |
| clhu s1, TrustedStack_offset_frameoffset(ct2) |
| addi s1, s1, TrustedStackFrame_size |
| csh s1, TrustedStack_offset_frameoffset(ct2) |
| |
| /* |
| * Chop off the stack, using... |
| * - s0 for the current untrusted stack base address (the lowest address of |
| * the register spill we created at .Lswitch_entry_first_spill) |
| * - s1 for the length of the stack suffix to which the callee is entitled |
| */ |
| //.Lswitch_stack_chop: |
| cgetaddr s0, csp |
| cgetbase s1, csp |
| csetaddr csp, csp, s1 |
| sub s1, s0, s1 |
| csetboundsexact ct2, csp, s1 |
| csetaddr csp, ct2, s0 |
| /* |
| * Atlas: |
| * s0: address of stack boundary between caller and callee frames, that is, |
| * the lowest address of the register spill from |
| * .Lswitch_entry_first_spill) |
| * sp: pointer to stack, with its limit and address set to the address in |
| * s0. The base and permissions have not been altered from sp at |
| * entry, and the tag remains set since all manipulations have been |
| * monotone non-increasing of, and within, bounds. |
| * tp: pointer to the freshly populated TrustedStackFrame (still) |
| * t1: sealed export table entry for the target callee (still) |
| * a0, a1, a2, a3, a4, a5, t0: call argument values / to be zeroed (still) |
| * t2, s1: dead (again) |
| * ra, gp: dead (still) |
| */ |
| #ifdef CONFIG_MSHWM |
| // Read the stack high water mark (which is 16-byte aligned) |
| csrr gp, CSR_MSHWM |
| // Skip zeroing if high water mark >= stack pointer |
| //.Lswitch_shwm_skip_zero: |
| bge gp, sp, .Lswitch_after_zero |
| /* |
| * Use stack high water mark as base address for zeroing. If this faults |
| * then it will trigger a force unwind. This can happen only if the caller |
| * is doing something bad. |
| */ |
| csetaddr ct2, csp, gp |
| #endif |
| zero_stack /* base = */ t2, /* top = */ s0, /* scratch = */ gp |
| .Lswitch_after_zero: |
| /* |
| * FROM: above |
| * FROM: .Lswitch_shwm_skip_zero |
| * LIVE IN: mtdc, sp, tp, t0, t1, a0, a1, a2, a3, a4, a5 |
| * |
| * Atlas: |
| * sp: pointer to stack, with bounds as t2, cursor at boundary in s0 |
| * (still) |
| * tp: pointer to the freshly populated TrustedStackFrame (still) |
| * t1: sealed export table entry for the target callee (still) |
| * a0, a1, a2, a3, a4, a5, t0: call argument values / to be zeroed (still) |
| * ra, s1: dead (still) |
| * s0, t2, gp: dead (again) |
| */ |
| |
| // Fetch the sealing key |
| LoadCapPCC cs0, .Lunsealing_key_import_tables |
| // Atlas update: s0: switcher sealing key |
| /* |
| * The caller's handle to the callee (the sealed capability to the export |
| * table entry) is in t1, which has been kept live all this time. Unseal |
| * and load the entry point offset. |
| */ |
| //.Lswitch_unseal_entry: |
| cunseal ct1, ct1, cs0 |
| /* |
| * Atlas update: |
| * t1: if tagged, an unsealed pointer with bounds encompassing callee |
| * compartment ExportTable and ExportEntry array and cursor pointing at |
| * the callee ExportEntry; if untagged, the caller is malicious or |
| * deeply confused, the next instruction will trap, and we'll |
| * .Lcommon_force_unwind via exception_entry_asm and |
| * .Lhandle_error_in_switcher. |
| */ |
| /* |
| * LOCAL SEAL: If it happened that the export table reference given to us |
| * is not GL(obal), then the result of unsealing above, now in t1, will |
| * also be not GL(obal). This reference is stored to the TrustedStack frame |
| * through a SL-bearing authority (because the TrustedStack also holds our |
| * register spill area, and so must have SL) but neither it or any monotone |
| * progeny otherwise escape the switcher's private register file. |
| */ |
| /* |
| * Load the entry point offset. If cunseal failed then this will fault and |
| * we will force unwind; see .Lhandle_error_switcher_pcc_check. |
| */ |
| clhu s0, ExportEntry_offset_functionStart(ct1) |
| // Atlas update: s0: callee compartment function entrypoint offset |
| /* |
| * At this point, we know that the cunseal has succeeded (we didn't trap on |
| * the load) and so it's safe to store the unsealed value of the export |
| * table pointer. |
| * |
| * TODO for formal verification: Nothing between this point and transition |
| * to the callee should fault. |
| */ |
| csc ct1, TrustedStackFrame_offset_calleeExportTable(ctp) |
| |
| //.Lswitch_stack_check_length: |
| /* |
| * Load the minimum stack size required by the callee, clobbering tp, which |
| * holds a capability to the TrustedStackFrame, bringing us closer to a |
| * register file that is not holding values kept secret from the callee. |
| */ |
| clbu tp, ExportEntry_offset_minimumStackSize(ct1) |
| // Atlas update: tp: minimum stack size, in units of 8 bytes. |
| slli tp, tp, 3 |
| // Atlas update: tp: minimum stack size, in bytes. |
| /* |
| * Check that the stack is large enough for the callee. |
| * At this point, we have already truncated the stack and so the length of |
| * the stack is the length that the callee can use. |
| */ |
| cgetlen t2, csp |
| // Atlas update: t2: length of available stack |
| /* |
| * Include the space we reserved for the unwind state. |
| * |
| * tp holds the number of required stack bytes, a value between 0 and 0x7F8 |
| * (the result of an unsigned byte load left shifted by 3). Given this |
| * extremely limited range, adding STACK_ENTRY_RESERVED_SPACE will not cause |
| * overflow (while instead subtracting it from the available length, in t2, |
| * might underflow). |
| * |
| * TODO for formal verification: prove the above. |
| */ |
| addi tp, tp, STACK_ENTRY_RESERVED_SPACE |
| // LIVE OUT: mtdc |
| bgtu tp, t2, .Lswitch_stack_too_small |
| |
| /* |
| * Reserve space for unwind state and so on; this cannot take sp out of |
| * bounds, in light of the check we just performed. |
| */ |
| cincoffset csp, csp, -STACK_ENTRY_RESERVED_SPACE |
| #ifdef CONFIG_MSHWM |
| // store new stack top as stack high water mark |
| csrw CSR_MSHWM, sp |
| #endif |
| |
| // Get the flags field into tp |
| clbu tp, ExportEntry_offset_flags(ct1) |
| // Atlas update: tp: callee entry flags field |
| |
| // All ExportEntry state has been consulted; move to ExportTable header |
| cgetbase s1, ct1 |
| csetaddr ct1, ct1, s1 |
| /* |
| * Atlas update: |
| * t1: pointer to the callee compartment ExportTable structure. Bounds |
| * still inclusive of ExportEntry array, but that will not be accessed. |
| */ |
| //.Lswitch_callee_load: |
| // At this point we begin loading callee compartment state. |
| clc cgp, ExportTable_offset_cgp(ct1) |
| // Atlas update: gp: target compartment CGP |
| clc cra, ExportTable_offset_pcc(ct1) |
| cincoffset cra, cra, s0 |
| // Atlas update: ra: target function entrypoint (pcc base + offset from s0) |
| |
| // Zero any unused argument registers |
| /* |
| * The low 3 bits of the flags field (tp) contain the number of argument |
| * registers to pass. We create a small sled that zeroes them in the order |
| * they are used as argument registers, and we jump into the middle of it at |
| * an offset defined by that value, preserving the prefix of the sequence. |
| */ |
| .Lswitch_load_zero_arguments_start: |
| // FROM: above |
| auipcc cs0, %cheriot_compartment_hi(.Lswitch_zero_arguments_start) |
| cincoffset cs0, cs0, %cheriot_compartment_lo_i(.Lswitch_load_zero_arguments_start) |
| // Atlas update: s0: .Lzero_arguments_start |
| andi t2, tp, 0x7 // loader/types.h's ExportEntry::flags |
| /* |
| * Change from the number of registers to pass into the number of 2-byte |
| * instructions to skip. |
| */ |
| sll t2, t2, 1 |
| // Offset the jump target by the number of instructions to skip |
| cincoffset cs0, cs0, t2 |
| // Jump into the sled. |
| cjr cs0 |
| .Lswitch_zero_arguments_start: |
| // IFROM: above |
| zeroRegisters a0, a1, a2, a3, a4, a5, t0 |
| |
| /* |
| * Enable interrupts if the interrupt-disable bit is not set in flags. See |
| * loader/types.h's InterruptStatus and ExportEntry::InterruptStatusMask. |
| * InterruptStatus::Inherited is prohibited on export entries, so we need |
| * look only at one bit. |
| */ |
| andi t1, tp, ExportEntryInterruptStatusSwitcherMask |
| bnez t1, .Lswitch_skip_interrupt_enable |
| csrsi mstatus, 0x8 |
| .Lswitch_skip_interrupt_enable: |
| /* |
| * FROM: above |
| * IRQ REQUIRE: any (have adopted callee disposition) |
| * |
| * Atlas: |
| * ra: (still) target function entrypoint |
| * sp: (still) pointer to stack, below compartment invocation local storage |
| * gp: (still) target compartment CGP |
| * a0, a1, a2, a3, a4, a5, t0: arguments or zeroed, as above |
| * tp, t1, t2, s0, s1: dead |
| */ |
| /* |
| * There is an interesting narrow race to consider here. We're preemptable |
| * and in the switcher. That means someone could call |
| * __Z25switcher_interrupt_threadPv on us, and when we came back on core, |
| * we'd jump ahead to switcher_after_compartment_call, via... |
| * |
| * - .Lexception_scheduler_return_installed |
| * - .Lhandle_injected_error |
| * - .Lhandle_error |
| * - .Lhandle_error_switcher_pcc |
| * - .Lhandle_error_in_switcher |
| * - .Lcommon_force_unwind |
| * |
| * That is, rather than invoking the callee's compartment's error handler, |
| * and letting it service the MCAUSE_THREAD_INTERRUPT, we'll return to the |
| * caller with -ECOMPARTMENTFAIL. |
| * |
| * TODO: https://github.com/CHERIoT-Platform/cheriot-rtos/issues/372 |
| */ |
| /* |
| * Up to 10 registers are carrying state for the callee or are properly |
| * zeroed. Clear the remaining 5 now. |
| */ |
| //.Lswitch_caller_dead_zeros: |
| zeroRegisters tp, t1, t2, s0, s1 |
| //.Lswitch_callee_call: |
| /* |
| * "cjalr cra" simultaneously moves the live-in ra value into the *next* |
| * program counter and the program counter (of the instruction itself) into |
| * ra (while sealing it to be a backwards-arc sentry). That is, the value |
| * we have so carefully been keeping in ra is clobbered, but only after it |
| * becomes the next program counter. |
| */ |
| // LIVE OUT: * |
| cjalr cra |
| |
| .globl switcher_after_compartment_call |
| switcher_after_compartment_call: |
| /* |
| * FROM: malice |
| * IFROM: above |
| * FROM: .Lswitch_stack_too_small |
| * FROM: .Lcommon_force_unwind |
| * IRQ ASSUME: any (both IRQ-deferring and IRQ-enabling sentries are |
| * provided to the callees and can escape for malice's use, and |
| * the TrustedStack spill frame is not precious, and nothing |
| * that would happen were we are preempted would shift our |
| * TrustedStack::frameoffset or the contents of ::frames) |
| * LIVE IN: mtdc, a0, a1 |
| * |
| * Atlas: |
| * mtdc: pointer to this thread's TrustedStack |
| * a0, a1: return value(s). The callee function must ensure that it clears |
| * these as appropriate if it is returning 0 or 1 values and not 2. |
| * ra, sp, gp: dead or callee state (to be replaced by caller state) |
| * tp, s0, s1, t0, t1, t2, a2, a3, a4, a5: dead or callee state (to be |
| * zeroed before return to caller) |
| */ |
| /* |
| * Pop a frame from the trusted stack, leaving all registers in the state |
| * expected by the caller of a cross-compartment call. The callee is |
| * responsible for zeroing unused return registers; the switcher will zero |
| * other non-return argument and temporary registers. |
| * |
| * This unwind path is common to both ordinary return (from above), benign |
| * errors after we'd set up the trusted frame (.Lswitch_stack_too_small), |
| * and forced unwinds (.Lcommon_force_unwind). |
| * |
| * TODO for formal verification: the below should not fault before returning |
| * back to the caller. If a fault occurs there must be a serious bug |
| * elsewhere. |
| */ |
| /* |
| * As just before the call, we are preemptive and in the switcher. If we |
| * are signaled via MCAUSE_THREAD_INTERRUPT at this point, we will come |
| * back here (with a0 holding -ECOMPARTMENTFAIL and a1 holding 0). This |
| * block is _idempotent_ until the update of mtdc's |
| * TrustedStack::frameoffset, so until then we will effectively just |
| * clobber the return values. After that, though, we'd forcibly unwind out |
| * of the caller. |
| * |
| * TODO: https://github.com/CHERIoT-Platform/cheriot-rtos/issues/372 |
| */ |
| /* |
| * The return sentry given to the callee as part of that cjalr could be |
| * captured by the callee or passed between compartments arbitrarily for |
| * later use. That is, in some sense, we cannot assume that any use of this |
| * sentry corresponds to the most recent derivation of it by this thread. |
| * Phrased differently, the sentry created by the "cjalr" above is not tied |
| * to the topmost TrustedStackFrame at the time of its creation. Invoking |
| * this sentry, regardless of how one comes to hold it, and even if |
| * invocation is not matched to the call that constructed any given instance |
| * of it, will always result in popping the topmost trusted stack frame (at |
| * the time of invocation) and returning to its caller. Thus, the |
| * possibility of more than one of these sentries in scope at any moment is |
| * not concerning. |
| * |
| * Additionally, threads are given a manufactured, interrupt-deferring |
| * sentry to here as part of their initial activation frame (so that |
| * returning acts as an orderly unwind). See |
| * loader/boot.cc:/boot_threads_create . |
| * |
| * Being robust to malicious or "unusual" entry here is facilitated by the |
| * requirements of the next block of code being minimal: mtdc must be a |
| * TrustedStack pointer. The contents of a0 and a1 will be exposed to the |
| * compartment above the one currently executing, or the thread will be |
| * terminated if there is no such. |
| */ |
| |
| cspecialr ctp, mtdc |
| // Atlas update: tp: pointer to TrustedStack |
| |
| clear_hazard_slots ctp, ct2 |
| |
| /* |
| * Make sure there is a frame left in the trusted stack by... |
| * |
| * 1. Loading TrustedStack::frameoffset and offsetof(TrustedStack, frames) |
| */ |
| clhu t2, TrustedStack_offset_frameoffset(ctp) |
| li t0, TrustedStack_offset_frames |
| /* |
| * 2. Decreasing frameoffset by one frame. This will go below |
| * offsetof(TrustedStack, frames) if there are no active frames. |
| */ |
| addi t2, t2, -TrustedStackFrame_size |
| /* |
| * 3. Comparing. If this is the first trusted stack frame, then the csp |
| * that we would be loading is the csp on entry, which does not have a |
| * spilled area. In this case, we would fault when loading (because the |
| * stack cursor is at its limit), so would exit the thread, but we should |
| * instead gracefully exit the thread. |
| */ |
| bgeu t0, t2, .Lcommon_defer_irqs_and_thread_exit |
| cincoffset ct1, ctp, t2 |
| /* |
| * Atlas update: |
| * t0: dead (again) |
| * t1: pointer to the TrustedStackFrame to bring on core |
| * t2: the TrustedStack::frameoffset associated with t1 |
| */ |
| |
| /* |
| * Restore the untrusted stack pointer from the trusted stack. This points |
| * at the spill frame, created by .Lswitch_entry_first_spill and following |
| * instructions, holding caller register values. |
| */ |
| clc csp, TrustedStackFrame_offset_csp(ct1) |
| /* |
| * Atlas update: |
| * sp: pointer to untrusted stack (the spill frame created by |
| * .Lswitch_entry_first_spill) |
| */ |
| // Update the current frame offset in the TrustedStack |
| csh t2, TrustedStack_offset_frameoffset(ctp) |
| /* |
| * Do the loads *after* moving the trusted stack pointer. In theory, the |
| * checks after `.Lswitch_entry_first_spill` make it impossible for this to |
| * fault, but if we do fault here and hadn't moved the frame offset, then |
| * we'd end up in an infinite loop trying repeatedly to pop the same |
| * trusted stack frame. This would be bad. Instead, we move the trusted |
| * stack pointer *first* and so, if the accesses to the untrusted stack |
| * fault, we will detect a fault in the switcher, enter the force-unwind |
| * path, and pop the frame for the compartment that gave us a malicious |
| * csp. |
| */ |
| clc cs0, SPILL_SLOT_cs0(csp) |
| clc cs1, SPILL_SLOT_cs1(csp) |
| clc cra, SPILL_SLOT_pcc(csp) |
| clc cgp, SPILL_SLOT_cgp(csp) |
| cincoffset csp, csp, SPILL_SLOT_SIZE |
| #ifdef CONFIG_MSHWM |
| /* |
| * Read the stack high water mark, which is 16-byte aligned. We will use |
| * this as base address for stack clearing. Note that it cannot be greater |
| * than stack top as we set it to stack top when we pushed to the trusted |
| * stack frame, and it is a monotonically non-increasing value. |
| */ |
| csrr tp, CSR_MSHWM |
| #else |
| cgetbase tp, csp |
| #endif |
| cgetaddr t1, csp |
| csetaddr ct2, csp, tp |
| zero_stack t2, t1, tp |
| #ifdef CONFIG_MSHWM |
| csrw CSR_MSHWM, sp |
| #endif |
| |
| // Zero all registers not holding state intended for caller; see atlas below |
| .Lswitch_callee_dead_zeros: |
| /* |
| * FROM: above |
| * FROM: .Lswitch_trusted_stack_exhausted |
| * LIVE IN: mtdc, ra, sp, gp, s0, s1, a0, a1 |
| * |
| * Atlas: |
| * mtdc: pointer to this thread's TrustedStack |
| * a0, a1: return value(s) |
| * ra, sp, gp, s0, s1: caller state |
| * tp, t0, t1, t2, a2, a3, a4, a5: dead (to be zeroed here) |
| */ |
| zeroAllRegistersExcept ra, sp, gp, s0, s1, a0, a1 |
| .Lswitch_just_return: |
| /* |
| * FROM: above |
| * IFROM: .Lswitch_entry_first_spill (via .Lhandle_error_in_switcher) |
| * LIVE IN: mtdc, ra, sp, gp, s0, s1, a0, a1 |
| * |
| * Atlas: |
| * mtdc: pointer to this thread's TrustedStack |
| * a0, a1: return value(s) (still) |
| * ra, sp, gp, s0, s1: caller state |
| * tp, t0, t1, t2, a2, a3, a4, a5: zero (if from above) or caller state (if |
| * from .Lhandle_error_in_switcher via |
| * .Lhandle_return_context_install) |
| */ |
| cret |
| |
| /* |
| * If the stack is too small, we don't do the call, but to avoid leaking |
| * any other state we still go through the same return path as normal. We |
| * set the return registers to -ENOTENOUGHSTACK and 0, so users can see |
| * that this is the failure reason. |
| */ |
| .Lswitch_stack_too_small: |
| /* |
| * FROM: .Lswitch_stack_check_length |
| * IRQ REQUIRE: any (TrustedStack spill frame is not precious) |
| * LIVE IN: mtdc |
| * |
| * Atlas: |
| * mtdc: thread trusted stack pointer |
| */ |
| li a0, -ENOTENOUGHSTACK |
| li a1, 0 |
| // Atlas update: a0, a1: error return values |
| // LIVE OUT: mtdc, a0, a1 |
| j switcher_after_compartment_call |
| |
| /* |
| * If we have run out of trusted stack, then just restore the caller's state |
| * (mostly, the callee-save registers from the spills we did at the top of |
| * __Z26compartment_switcher_entryz) and return an error value. |
| */ |
| .Lswitch_trusted_stack_exhausted: |
| /* |
| * FROM: .Lswitch_trusted_stack_push |
| * IRQ REQUIRE: any (all state is in registers, TrustedStack spill frame is |
| * not precious) |
| * LIVE IN: mtdc, sp |
| * |
| * Atlas: |
| * mtdc: TrustedStack pointer |
| * sp: Caller stack pointer, pointing at switcher spill frame, after |
| * validation |
| */ |
| /* |
| * Restore the spilled values. Because csp has survived being spilled to |
| * and the permission validations, these will not fault. |
| */ |
| clc cs0, SPILL_SLOT_cs0(csp) |
| clc cs1, SPILL_SLOT_cs1(csp) |
| clc cra, SPILL_SLOT_pcc(csp) |
| clc cgp, SPILL_SLOT_cgp(csp) |
| cincoffset csp, csp, SPILL_SLOT_SIZE |
| // Set the first return register (a0) and zero the other (a1) |
| li a0, -ENOTENOUGHTRUSTEDSTACK |
| zeroOne a1 |
| j .Lswitch_callee_dead_zeros |
| |
| .size compartment_switcher_entry, . - compartment_switcher_entry |
| |
| .global exception_entry_asm |
| .p2align 2 |
| /** |
| * The entry point of all exceptions and interrupts |
| * |
| * For now, the entire routine is run with interrupts disabled. |
| */ |
| exception_entry_asm: |
| /* |
| * FROM: malice |
| * FROM: interrupt |
| * FROM: error |
| * IRQ ASSUME: deferred (sole entry is via architectural exception path, |
| * which unconditionally, atomically defers IRQs) |
| * LIVE IN: mcause, mtval, mtdc, * |
| * |
| * Atlas: |
| * mtdc: either pointer to TrustedStack or zero |
| * mcause, mtval: architecture-specified exception information. These are |
| * assumed correct -- for example, that it is impossible for |
| * untrusted code to enter the exception path with |
| * arbitrarily chosen values. |
| * *: The GPRs at the time of exception. |
| */ |
| /* |
| * We do not trust the interruptee's context. We cannot use its stack in any |
| * way. The save register frame we can use is fetched from the |
| * TrustedStack. In general, mtdc holds the trusted stack register. We are |
| * here with interrupts off and precious few registers available to us, so |
| * swap it with the csp (we'll put it back, later). |
| */ |
| cspecialrw csp, mtdc, csp |
| |
| /* |
| * If we read out zero, we've reentered the exception and are about to trap |
| * (in spillRegisters, which uses sp as its authority). |
| * |
| * Failure to guard here would mean that the trap in spillRegisters below |
| * would re-enter the trap-handler with an unknown value (the first trap's |
| * sp) in mtdc, which the rest of this code would take to be a valid |
| * TrustedStack. Exactly what would happen then is hard to say; we'd try |
| * spilling registers to a potentially attacker-controlled pointer, at the |
| * very least, and that's something to avoid. |
| */ |
| beqz sp, .Lexception_reentered |
| |
| /* |
| * The guest sp/csp (x2/c2) is now in mtdc. Will be spilled later, but we |
| * spill all the other 14 registers now. |
| */ |
| trustedSpillRegisters cra, cgp, ctp, ct0, ct1, ct2, cs0, cs1, ca0, ca1, ca2, ca3, ca4, ca5 |
| |
| /* |
| * The control flow of an exiting thread rejoins us (that is, running |
| * threads which have taken an exception, be that a trap or an interrupt) |
| * here, as if it had taken an exception. We even use the mcause register |
| * to signal the exit "exception"; see .Lcommon_thread_exit. |
| */ |
| .Lexception_exiting_threads_rejoin: |
| /* |
| * FROM: above |
| * FROM: .Lcommon_thread_exit |
| * IRQ REQUIRE: deferred (about to set MTDC to nullptr) |
| * LIVE IN: mcause, mtval, mtdc, sp |
| * |
| * Atlas: |
| * mtdc: the interrupted context's sp (or zero, if coming from |
| * .Lcommon_thread_exit) |
| * sp: TrustedStack pointer (and in particular a spill frame we can use) |
| */ |
| |
| /* |
| * mtdc got swapped with the thread's csp, store it and clobber mtdc with |
| * zero (using t1 as a scratch register, because using source register index |
| * 0 with cspecialrw means "don't write" rather than "write zero"). The |
| * trusted stack pointer is solely in csp, now; if we take another trap |
| * before a new one is installed, or if the scheduler enables interrupts and |
| * we take one, we'll pull this zero out of mtdc, above. |
| */ |
| zeroOne t1 |
| cspecialrw ct1, mtdc, ct1 |
| csc ct1, TrustedStack_offset_csp(csp) |
| /* |
| * Atlas update: |
| * mtdc: zero |
| * sp: (still) TrustedStack pointer |
| */ |
| |
| // Store the rest of the special registers |
| cspecialr ct0, mepcc |
| csc ct0, TrustedStack_offset_mepcc(csp) |
| csrr t1, mstatus |
| csw t1, TrustedStack_offset_mstatus(csp) |
| #ifdef CONFIG_MSHWM |
| csrr t1, CSR_MSHWM |
| csw t1, TrustedStack_offset_mshwm(csp) |
| csrr t1, CSR_MSHWMB |
| csw t1, TrustedStack_offset_mshwmb(csp) |
| #endif |
| csrr t1, mcause |
| csw t1, TrustedStack_offset_mcause(csp) |
| // Atlas update: t1: copy of mcause |
| |
| /* |
| * If we hit one of the exception conditions that we should let compartments |
| * handle then maybe deliver it to the compartment (if it has a handler that |
| * we have the resources to invoke). |
| */ |
| //.Lexception_might_handle: |
| li a0, MCAUSE_CHERI |
| // LIVE OUT: sp |
| beq a0, t1, .Lhandle_error |
| /* |
| * A single test suffices to catch all of... |
| * - MCAUSE_INST_MISALINED (0), |
| * - MCAUSE_INST_ACCESS_FAULT (1), |
| * - MCAUSE_ILLEGAL_INSTRUCTION (2), |
| * - MCAUSE_BREAKPOINT (3), |
| * - MCAUSE_LOAD_MISALIGNED (4), |
| * - MCAUSE_LOAD_ACCESS_FAULT (5), |
| * - MCAUSE_STORE_MISALIGNED (6), |
| * - MCAUSE_STORE_ACCESS_FAULT (7) |
| */ |
| li a0, 0x8 |
| // LIVE OUT: sp |
| bltu t1, a0, .Lhandle_error |
| |
| //.Lexception_scheduler_call: |
| // TODO: On an ecall, we don't need to save any caller-save registers |
| |
| /* |
| * At this point, thread state is completely saved. Now prepare the |
| * scheduler context. |
| * Function signature of the scheduler entry point: |
| * TrustedStack *exception_entry(TrustedStack *sealedTStack, |
| * size_t mcause, size_t mepc, size_t mtval) |
| */ |
| LoadCapPCC ca0, .Lsealing_key_trusted_stacks |
| cseal ca0, csp, ca0 // sealed trusted stack |
| mv a1, t1 // mcause |
| cgetaddr a2, ct0 // mepcc address |
| csrr a3, mtval |
| // Fetch the stack, cgp and the trusted stack for the scheduler. |
| LoadCapPCC csp, switcher_scheduler_entry_csp |
| LoadCapPCC cgp, switcher_scheduler_entry_cgp |
| LoadCapPCC cra, switcher_scheduler_entry_pcc |
| /* |
| * Atlas: |
| * ra, gp: scheduler compartment context |
| * sp: scheduler thread context |
| * a0: sealed trusted stack pointer (opaque thread handle) |
| * a1: copy of mcause |
| * a2: copy of mepc |
| * a3: copy of mtval |
| * tp, t0, t1, t2, s0, s1, a4, a5: dead |
| */ |
| |
| // Zero everything apart from things explicitly passed to scheduler. |
| zeroAllRegistersExcept ra, sp, gp, a0, a1, a2, a3 |
| |
| // Call the scheduler. This returns the new thread in ca0. |
| cjalr cra |
| |
| //.Lexception_scheduler_return: |
| /* |
| * IFROM: above |
| * IRQ ASSUME: deferred (reachable only by IRQ-deferring reverse sentry) |
| * IRQ REQUIRE: deferred (mtdc is zero) |
| * LIVE IN: a0 |
| * |
| * Atlas: |
| * mtdc: (still) zero |
| * a0: sealed trusted stack pointer to bring onto core |
| */ |
| /* |
| * The interrupts-disabling return sentry handed to the scheduler as part of |
| * that cjalr may be captured on its stack, but as the scheduler is the |
| * topmost and only compartment in its thread (as it cannot make |
| * cross-compartment calls without faulting, due to the null presently in |
| * mtdc), there is very little that can go wrong as as a result of that |
| * capture. |
| */ |
| /* |
| * The scheduler may change interrupt posture or may trap (and infinite loop |
| * if it does so; see the top of exception_entry_asm and recall that mtdc is |
| * 0 at this point), but if it returns to us (that is, we reach here), the |
| * use of the sentry created by cjalr will have restored us to deferring |
| * interrupts, and we will remain in that posture until the mret in |
| * .Lcommon_context_install. |
| */ |
| |
| // Switch onto the new thread's trusted stack |
| LoadCapPCC csp, .Lsealing_key_trusted_stacks |
| cunseal csp, ca0, csp |
| // Atlas update: sp: unsealed target thread trusted stack pointer |
| /* |
| * LOCAL SEAL: if the scheduler has shed GL(obal) of the reference it gave |
| * us in a0, then sp will also lack GL(obal) after unsealing. This |
| * reference is not stored in memory (in the switcher, anyway), just mtdc. |
| * However, when this reference is extracted and sealed for the next |
| * context switch (in .Lexception_scheduler_call), the result will lack |
| * GL(obal), which will likely prove challenging for the scheduler. That |
| * is, this is an elaborate way for the scheduler to crash itself. |
| */ |
| |
| clw t0, TrustedStack_offset_mcause(csp) |
| // Atlas update: t0: stored mcause for the target thread |
| |
| /* |
| * Only now that we have done something that actually requires the tag of |
| * csp be set, put it into mtdc. If the scheduler has returned something |
| * untagged or something with the wrong otype, the cunseal will have left |
| * csp untagged and clw will trap with mtdc still 0. If we made it here, |
| * though, csp is tagged and so was tagged and correctly typed, and so it |
| * is safe to install it to mtdc. We won't cause traps between here and |
| * mret, so reentrancy is no longer a concern. |
| */ |
| cspecialw mtdc, csp |
| //.Lexception_scheduler_return_installed: |
| /* |
| * IRQ REQUIRE: deferred (TrustedStack spill frame is precious) |
| * Atlas update: mtdc: TrustedStack pointer |
| */ |
| |
| /* |
| * If mcause is MCAUSE_THREAD_INTERRUPT, then we will jump into the error |
| * handler: another thread has signalled that this thread should be |
| * interrupted. MCAUSE_THREAD_INTERRUPT is a reserved exception number that |
| * we repurpose to indicate explicit interruption. |
| */ |
| li t1, MCAUSE_THREAD_INTERRUPT |
| // LIVE OUT: mtdc, sp |
| beq t0, t1, .Lhandle_injected_error |
| |
| /* |
| * Environment call from M-mode is exception code 11. |
| * We need to skip the ecall instruction to avoid an infinite loop. |
| */ |
| li t1, 11 |
| clc ct2, TrustedStack_offset_mepcc(csp) |
| // Atlas update: t2: interrupted program counter to resume |
| // LIVE OUT: mtdc, sp, t2 |
| bne t0, t1, .Lcommon_context_install |
| cincoffset ct2, ct2, 4 |
| // Fall through to install context |
| |
| .Lcommon_context_install: |
| /* |
| * FROM: above |
| * FROM: .Lhandle_error_install_context |
| * FROM: .Lhandle_return_context_install |
| * IRQ REQUIRE: deferred (TrustedStack spill frame is precious) |
| * LIVE IN: mtdc, sp, t2 |
| * |
| * Atlas: |
| * mtdc, sp: TrustedStack pointer |
| * t2: target pcc to resume |
| * ra, gp, tp, t0, t1, s0, s1, a0, a1, a2, a3, a4, a5: dead |
| */ |
| /* |
| * All registers other than sp and t2 are in unspecified states and will be |
| * overwritten when we install the context. |
| */ |
| clw ra, TrustedStack_offset_mstatus(csp) |
| csrw mstatus, ra |
| #ifdef CONFIG_MSHWM |
| clw ra, TrustedStack_offset_mshwm(csp) |
| csrw CSR_MSHWM, ra |
| clw ra, TrustedStack_offset_mshwmb(csp) |
| csrw CSR_MSHWMB, ra |
| #endif |
| cspecialw mepcc, ct2 |
| |
| /* |
| * reloadRegisters restores registers in the order given, and we ensure that |
| * sp/csp (x2/c2) will be loaded last and will overwrite the trusted stack |
| * pointer with the thread's stack pointer. |
| */ |
| trustedReloadRegisters cra, cgp, ctp, ct0, ct1, ct2, cs0, cs1, ca0, ca1, ca2, ca3, ca4, ca5, csp |
| mret |
| |
| /** |
| * We are starting a forced unwind. This is reached either when we are unable |
| * to run an error handler, or when we do run an error handler and it instructs |
| * us to return. This treats all register values as undefined on entry. |
| */ |
| .Lcommon_force_unwind: |
| /* |
| * FROM: .Lhandle_error_handler_return_irqs |
| * FROM: .Lhandle_error_in_switcher |
| * FROM: .Lhandle_error_test_double_fault |
| * FROM: .Lhandle_error_test_too_many |
| * FROM: .Lhandle_error_try_stackless |
| * FROM: .Lswitch_csp_check |
| * IRQ REQUIRE: any |
| * LIVE IN: mtdc |
| * |
| * Atlas: |
| * mtdc: pointer to TrustedStack |
| */ |
| li a0, -ECOMPARTMENTFAIL |
| li a1, 0 |
| j switcher_after_compartment_call |
| |
| /** |
| * If we have a possibly recoverable error, see if we have a useful error |
| * handler. At this point, the register state will have been saved in the |
| * register-save area and so we just need to set up the environment. |
| * The handler will have this type signature: |
| * |
| * enum ErrorRecoveryBehaviour |
| * compartment_error_handler(struct ErrorState *frame, |
| * size_t mcause, |
| * size_t mtval); |
| */ |
| .Lhandle_error: |
| /* |
| * FROM: .Lexception_might_handle |
| * FROM: .Lhandle_injected_error |
| * IRQ REQUIRE: deferred (TrustedStack spill frame is precious) |
| * LIVE IN: sp |
| * |
| * Atlas: |
| * sp: pointer to TrustedStack |
| */ |
| /* |
| * We're now out of the exception path, so make sure that mtdc contains |
| * the trusted stack pointer. |
| */ |
| cspecialw mtdc, csp |
| /* |
| * Atlas update: |
| * mtdc: pointer to TrustedStack |
| * sp: (still) pointer to TrustedStack |
| */ |
| |
| //.Lhandle_error_switcher_pcc: |
| /* |
| * We want to make sure we can't leak any switcher state into error |
| * handlers, so if we're faulting in the switcher then we should force |
| * unwind. We never change the base of PCC in the switcher, so we can |
| * check for this case by ensuring that the spilled mepcc and our current |
| * pcc have the same base. |
| */ |
| auipcc ct0, 0 |
| clc ct1, TrustedStack_offset_mepcc(csp) |
| cgetbase t0, ct0 |
| cgetbase tp, ct1 |
| beq t0, tp, .Lhandle_error_in_switcher |
| // Atlas update: t1: a copy of mepcc |
| |
| //.Lhandle_error_not_switcher: |
| // Load the interrupted thread's stack pointer into ct0 |
| clc ct0, TrustedStack_offset_csp(csp) |
| // Atlas update: t0: interrupted thread's stack pointer |
| |
| /* |
| * If we have already unwound so far that the TrustedStack::frameoffset is |
| * pointing at TrustedStack::frames[0] -- that is, if the stack has no |
| * active frames on it -- then just go back to the context we came from, |
| * effectively parking this thread in a (slow) infinite loop. |
| */ |
| clhu tp, TrustedStack_offset_frameoffset(csp) |
| li t1, TrustedStack_offset_frames |
| // LIVE OUT: sp |
| beq tp, t1, .Lcommon_thread_exit |
| |
| addi tp, tp, -TrustedStackFrame_size |
| cincoffset ctp, csp, tp |
| // Atlas update: tp: pointer to current TrustedStackFrame |
| |
| // a0 indicates whether we're calling a stackless error handler (0: stack, |
| // 1: stackless) |
| li a0, 0 |
| // Atlas update: a0: stackful (0) or stackless (1) indicator, currently 0 |
| |
| // Allocate space for the register save frame on the stack. |
| cincoffset ct0, ct0, -(16*8) |
| |
| //.Lhandle_error_stack_oob: |
| /* |
| * WARNING: ENCODING SPECIFIC. |
| * |
| * The following depends on the fact that before-the-start values are not |
| * representable in the CHERIoT encoding and so will clear the tag. If |
| * this property changes then this will need to be replaced by a check that |
| * against the base of the stack. Note that this check can't be a simple |
| * cgetbase on ct0, because moving the address below the base sufficiently |
| * far that it's out of *representable* bounds will move the reported base |
| * value (base is a displacement from the address). |
| */ |
| cgettag t1, ct0 |
| |
| /* |
| * A value of 0xffff indicates no error handler. Both of our conditional |
| * paths want this value, but we can load it once, now. |
| */ |
| li s1, 0xffff |
| // Atlas update: s1: 0xffff |
| |
| /* |
| * If there isn't enough space on the stack, see if there's a stackless |
| * handler. |
| */ |
| // LIVE OUT: sp, tp, t0 |
| beqz t1, .Lhandle_error_try_stackless |
| |
| clc ct1, TrustedStackFrame_offset_calleeExportTable(ctp) |
| // Atlas: t1: pointer to callee's invoked export table entry |
| /* |
| * Set the export table pointer to point to the *start* of the export |
| * table. It will currently point to the entry point that was raised. |
| * |
| * TODO: We might want to pass this to the error handler, it might be |
| * useful for providing per-entry-point error results. |
| */ |
| cgetbase s0, ct1 |
| csetaddr ct1, ct1, s0 |
| clhu s0, ExportTable_offset_errorHandler(ct1) |
| |
| //.Lhandle_error_try_stackful: |
| /* |
| * A value of 0xffff indicates no error handler. If we found one, use it, |
| * otherwise fall through and try to find a stackless handler. |
| */ |
| // LIVE OUT: sp, tp, t0, t1, s0, a0 |
| bne s0, s1, .Lhandle_error_found |
| |
| .Lhandle_error_try_stackless: |
| /* |
| * FROM: above |
| * FROM: .Lhandle_error_stack_oob |
| * IRQ REQUIRE: deferred (TrustedStack spill frame is precious) |
| * LIVE IN: sp, tp, s1, t0 |
| * Atlas: |
| * sp: pointer to TrustedStack |
| * tp: pointer to current TrustedStackFrame |
| * t0: interrupted thread's stack pointer |
| * s1: 0xffff |
| */ |
| |
| clc ct1, TrustedStackFrame_offset_calleeExportTable(ctp) |
| /* |
| * Set the export table pointer to point to the *start* of the export |
| * table. It will currently point to the entry point that was raised. |
| */ |
| cgetbase s0, ct1 |
| csetaddr ct1, ct1, s0 |
| // Atlas: t1: pointer to callee's export table |
| clhu s0, ExportTable_offset_errorHandlerStackless(ct1) |
| /* |
| * A value of 0xffff indicates no error handler. Give up if there is no |
| * error handler for this compartment, having already tried any stackful |
| * handler. |
| */ |
| // LIVE OUT: mtdc |
| beq s0, s1, .Lcommon_force_unwind |
| |
| /* |
| * The stack may have had its tag cleared at this point, so for stackless |
| * handlers we need to restore the on-entry stack. |
| */ |
| clc ct0, TrustedStackFrame_offset_csp(ctp) |
| // Atlas: t0: target invocation's stack pointer, as of invocation start |
| |
| /* |
| * If this is the top (initial) stack frame, then the csp field is the value |
| * on entry and it is safe to use directly. Otherwise, we reconstruct the |
| * stack as it would have been on compartment invocation. |
| */ |
| cincoffset cs1, csp, TrustedStack_offset_frames |
| beq s1, tp, .Lhandle_stack_recovered |
| |
| //.Lhandle_stack_rebound: |
| /* |
| * The address of the stack pointer will point to the bottom of the |
| * caller's save area created by .Lswitch_entry_first_spill and following |
| * instructions, so we set the bounds to be the base up to the current |
| * address, giving the handler access to the entirety of this invocation's |
| * activation frame (except the caller save registers we spilled). |
| */ |
| cgetaddr a1, ct0 |
| cgetbase a2, ct0 |
| sub a1, a1, a2 |
| csetaddr ct0, ct0, a2 |
| // The code that installs the context expects the target stack to be in ct0 |
| csetboundsexact ct0, ct0, a1 |
| .Lhandle_stack_recovered: |
| /* |
| * FROM: above |
| * FROM: .Lhandle_error_try_stackless |
| * IRQ REQUIRE: deferred (TrustedStack spill frame is precious) |
| * LIVE IN: sp, tp, t0, t1, s0 |
| * |
| * Atlas: |
| * sp: pointer to TrustedStack |
| * tp: pointer to current TrustedStackFrame |
| * t0: pointer to the untrusted stack to use on invocation. Either below |
| * all activations, in the stackful handler case, or the entire |
| * invocation's stack (below the spill frame created by |
| * .Lswitch_entry_first_spill and following instructions). |
| * t1: pointer to callee's export table |
| * s0: offset from compartment PCC base to handler |
| */ |
| li a0, 1 |
| |
| .Lhandle_error_found: |
| /* |
| * FROM: above |
| * FROM: .Lhandle_error_try_stackful |
| * IRQ REQUIRE: deferred (TrustedStack spill frame is precious) |
| * LIVE IN: sp, tp, t0, t1, s0, a0 |
| * |
| * Atlas: |
| * sp: pointer to TrustedStack |
| * tp: pointer to current TrustedStackFrame |
| * t0: pointer to the untrusted stack to use on invocation. Either below |
| * all activations, in the stackful handler case, or the entire |
| * invocation's stack (below the spill frame created by |
| * .Lswitch_entry_first_spill and following instructions). |
| * t1: pointer to callee's export table |
| * s0: offset from compartment PCC base to handler |
| * a0: stackful (0) or stackless (1) indicator |
| */ |
| |
| // Increment the handler invocation count. |
| clhu s1, TrustedStackFrame_offset_errorHandlerCount(ctp) |
| addi s1, s1, 1 |
| csh s1, TrustedStackFrame_offset_errorHandlerCount(ctp) |
| |
| /* |
| * The low bit should be 1 while we are handling a fault. If we are in a |
| * double fault (that is, the value we just wrote back has its low bit 0), |
| * unwind now. |
| */ |
| //.Lhandle_error_test_double_fault: |
| andi ra, s1, 1 |
| // LIVE OUT: mtdc |
| beqz ra, .Lcommon_force_unwind |
| |
| /* |
| * If we have reached some arbitrary limit on the number of faults in a |
| * singe compartment calls, give up now. |
| * |
| * TODO: Make this a number based on something sensible, possibly something |
| * set per entry point. Some compartments (especially top-level ones) |
| * should be allowed to fault an unbounded number of times. |
| */ |
| //.Lhandle_error_test_too_many: |
| li ra, MAX_FAULTS_PER_COMPARTMENT_CALL |
| // LIVE OUT: mtdc |
| bgtu s1, ra, .Lcommon_force_unwind |
| |
| // Load the pristine pcc and cgp for the invoked compartment. |
| clc cra, ExportTable_offset_pcc(ct1) |
| clc cgp, ExportTable_offset_cgp(ct1) |
| /* |
| * Set the jump target to the error handler entry point. This may result in |
| * something out-of-bounds if the compartment has a malicious value for |
| * their error handler (hopefully caught at link or load time), but if it |
| * does then we will fault when attempting the cjalr below and force unwind |
| * (either because the cjalr itself will raise a fault, because ra is |
| * untagged, or because the resulting PCC is out of bounds and instruction |
| * fetch fails; either case results in a forced unwind, albeit by slightly |
| * different paths, with .Lhandle_error_switcher_pcc relevant for the former |
| * and .Lhandle_error_test_double_fault for the latter. |
| */ |
| cgetbase s1, cra |
| csetaddr cra, cra, s1 |
| cincoffset cra, cra, s0 |
| |
| /* |
| * If we're in an error handler with a stack, set up the stack, otherwise |
| * we just need to set up argument registers. |
| */ |
| //.Lhandle_error_test_stackful: |
| beqz a0, .Lhandle_error_stack_setup |
| |
| //.Lhandle_error_stackless_setup: |
| clw a0, TrustedStack_offset_mcause(csp) |
| csrr a1, mtval |
| li a2, 0 |
| cmove csp, ct0 |
| // Atlas: sp: taget compartment invocation stack pointer |
| j .Lhandle_error_handler_invoke |
| |
| .Lhandle_error_stack_setup: |
| /* |
| * FROM: .Lhandle_error_test_stackful |
| * IRQ REQUIRE: deferred (TrustedStack spill frame is precious) |
| * LIVE IN: ra, sp, gp, t0 |
| * |
| * Atlas: |
| * ra: handler entrypoint (with bounds of compartment's .text) |
| * sp: pointer to TrustedStack |
| * gp: target compartment cgp |
| * t0: pointer to the untrusted stack to use on invocation. This is |
| * presently sufficiently below all activations to provide space for an |
| * ErrorState structure. |
| */ |
| /* |
| * Set up the on-stack context, a compartment.h:/struct ErrorState value, |
| * which has the same layout at a TrustedStack spill frame. |
| * |
| * These begin with a PCC. To ensure that handlers do not have access to |
| * values (especially, capabilities) reachable through the trapping PCC, |
| * we clear the tag. Handlers of course retain access to values reachable |
| * through their own PCC and CGP. |
| */ |
| clc cs1, TrustedStack_offset_mepcc(csp) |
| ccleartag cs1, cs1 |
| csc cs1, TrustedStack_offset_mepcc(ct0) |
| /* |
| * Now copy the 15 GPRs from the trusted stack (sp). We use a2 as the |
| * source of the copy and a3 as the destination, preserving sp (TrustedStack |
| * pointer) and t0 (untrusted stack pointer to the base of the spill area). |
| */ |
| cincoffset ca2, csp, TrustedStack_offset_cra |
| cincoffset ca3, ct0, TrustedStack_offset_cra |
| copyContext /* dst = */ ca3, /* src = */ ca2, /* scratch = */ cs1, /* counter = */ a4 |
| |
| // Set up the arguments for the call |
| cmove ca0, ct0 |
| clw a1, TrustedStack_offset_mcause(csp) |
| csrr a2, mtval |
| cmove csp, ca0 |
| |
| .Lhandle_error_handler_invoke: |
| /* |
| * FROM: above |
| * FROM: .Lhandle_error_stackless_setup |
| * IRQ REQUIRE: any (see below) |
| * LIVE IN: mtdc, ra, sp, gp, a0, a1, a2 |
| * |
| * Atlas: |
| * mtdc: TrustedStack pointer |
| * ra: handler entrypoint (with bounds of compartment's .text) |
| * gp: target compartment cgp |
| * sp: target compartment invocation stack pointer |
| * a0, a1, a2: arguments to handler (see below) |
| * tp, t0, t1, t2, s0, s1, a3, a4, a5: dead (to be zeroed) |
| */ |
| /* |
| * At this point, the TrustedStack spill frame is no longer precious: either |
| * we have copied it down to the untrusted stack for the stackful handler's |
| * use or we have abandoned it in deciding to use the stackless handler. |
| * Thus, our "IRQ REQUIRE: any" above: it's safe to be preemptive here, |
| * though all paths to us in fact run with IRQs deferred. |
| * |
| * Since we are not using a sentry, but rather a capability constructed from |
| * the compartment's PCC (and handler offset value) to enter the |
| * compartment, enable interrupts now. |
| */ |
| /* |
| * For a stackful handler, the arguments are: |
| * - a0: equal to the invocation stack (sp), with a register spill frame |
| * here and above (the stack grows down!) |
| * - a1: mcause |
| * - a2: mtval |
| * |
| * While for stackless, the arguments are: |
| * - a0: mcause |
| * - a1: mtval |
| * - a2: zero |
| */ |
| csrsi mstatus, 0x8 |
| //.Lhandle_error_handler_invoke_irqs: |
| // IRQ ASSUME: enabled |
| |
| // Clear all other registers and invoke the handler |
| zeroAllRegistersExcept ra, sp, gp, a0, a1, a2 |
| cjalr cra |
| //.Lhandle_error_handler_return: |
| /* |
| * IFROM: above |
| * FROM: malice |
| * IRQ ASSUME: enabled (only IRQ-enabling reverse sentries given out) |
| * LIVE IN: mtdc, a0, sp |
| * |
| * Atlas: |
| * mtdc: pointer to this thread's TrustedStack |
| * a0: handler return value |
| * sp: target compartment invocation stack pointer |
| * gp, tp, t0, t1, t2, s0, s1, a1, a2, a3, a4, a5: dead (to be clobbered |
| * by replacement context |
| * or .Lcommon_force_unwind) |
| */ |
| /* |
| * The return sentry given to the handler as part of that cjalr could be |
| * captured in that compartment or any of its callers (recall similar |
| * commentary in switcher_after_compartment_call). Invoking this sentry, |
| * regardless of how one comes to hold it, and even if invocation is not |
| * matched to the call that constructed any given instance of it, will |
| * always result in popping the topmost trusted stack frame (at the time of |
| * invocation) and returning to its caller. |
| * |
| * Being robust to malicious entry here is facilitated by the requirements |
| * of the next block of code being minimal: mtdc must be a TrustedStack |
| * pointer, and we may try to dereference the provided sp, but we are |
| * prepared for that to trap (and induce forced-unwinding). |
| */ |
| |
| /* |
| * Now that we're back, defer interrupts again before we do anything that |
| * manipulates the TrustedStack. |
| * |
| * TODO: Eventually we'd like to move this down onto the paths where it |
| * actually matters and let most of this code run with IRQs enabled. |
| */ |
| csrci mstatus, 0x8 |
| //.Lhandle_error_handler_return_irqs: |
| // IRQ ASSUME: deferred |
| |
| /* |
| * Return values are compartment.h's enum ErrorRecoveryBehaviour : |
| * - InstallContext (0) |
| * - ForceUnwind (1) |
| * Other values are invalid and so we should do a forced unwind anyway. |
| */ |
| // LIVE OUT: mtdc |
| bnez a0, .Lcommon_force_unwind |
| |
| //.Lhandle_error_install_context: |
| // IRQ REQUIRE: deferred (TrustedStack spill frame precious, once populated) |
| /* |
| * We have been asked to install the new register context and resume. We do |
| * this by copying the register frame over the save area and entering the |
| * exception resume path. This may fault, but if it does then we will |
| * detect it as a double fault and forcibly unwind. |
| * |
| * The state of the target stack (sp) is expected to be common across both |
| * stackful and stackless handlers in the case of an InstallContext return. |
| * Above, in .Lhandle_error_stack_setup, we arranged for sp to point to a |
| * register spill frame (also passed in a0 for convenience from C). |
| * Stackless handlers are expected to arrange for sp to point to a register |
| * spill area before returning; compartments availing themselves of |
| * stackless handlers must also manage reserving space for such. |
| */ |
| |
| cspecialr ct1, mtdc |
| // Atlas update: t1: pointer to TrustedStack |
| #ifdef CONFIG_MSHWM |
| /* |
| * Update the spilled copy of the stack high water mark to ensure that we |
| * will clear all of the stack used by the error handler and the spilled |
| * context. |
| */ |
| csrr t0, CSR_MSHWM |
| csw t0, TrustedStack_offset_mshwm(ct1) |
| #endif |
| clhu tp, TrustedStack_offset_frameoffset(ct1) |
| addi tp, tp, -TrustedStackFrame_size |
| // Atlas update: tp: pointer to the current available trusted stack frame. |
| cincoffset ctp, ct1, tp |
| |
| /* |
| * The PCC the handler has given to us is not particularly trusted and might |
| * be an attempt to escape from the compartment. Confine it to being |
| * derived from the compartment's (static) PCC. This is a multi-step |
| * process, in which we... |
| * |
| * 1. Load the (tagged) PCC for the compartment, which is the 0th word in |
| * the ExportTable. |
| */ |
| clc ct0, TrustedStackFrame_offset_calleeExportTable(ctp) |
| cgetbase s0, ct0 |
| csetaddr ct0, ct0, s0 |
| clc ct0, ExportTable_offset_pcc(ct0) |
| // Atlas update: t0: compartment .text / PCC |
| |
| // 2. Load the untrusted PCC from the handler's returned spill area (sp). |
| clc cra, TrustedStack_offset_mepcc(csp) |
| |
| /* |
| * 3. Copy the address from the returned PCC into the compartment's PCC, |
| * which will result in an out-of-bounds capability if the handler was |
| * trying anything fishy. |
| */ |
| cgetaddr ra, cra |
| csetaddr ct2, ct0, ra |
| // Atlas update: t2: program counter to resume |
| |
| /* |
| * Now copy everything else from the stack up into the trusted saved |
| * context, using a2 as the source and a3 as the destination, preserving sp |
| * (the untrusted stack pointer) and t1 (TrustedStack pointer). |
| */ |
| cincoffset ca2, csp, TrustedStack_offset_cra |
| cincoffset ca3, ct1, TrustedStack_offset_cra |
| copyContext /* dst = */ ca3, /* src = */ ca2, /* scratch = */ cs1, /* counter = */ a4 |
| |
| /* |
| * Increment the handler invocation count. We have now returned and |
| * finished touching any data from the error handler that might cause a |
| * fault. Any subsequent fault is not treated as a double fault. It might |
| * be a fault loop, but that will be caught by the fault limit check. |
| */ |
| clh s1, TrustedStackFrame_offset_errorHandlerCount(ctp) |
| addi s1, s1, 1 |
| csh s1, TrustedStackFrame_offset_errorHandlerCount(ctp) |
| |
| /* |
| * Now that the context is set up, let the exception handler code deal with |
| * it. It expects the context to be in csp, so move the context pointer |
| * there. |
| */ |
| cmove csp, ct1 |
| // LIVE OUT: mtdc, sp, t2 |
| j .Lcommon_context_install |
| |
| .Lhandle_injected_error: |
| /* |
| * FROM: .Lexception_scheduler_return_installed |
| * IRQ REQUIRE: deferred (TrustedStack spill frame is precious) |
| * LIVE IN: mtdc, sp |
| * |
| * Atlas: |
| * mtdc: TrustedStack pointer |
| * sp: TrustedStack pointer (a copy of mtdc) |
| */ |
| #ifdef CONFIG_MSHWM |
| clw ra, TrustedStack_offset_mshwm(csp) |
| csrw CSR_MSHWM, ra |
| clw ra, TrustedStack_offset_mshwmb(csp) |
| csrw CSR_MSHWMB, ra |
| #endif |
| j .Lhandle_error |
| |
| .Lcommon_defer_irqs_and_thread_exit: |
| /* |
| * FROM: switcher_after_compartment_call |
| * IRQ REQUIRE: any |
| */ |
| csrci mstatus, 0x8 |
| //.Lcommon_deferred_irqs_and_thread_exit: |
| // IRQ ASSUME: deferred |
| |
| /** |
| * Signal to the scheduler that the current thread is finished |
| */ |
| .Lcommon_thread_exit: |
| /* |
| * FROM: above |
| * FROM: .Lhandle_error_not_switcher |
| * IRQ REQUIRE: deferred (about to zero out MTDC and join exception path) |
| * LIVE IN: mtdc |
| * |
| * Atlas: |
| * mtdc: pointer to TrustedStack |
| */ |
| csrw mcause, MCAUSE_THREAD_EXIT |
| /* |
| * mtval may have been updated by the action of other threads in the system |
| * and holds the last value latched during an exception. From the |
| * scheduler's perspective, thread exits are a kind of exception, and |
| * exceptions get to see mtval. Write a constant value to mtval to act more |
| * like an architectural fault and to close a small information leak to the |
| * scheduler's event handler. |
| */ |
| csrw mtval, MCAUSE_THREAD_EXIT |
| /* |
| * The thread exit code expects the TrustedStack pointer to be in csp and |
| * the thread's stack pointer to be in mtdc. After thread exit, we don't |
| * need the stack pointer so just put zero there. |
| */ |
| zeroOne sp |
| cspecialrw csp, mtdc, csp |
| // LIVE OUT: mtdc, sp |
| j .Lexception_exiting_threads_rejoin |
| |
| /* |
| * Some switcher instructions' traps are handled specially, by looking at |
| * the offset of mepcc. Otherwise, we're off to a force unwind. |
| */ |
| .Lhandle_error_in_switcher: |
| /* |
| * FROM: .Lhandle_error_switcher_pcc |
| * IRQ REQUIRE: deferred (TrustedStack spill frame is precious) |
| * LIVE IN: mtdc, t1 |
| * |
| * Atlas: |
| * mtdc: pointer to TrustedStack |
| * t1: A copy of mepcc, the faulting program counter |
| */ |
| auipcc ctp, %cheriot_compartment_hi(.Lswitch_entry_first_spill) |
| cincoffset ctp, ctp, %cheriot_compartment_lo_i(.Lhandle_error_in_switcher) |
| bne t1, tp, .Lcommon_force_unwind |
| li a0, -ENOTENOUGHSTACK |
| li a1, 0 |
| |
| /* |
| * Cause the interrupted thread to resume as if a return had just executed. |
| * We do this by vectoring to a `cjalr ra` (`cret`) instruction through |
| * `mepcc`; whee! Overwrites the stored context a0 and a1 with the current |
| * values of those registers, effectively passing them through |
| * .Lcommon_context_install. |
| */ |
| .Lhandle_return_context_install: |
| /* |
| * FROM: above |
| * IRQ REQUIRE: deferred (TrustedStack spill frame is precious) |
| * LIVE IN: sp, a0, a1 |
| * |
| * Atlas: |
| * sp: pointer to TrustedStack |
| * a0, a1: return values to the caller |
| */ |
| auipcc ct2, %cheriot_compartment_hi(.Lswitch_just_return) |
| cincoffset ct2, ct2, %cheriot_compartment_lo_i(.Lhandle_return_context_install) |
| csc ca0, TrustedStack_offset_ca0(csp) |
| csc ca1, TrustedStack_offset_ca1(csp) |
| // LIVE OUT: sp, t2 |
| j .Lcommon_context_install |
| |
| .Lexception_reentered: |
| /* |
| * FROM: exception_entry_asm |
| * FROM: .Lexception_reentered |
| * IRQ REQUIRE: deferred (an IRQ before we reprogram MTCC could escape |
| * looping) |
| */ |
| /* |
| * We've reentered our exception handler, a "double fault" of sorts. Make |
| * sure that we end up in an architectural trap loop: clobber mtcc, so that |
| * that trap attempts to vector to an untagged PCC, thereby causing another |
| * trap, which immediately traps, and so on. |
| * |
| * We could instead zero mtdc, ensuring that we spin through several |
| * instructions (taking a trap then running enough of exception_entry_asm |
| * until we again trapped), but this is less architecturally visible. |
| */ |
| /* |
| * Writing cnull to mtcc takes two instructions because cspecialw is an |
| * alias for cspecialrw with a zero source, which means "don't write". So, |
| * put nullptr in a register with non-zero index, and then put that in mtcc. |
| */ |
| zeroOne sp |
| cspecialw mtcc, csp |
| // Take a trap and wedge the machine on that null MTCC |
| clc csp, 0(csp) |
| j .Lexception_reentered |
| |
| .size exception_entry_asm, . - exception_entry_asm |
| |
| /******************************************************************************* |
| * Switcher-exported library functions. |
| * |
| * These all provide some reflection on the switcher's state. |
| * |
| * At the moment, all of these avoid touching any registers except the argument |
| * registers, which means that we can define an alternative calling convention |
| * for them in the future to allow the compiler to preserve values in the |
| * temporary registers across calls. |
| * |
| * These are all part of the switcher's PCC and so will be covered by the same |
| * defence that the switcher has against being made to trap at unexpected |
| * times: any trap in the switcher will force unwind the caller's trusted stack |
| * frame. As such, no trap here can leak data. |
| * |
| * These functions must not use the stack and must ensure that the clobber all |
| * registers that hold sensitive state on the way out. |
| ******************************************************************************/ |
| |
| // Returns whether the trusted stack has space for N more calls. |
| .section .text, "ax", @progbits |
| .p2align 2 |
| .type __Z23trusted_stack_has_spacei,@function |
| __Z23trusted_stack_has_spacei: |
| /* |
| * FROM: malice |
| * IRQ ASSUME: deferred |
| * LIVE IN: mtdc, callee-save, ra, a0 |
| * |
| * Atlas: |
| * mtdc: pointer to TrustedStack (or nullptr if from buggy scheduler) |
| * ra: return pointer (guaranteed because this symbol is reachable only |
| * through an interrupt-disabling forward-arc sentry) |
| * a0: requested number of trusted stack frames |
| */ |
| li a2, TrustedStackFrame_size |
| mul a2, a0, a2 |
| // Atlas update: a2: requested number trusted stack frames, in bytes |
| /* |
| * Load the trusted stack into the return register, so that we clobber it on |
| * the way out. Nothing here should trap, but if it does we'll forcibly |
| * unwind (see .Lhandle_error_in_switcher) and also clobber this pointer. |
| */ |
| cspecialr ca0, mtdc |
| /* |
| * TrustedStack::frames[] is a FAM at the end of the structure, and |
| * ::frameoffset codes for our current position therein (by counting bytes |
| * relative to the start of the TrustedStack). We have sufficiently many |
| * frames if the TrustedStack length minus ::frameoffset is greater than |
| * the requested number of bytes. |
| */ |
| clhu a1, TrustedStack_offset_frameoffset(ca0) |
| // Atlas update: a1: this thread's TrustedStack::frameoffset |
| cgetlen a0, ca0 |
| // Atlas update: a0: length of this thread's TrustedStack |
| sub a0, a0, a1 |
| sltu a0, a2, a0 |
| // LIVE OUT: mtdc, a0 |
| cret |
| |
| // Reveal the stack pointer given to this compartment invocation |
| .section .text, "ax", @progbits |
| .p2align 2 |
| .type __Z22switcher_recover_stackv,@function |
| __Z22switcher_recover_stackv: |
| /* |
| * FROM: malice |
| * IRQ ASSUME: deferred |
| * LIVE IN: mtdc, callee-save, ra |
| * |
| * Atlas: |
| * mtdc: pointer to TrustedStack (or nullptr if buggy scheduler) |
| * ra: return pointer (guaranteed because this symbol is reachable only |
| * through an interrupt-disabling forward-arc sentry) |
| */ |
| /* |
| * Load the trusted stack pointer into a register that we will clobber after |
| * two instructions. |
| */ |
| cspecialr ca0, mtdc |
| // Atlas update: a0: pointer to TrustedStack |
| clhu a1, TrustedStack_offset_frameoffset(ca0) |
| // Atlas update: a1: TrustedStack::frameoffset |
| addi a1, a1, -TrustedStackFrame_size |
| // Atlas update: a1: offset of current TrustedStackFrame |
| cincoffset ca0, ca0, a1 |
| // Atlas update: a0: pointer to current TrustedStackFrame |
| clc ca0, TrustedStackFrame_offset_csp(ca0) |
| // Atlas update: a0: saved stack pointer at time of frame creation |
| /* |
| * If this is the first frame, then the recovered stack will be the stack |
| * on entry, and can be returned directly. |
| */ |
| li a2, TrustedStack_offset_frames |
| // Atlas update: a2: dead but exposed: TrustedStack_offset_frames |
| beq a1, a2, 0f |
| |
| /* |
| * Otherwise, this is not the first frame, and the TrustedStackFrame::csp |
| * value is pointing to the spills done at .Lswitch_entry_first_spill. Redo |
| * the stack chopping done at .Lswitch_stack_chop to recompute the bounds |
| * we would have given to the callee. |
| */ |
| cgetaddr a1, ca0 |
| cgetbase a2, ca0 |
| sub a1, a1, a2 |
| csetaddr ca0, ca0, a2 |
| csetboundsexact ca0, ca0, a1 |
| /* |
| * Atlas update: |
| * a1: dead but exposed: the length of the stack |
| * a2: dead but exposed: base address of the stack |
| */ |
| 0: |
| // LIVE OUT: mtdc, a0 |
| cret |
| |
| .section .text, "ax", @progbits |
| .p2align 2 |
| .type __Z30trusted_stack_interrupt_threadPv,@function |
| __Z25switcher_interrupt_threadPv: |
| /* |
| * FROM: malice |
| * IRQ ASSUME: deferred |
| * LIVE IN: mtdc, callee-save, ra, a0 |
| * |
| * Atlas: |
| * mtdc: pointer to TrustedStack (or nullptr if buggy scheduler) |
| * a0: sealed pointer to target thread TrustedStack |
| * ra: return pointer (guaranteed because this symbol is reachable only |
| * through an interrupt-disabling forward-arc sentry) |
| */ |
| /* |
| * Because this function involves looking across two threads' states, it |
| * needs to run with preemption prohibited, and that means IRQs deferred. |
| */ |
| |
| // Load the unsealing key |
| LoadCapPCC ca1, .Lsealing_key_trusted_stacks |
| /* |
| * The target capability is in ca0. Unseal, clobbering our authority; |
| * check tag; and load the entry point offset. |
| */ |
| cunseal ca1, ca0, ca1 |
| // Atlas update: a1: unsealed pointer to target thread TrustedStack |
| /* |
| * LOCAL SEAL: Nothing herein depends on a1 being GL(obal). |
| */ |
| cgettag a0, ca1 |
| // a0 (return register) now contains the tag. We return false on failure |
| // so can just branch to the place where we zero non-return registers from |
| // here and it will contain faluse on failure. |
| beqz a0, .Lswitcher_interrupt_thread_return |
| |
| // A thread can't interrupt itself, return failure if it tries. |
| cspecialr ca2, mtdc |
| li a0, 0 |
| beq a2, a1, .Lswitcher_interrupt_thread_return |
| // Atlas update: a2: unsealed pointer to current thread TrustedStack |
| |
| /* |
| * We allow the target thread to be interrupted if (and only if) the caller |
| * is in the same compartment as the interrupted thread. We will determine |
| * this by checking if the base of the two export table entries from the |
| * top of the trusted stack frames match. |
| */ |
| |
| // Helper macro that loads the export table from the register containing the |
| // trusted stack. The two arguments must be different registers. |
| .macro LoadExportTable result, trustedStack |
| clhu \result, TrustedStack_offset_frameoffset(\trustedStack) |
| addi \result, \result, -TrustedStackFrame_size |
| cincoffset c\result, \trustedStack, \result |
| clc c\result, TrustedStackFrame_offset_calleeExportTable(c\result) |
| cgetbase \result, c\result |
| .endm |
| |
| LoadExportTable a3, ca1 |
| cspecialr ca0, mtdc |
| LoadExportTable a2, ca0 |
| |
| // ca1 now contains the unsealed capability for the target thread, a3 |
| // contains the base of the export table entry for that thread, a2 the base |
| // of the export table for our thread. |
| li a0, 42 |
| |
| // If the two export table entries differ, return. |
| bne a2, a3, .Lswitcher_interrupt_thread_return |
| // Atlas update: a1, a2, a3: dead (to be zeroed) |
| |
| /* |
| * Mark the thread as interrupted. Store a magic value in mcause. This |
| * value will not be overwritten by a trap before the scheduler sees the |
| * target thread, since we are on core and it isn't. |
| */ |
| li a2, MCAUSE_THREAD_INTERRUPT |
| csw a2, TrustedStack_offset_mcause(ca1) |
| // Return success |
| li a0, 1 |
| .Lswitcher_interrupt_thread_return: |
| zeroRegisters a1, a2, a3 |
| cret |
| |
| // Get a sealed pointer to the current thread's TrustedStack |
| .section .text, "ax", @progbits |
| .p2align 2 |
| .type __Z23switcher_current_threadv,@function |
| __Z23switcher_current_threadv: |
| /* |
| * FROM: malice |
| * IRQ ASSUME: deferred |
| * LIVE IN: mtdc, callee-save, ra |
| * |
| * Atlas: |
| * mtdc: pointer to TrustedStack (or nullptr if buggy scheduler) |
| * ra: return pointer (guaranteed because this symbol is reachable only |
| * through an interrupt-disabling forward-arc sentry) |
| */ |
| |
| LoadCapPCC ca0, .Lsealing_key_trusted_stacks |
| // Atlas update: a0: sealing authority for trusted stacks |
| cspecialr ca1, mtdc |
| // Atlas update: a1: copy of mtdc |
| cseal ca0, ca1, ca0 |
| li a1, 0 |
| /* |
| * Atlas update: |
| * a0: sealed copy of mtdc, this thread's TrustedStack |
| * a1: zero |
| */ |
| cret |
| |
| // Get a pointer to this thread's hazard pointers array |
| .section .text, "ax", @progbits |
| .p2align 2 |
| .type __Z28switcher_thread_hazard_slotsv,@function |
| __Z28switcher_thread_hazard_slotsv: |
| /* |
| * FROM: malice |
| * IRQ ASSUME: deferred |
| * LIVE IN: mtdc, callee-save, ra |
| * |
| * Atlas: |
| * mtdc: pointer to TrustedStack (or nullptr if buggy scheduler) |
| * ra: return pointer (guaranteed because this symbol is reachable only |
| * through an interrupt-disabling forward-arc sentry) |
| */ |
| |
| cspecialr ca0, mtdc |
| |
| // If this traps (from null mtdc, say), we'll forcibly unwind. |
| clc ca0, TrustedStack_offset_hazardPointers(ca0) |
| // Atlas update: a0: pointer to hazard pointers |
| |
| cret |
| |
| // Get the current thread's integer ID |
| .section .text, "ax", @progbits |
| .p2align 2 |
| .type __Z13thread_id_getv,@function |
| __Z13thread_id_getv: |
| /* |
| * FROM: malice |
| * IRQ ASSUME: deferred |
| * LIVE IN: mtdc, callee-save, ra |
| * |
| * Atlas: |
| * mtdc: pointer to TrustedStack (or nullptr if buggy scheduler) |
| * ra: return pointer (guaranteed because this symbol is reachable only |
| * through an interrupt-disabling forward-arc sentry) |
| */ |
| |
| cspecialr ca0, mtdc |
| /* |
| * If this is a null pointer, don't try to dereference it and report that |
| * we are thread 0. This permits the debug code to work even from things |
| * that are not real threads. |
| */ |
| cgettag a1, ca0 |
| // Atlas update: a1: tag of a0/mtdc |
| beqz a1, 0f |
| clh a0, TrustedStack_offset_threadID(ca0) |
| // Atlas update: a0: integer ID of current thread |
| 0: |
| cret |
| |
| |
| // Return the stack high-water mark |
| .section .text, "ax", @progbits |
| .p2align 2 |
| .type __Z25stack_lowest_used_addressv,@function |
| __Z25stack_lowest_used_addressv: |
| csrr a0, CSR_MSHWM |
| cret |
| |
| // Reset the count of error handler invocations in this compartment invocation |
| .section .text, "ax", @progbits |
| .p2align 2 |
| .type __Z39switcher_handler_invocation_count_resetv,@function |
| __Z39switcher_handler_invocation_count_resetv: |
| /* |
| * FROM: malice |
| * IRQ ASSUME: deferred |
| * LIVE IN: mtdc, callee-save, ra |
| * |
| * Atlas: |
| * mtdc: pointer to TrustedStack (or nullptr if buggy scheduler) |
| * ra: return pointer (guaranteed because this symbol is reachable only |
| * through an interrupt-disabling forward-arc sentry) |
| */ |
| |
| cspecialr ca1, mtdc |
| // Atlas update: a1: copy of mtdc |
| clhu a0, TrustedStack_offset_frameoffset(ca1) |
| addi a0, a0, -TrustedStackFrame_size |
| // Atlas update: a0: offset of the current trusted stack frame |
| cincoffset ca1, ca1, a0 |
| /* |
| * Atlas update: |
| * a0: dead |
| * a1: pointer to current TrustedStack::frame |
| */ |
| clh a0, TrustedStackFrame_offset_errorHandlerCount(ca1) |
| // Atlas update: a0: current invocation count (for return) |
| // Reset invocation count |
| csh zero, TrustedStackFrame_offset_errorHandlerCount(ca1) |
| // Atlas update: a1: dead (to be zeroed) |
| 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 |
| // it's not urgent. |
| .section .compartment_export_table,"a",@progbits |
| export_table_start: |
| .space 20, 0 |
| |
| /** |
| * Helper that exports a switcher function as a library call. |
| */ |
| .macro export function, prefix=__library_export_libcalls |
| .type \prefix\function,@object |
| .globl \prefix\function |
| .p2align 2 |
| \prefix\function: |
| .half \function-switcher_code_start |
| // Number of registers to clear (ignored for library exports) |
| .byte 0 |
| // Interrupts disabled. |
| .byte 16 |
| .size \prefix\function, 4 |
| .endm |
| |
| // Switcher entry point must be first. |
| /* |
| * We mangle the switcher export as if it were a compartment call, but see |
| * loader/boot.cc's special handling of this entry. |
| */ |
| export __Z26compartment_switcher_entryz, __export_switcher |
| export __Z23trusted_stack_has_spacei |
| export __Z22switcher_recover_stackv |
| export __Z25switcher_interrupt_threadPv |
| export __Z23switcher_current_threadv |
| export __Z28switcher_thread_hazard_slotsv |
| export __Z13thread_id_getv |
| export __Z25stack_lowest_used_addressv |
| export __Z39switcher_handler_invocation_count_resetv |