Merge remote-tracking branch 'upstream/main' into update Change-Id: Ica4acb814c9b3d180926ef3f3c70be79d4358af6
diff --git a/scripts/dot_from_switcher.lua b/scripts/dot_from_switcher.lua new file mode 100644 index 0000000..387ec66 --- /dev/null +++ b/scripts/dot_from_switcher.lua
@@ -0,0 +1,414 @@ +#!/usr/bin/env lua5.3 +-- Copyright CHERIoT Contributors. +-- SPDX-License-Identifier: MIT + +local infile = io.input() + +local labels = {} -- label |-> style array +local label_outs = {} -- label |-> label |-> style +local label_ins = {} -- label |-> label |-> () + +local label_irq_assume = {} -- label |-> { "deferred", "enabled" } +local label_irq_require = {} -- label |-> { "deferred", "enabled" } + +local exports = {} -- label |-> () + +local lastlabels = {} + +local function debug() end +if true then + function debug(...) + io.stderr:write(string.format("DBG %s\n", + table.concat(table.pack(...), " "))) + end +end + +local function end_label(clear) + -- At the end of a lable, if we haven't been told that we've assumed a + -- different IRQ disposition than required on the way in, then inherit + -- assumptions from requirements + local lastlabel = lastlabels[#lastlabels] + if label_irq_assume[lastlabel] == nil then + label_irq_assume[lastlabel] = + assert(label_irq_require[lastlabel], + "Missing IRQ requirement for prior label, cannot inherit assumption") + end + + if clear then lastlabels = {} end +end + +local function found_label(label, where) + assert(label) + assert(where) + + assert(labels[label] == nil, label) + labels[label] = { ("tooltip=%s"):format(where) } + + if label_ins[label] == nil then + label_ins[label] = {} + end + + if label_outs[label] == nil then + label_outs[label] = {} + end + + if #lastlabels > 0 then end_label(false) end + + table.insert(lastlabels, label) + if #lastlabels > 2 then table.remove(lastlabels, 1) end + assert(#lastlabels <= 2) +end + +local function found_edge_from(from, to, style) + assert(from) + assert(to) + + debug("", "EDGE FROM", from, to) + + if label_outs[from] == nil then label_outs[from] = {} end + label_outs[from][to] = {style} +end + +local function found_edge_to(from, to) + assert(from) + assert(to) + + debug("", "EDGE TO", from, to) + + if label_ins[to] == nil then label_ins[to] = {} end + label_ins[to][from] = true +end + +local lineix = 1 + +-- Read and discard until we get to the good stuff +for line in infile:lines("*l") do + debug("HUNT", line) + if line:match(".globl __Z26compartment_switcher_entryz") then break end + lineix = lineix + 1 +end + +local IRQ_dispositions = + { ["any"] = true + , ["deferred"] = true + , ["enabled"] = true + } + +-- And here we go +for line in infile:lines("*l") do + local label + + debug("LINE", line) + + -- numeric labels are suppresed + label = line:match("^(%d+):$") + if label then + debug("", "Numeric label") + goto nextline + end + + -- local labels + label = line:match("^(%.L.*):$") + if label then + debug("", "Local label") + found_label(label, lineix) + if #lastlabels > 1 then + found_edge_to(lastlabels[#lastlabels-1], lastlabels[#lastlabels]) + end + goto nextline + end + + -- documentation-only labels + label = line:match("^//(%.L.*):$") + if label then + debug("", "Documentation label", #lastlabels) + found_label(label, lineix) + + -- Documentation labels are presumed to be fall-through and do not need the + -- clutter of "FROM: above" + assert(#lastlabels > 1) + found_edge_from(lastlabels[#lastlabels-1], lastlabels[#lastlabels]) + found_edge_to(lastlabels[#lastlabels-1], lastlabels[#lastlabels]) + + -- Documentation labels are presumed to inherit the IRQ disposition from + -- "above" as well. + label_irq_require[lastlabels[#lastlabels]] = + assert(label_irq_assume[lastlabels[#lastlabels-1]], + "Missing IRQ disposition for prior label") + + goto nextline + end + + -- other global labels + label = line:match("^([%w_]*):$") + if label then + debug("", "global label") + found_label(label, lineix) + if #lastlabels > 1 then + found_edge_to(lastlabels[#lastlabels-1], lastlabels[#lastlabels]) + end + exports[label] = true + goto nextline + end + + -- [cm]ret clear the last label, preventing fallthru + if line:match("^%s+[cm]ret$") then + debug("", "[cm]ret") + end_label(true) + goto nextline + end + + -- unconditonal jumps add an edge and clear the last label, since we cannot + -- be coming "FROM: above" + label = line:match("^%s+j%s+(%g*)$") + if label then + debug("", "Jump") + assert(#lastlabels > 0) + found_edge_to(lastlabels[#lastlabels], label) + end_label(true) + goto nextline + end + + -- branches add edges to local labels + label = line:match("^%s+b.*,%s*(%.L%g*)$") + if label then + debug("", "Branch") + assert(#lastlabels > 0) + found_edge_to(lastlabels[#lastlabels], label) + goto nextline + end + + -- OK, now hunt for structured comments. + line = line:match("^%s*%*%s*(%S.*)$") or line:match("^%s*//%s*(%S.*)$") + if not line then goto nextline end + + -- "FROM: malice" annotations promote lastlabel to being exported + label = line:match("^FROM:%s+malice%s*") + if label then + debug("", "Malice", #lastlabels) + assert(#lastlabels > 0) + exports[lastlabels[#lastlabels]] = true + goto nextline + end + + -- "FROM: above" + label = line:match("^FROM:%s+above%s*") + if label then + debug("", "Above") + assert(#lastlabels > 1) + found_edge_from(lastlabels[#lastlabels-1], lastlabels[#lastlabels]) + + -- "FROM: above" implies IRQ requirements, too + label_irq_require[lastlabels[#lastlabels]] = + assert(label_irq_assume[lastlabels[#lastlabels-1]], + "Missing IRQ disposition for prior label") + + goto nextline + end + + -- "IFROM: above" + label = line:match("^IFROM:%s+above%s*") + if label then + debug("", "Above") + assert(#lastlabels > 1) + found_edge_from(lastlabels[#lastlabels-1], + lastlabels[#lastlabels], + "style=dashed") + + -- "IFROM: above" implies IRQ requirements, too + label_irq_require[lastlabels[#lastlabels]] = + assert(label_irq_assume[lastlabels[#lastlabels-1]], + "Missing IRQ disposition for prior label") + + goto nextline + end + + -- "FROM: cross-call" no-op + label = line:match("^FROM:%s+cross%-call%s*") + if label then + goto nextline + end + + -- "FROM: interrupt" no-op + label = line:match("^FROM:%s+interrupt%s*") + if label then + goto nextline + end + + -- "FROM: error" no-op + label = line:match("^FROM:%s+interrupt%s*") + if label then + goto nextline + end + + -- "FROM: $symbol" + label = line:match("^FROM:%s+(%S+)%s*") + if label then + debug("", "FROM", lastlabels[#lastlabels], label) + assert(#lastlabels > 0) + found_edge_from(label, lastlabels[#lastlabels]) + goto nextline + end + + -- "IFROM: $symbol" + label = line:match("^IFROM:%s+(%S+)%s*") + if label then + debug("", "IFROM", lastlabels[#lastlabels], label) + assert(#lastlabels > 0) + found_edge_from(label, lastlabels[#lastlabels], "style=dashed") + goto nextline + end + + -- "IFROM: $symbol" + label = line:match("^ITO:%s+(%S+)%s*") + if label then + debug("", "ITO", lastlabels[#lastlabels], label) + assert(#lastlabels > 0) + found_edge_to(lastlabels[#lastlabels], label, "style=dashed") + goto nextline + end + + -- "IRQ ASSUME: {deferred,enabled}" + label = line:match("^IRQ ASSUME:%s+(%S+)%s*") + if label then + debug("", "IRQ ASSUME", lastlabels[#lastlabels], label) + assert (IRQ_dispositions[label]) + label_irq_assume[lastlabels[#lastlabels]] = label + goto nextline + end + + -- "IRQ REQURE: {deferred,enabled}" + label = line:match("^IRQ REQUIRE:%s+(%S+)%s*") + if label then + debug("", "IRQ", lastlabels[#lastlabels], label) + assert (IRQ_dispositions[label]) + label_irq_require[lastlabels[#lastlabels]] = label + goto nextline + end + + -- Stop reading when we get to the uninteresting library exports + if line:match("Switcher%-exported library functions%.$") then + debug("", "Break") + break + end + + ::nextline:: + lineix = lineix + 1 +end + +-- Take adjacency matrix representation and add lists. +label_inls = {} +label_outls = {} +for focus, _ in pairs(labels) do + label_inls[focus] = {} + label_outls[focus] = {} + + for from, _ in pairs(label_ins[focus]) do + assert(labels[from]) + assert(label_outs[from][focus], + string.format("%s in from %s but no out edge", focus, from)) + assert( label_irq_require[focus] == "any" + or label_irq_assume[from] == label_irq_require[focus], + string.format("IRQ-invalid arc from %s (%s) to %s (%s)", + from, label_irq_assume[from], focus, label_irq_require[focus])) + + table.insert(label_inls[focus], from) + end + for to, _ in pairs(label_outs[focus]) do + assert(labels[to]) + assert(label_ins[to][focus], + string.format("%s out to %s but no in edge", focus, to)) + assert( label_irq_require[to] == "any" + or label_irq_assume[focus] == label_irq_require[to], + string.format("IRQ-invalid arc from %s (%s) to %s (%s)", + focus, label_irq_assume[focus], to, label_irq_require[to])) + + table.insert(label_outls[focus], to) + end +end + +local function render_exports(...) + local args = {...} + + local nexports = 0 + for export, _ in pairs(exports) do nexports = nexports + 1 end + assert(nexports == #args, + ("Wrong number of exports: %d != %d"):format(nexports, #args)) + + print(" { rank=min; edge [style=invis]; ") + + for _, export in ipairs(args) do + assert(exports[export], "Purported export isn't") + print("", ("%q"):format(export), ";") + end + + for i = 1, #args-1 do + print("", ("%q -> %q ;"):format(args[i], args[i+1])) + end + + print(" }") +end + +print("digraph switcher {") + +-- Put all our exports at the top of the graph, in a fixed order. +render_exports(".Lhandle_error_handler_return", + "exception_entry_asm", + "switcher_after_compartment_call", + "__Z26compartment_switcher_entryz") + +for from, from_params in pairs(labels) do + + if exports[from] then + table.insert(from_params, "shape=box") + elseif #label_inls[from] == 1 then + -- Indegree 1, this is either an exit, a decision node, or just a waypoint + if #label_outls[from] == 0 then + -- Exit + table.insert(from_params, "shape=octagon") + elseif #label_outls[from] == 1 then + -- Waypoint + table.insert(from_params, "shape=oval") + else + -- Decision + table.insert(from_params, "shape=trapezium") + end + else + if #label_outls[from] == 0 then + -- Exit + table.insert(from_params, "shape=octagon") + elseif #label_outls[from] == 1 then + table.insert(from_params, "shape=invtrapezium") + else + table.insert(from_params, "shape=hexagon") + end + end + + table.insert(from_params, + ({ ["any"] = "fontname=\"Times\"" + , ["deferred"] = "fontname=\"Times-Bold\"" + , ["enabled"] = "fontname=\"Times-Italic\"" + })[label_irq_assume[from]]) + + if from:match("^%.?L?switch") + or from == "__Z26compartment_switcher_entryz" then + table.insert(from_params, "style=filled") + table.insert(from_params, "fillcolor=cyan") + elseif from:match("^%.?L?exception") then + table.insert(from_params, "style=filled") + table.insert(from_params, "fillcolor=red") + elseif from:match("^%.?L?handle") then + table.insert(from_params, "style=filled") + table.insert(from_params, "fillcolor=orange") + end + + print("", ("%q [%s];"):format(from, table.concat(from_params,","))) + + for to, style in pairs(label_outs[from]) do + print("", ("%q"):format(from), + ("-> %q [%s];"):format(to, table.concat(style,","))) + end + print("") +end + +print("}")
diff --git a/sdk/boards/sonata-0.2.json b/sdk/boards/sonata-0.2.json index 308904a..9747b93 100644 --- a/sdk/boards/sonata-0.2.json +++ b/sdk/boards/sonata-0.2.json
@@ -76,6 +76,7 @@ "ipconfigDRIVER_INCLUDED_TX_IP_CHECKSUM=1" ], "driver_includes" : [ + "../include/platform/sunburst/v0.2", "../include/platform/sunburst", "../include/platform/generic-riscv" ],
diff --git a/sdk/boards/sonata-prerelease.json b/sdk/boards/sonata-prerelease.json index d82e122..01b4058 100644 --- a/sdk/boards/sonata-prerelease.json +++ b/sdk/boards/sonata-prerelease.json
@@ -4,9 +4,21 @@ "start" : 0x30000000, "end" : 0x30004000 }, - "gpio" : { - "start" : 0x80000000, - "end" : 0x80000020 + "pwm": { + "start" : 0x80001000, + "length": 0x00001000 + }, + "rgbled" : { + "start" : 0x80009000, + "end" : 0x80009020 + }, + "revoker": { + "start" : 0x8000A000, + "length": 0x00001000 + }, + "adc": { + "start" : 0x8000B000, + "length": 0x00001000 }, "clint": { "start" : 0x80040000, @@ -24,53 +36,13 @@ "start" : 0x80102000, "end" : 0x80102034 }, - "uart3": { - "start" : 0x80103000, - "end" : 0x80103034 - }, - "uart4": { - "start" : 0x80104000, - "end" : 0x80104034 - }, - "i2c0": { - "start" : 0x80200000, - "end" : 0x80200080 - }, - "i2c1": { - "start" : 0x80201000, - "end" : 0x80201080 - }, - "spi0": { - "start" : 0x80300000, - "end" : 0x80301000 - }, - "spi1": { - "start" : 0x80301000, - "end" : 0x80302000 - }, - "spi2": { - "start" : 0x80302000, - "end" : 0x80303000 - }, - "rgbled" : { - "start" : 0x80009000, - "end" : 0x80009020 - }, - "revoker": { - "start": 0x8000A000, - "length": 0x1000 + "usbdev": { + "start" : 0x80400000, + "end" : 0x80401000 }, "plic": { "start" : 0x88000000, "end" : 0x88400000 - }, - "pwm": { - "start" : 0x80001000, - "length": 0x00001000 - }, - "adc": { - "start" : 0x8000B000, - "length": 0x00001000 } }, "instruction_memory": { @@ -94,7 +66,7 @@ "../include/platform/ibex", "../include/platform/generic-riscv" ], - "timer_hz" : 30000000, + "timer_hz" : 40000000, "tickrate_hz" : 100, "revoker" : "hardware", "stack_high_water_mark" : true, @@ -102,62 +74,69 @@ "simulation": false, "interrupts": [ { - "name": "Uart0TxWatermark", + "name": "RevokerInterrupt", "number": 1, - "priority": 3, - "edge_triggered": true - }, - { - "name": "Uart0RxWatermark", - "number": 2, - "priority": 3, - "edge_triggered": true - }, - { - "name": "Uart0TxEmpty", - "number": 3, - "priority": 3, - "edge_triggered": true - }, - { - "name": "Uart0RxOverflow", - "number": 4, - "priority": 3, - "edge_triggered": true - }, - { - "name": "Uart1TxWatermark", - "number": 9, - "priority": 3, - "edge_triggered": true - }, - { - "name": "Uart1RxWatermark", - "number": 10, - "priority": 3, - "edge_triggered": true - }, - { - "name": "Uart1TxEmpty", - "number": 11, - "priority": 3, - "edge_triggered": true - }, - { - "name": "Uart1RxOverflow", - "number": 12, - "priority": 3, - "edge_triggered": true + "priority": 2 }, { "name": "EthernetInterrupt", - "number": 47, + "number": 2, "priority": 3 }, { - "name": "RevokerInterrupt", - "number": 72, - "priority": 2 + "name": "UsbDevInterrupt", + "number": 3, + "priority": 3 + }, + { + "name": "Uart0Interrupt", + "number": 8, + "priority": 3 + }, + { + "name": "Uart1Interrupt", + "number": 9, + "priority": 3 + }, + { + "name": "Uart2Interrupt", + "number": 10, + "priority": 3 + }, + { + "name": "I2c0Interrupt", + "number": 16, + "priority": 3 + }, + { + "name": "I2c1Interrupt", + "number": 17, + "priority": 3 + }, + { + "name": "SpiLcdInterrupt", + "number": 24, + "priority": 3 + }, + { + "name": "SpiEthmacInterrupt", + "number": 25, + "priority": 3 + }, + { + "name": "Spi0Interrupt", + "number": 26, + "priority": 3 + }, + { + "name": "Spi1Interrupt", + "number": 27, + "priority": 3 + }, + { + "name": "Spi2Interrupt", + "number": 28, + "priority": 3 } ] }
diff --git a/sdk/core/allocator/alloc.h b/sdk/core/allocator/alloc.h index 8ab0c09..69f2726 100644 --- a/sdk/core/allocator/alloc.h +++ b/sdk/core/allocator/alloc.h
@@ -683,6 +683,8 @@ // the minimum size of a chunk (excluding the header) constexpr size_t MinRequest = MinChunkSize - sizeof(MChunkHeader); +static_assert(MinChunkSize == CHERIOTHeapMinChunkSize); + // true if cap address a has acceptable alignment static inline bool is_aligned(CHERI::Capability<void> a) {
diff --git a/sdk/core/loader/boot.cc b/sdk/core/loader/boot.cc index e41038a..bc67535 100644 --- a/sdk/core/loader/boot.cc +++ b/sdk/core/loader/boot.cc
@@ -460,7 +460,7 @@ * The sealing key for the switcher, used to seal all jump targets in the * import tables. */ - Capability<void> switcherKey; + Capability<void> exportSealingKey; /** * The sealing key for sealing trusted stacks. @@ -568,7 +568,7 @@ "Functions exported from compartments must have " "an explicit interrupt posture"); return build(compartment.exportTable, entry.address) - .seal(switcherKey); + .seal(exportSealingKey); }; // If the low bit is 1, then this is either an MMIO region or direct @@ -596,9 +596,9 @@ { if (contains<ExportEntry>(lib.exportTable, possibleLibcall)) { - // TODO: Library export tables are not used after the - // loader has run, we could move them to the end of the - // image and make that space available for the heap. + // Library export tables are not used after the loader has + // run; our linker script places them to the end of the + // image, which we make available for the heap. return createLibCall(build_pcc(lib)); } } @@ -850,6 +850,8 @@ * than by fiat of initial construction. The switcher will detect the * trusted stack underflow and will signal the scheduler that the thread * has exited and should not be brought back on core. + * + * See core/switcher/entry.S:/^switcher_after_compartment_call. */ auto threadInitialReturn = build<void, Root::Type::Execute, SwitcherPccPermissions>( @@ -1250,24 +1252,46 @@ }; // Set up the sealing keys for the privileged components. - switcherKey = - setSealingKey(imgHdr.switcher, Sentry, SealedTrustedStacks - Sentry + 1); - // We need only the rights to seal things with the switcher's data sealing - // types, so drop all others and store those two types separately. - trustedStackKey = switcherKey; - trustedStackKey.address() = SealedTrustedStacks; - trustedStackKey.bounds() = 1; - switcherKey.address() = SealedImportTableEntries; - switcherKey.bounds() = 1; - setSealingKey(imgHdr.allocator(), Allocator); + exportSealingKey = CHERI::Capability{ + build<void, + Root::Type::Seal, + PermissionSet{Permission::Global, Permission::Seal}>( + SealedImportTableEntries, 1)}; + + // We and the switcher both seal (and the switcher unseals) trusted stacks + trustedStackKey = setSealingKey(imgHdr.switcher, SealedTrustedStacks, 1, 0); + // The switcher unseals import entries built with exportSealingKey above + setSealingKey(imgHdr.switcher, + SealedImportTableEntries, + 1, + sizeof(void *), + PermissionSet{Permission::Global, Permission::Unseal}); + + /* + * The token library unseals both static and dynamic objects, sometimes + * either and sometimes with static knowledge of which is expected. To + * avoid `li; csetaddr; csetbounds` sequences, we give it a separate cap + * for each case. + */ setSealingKey(imgHdr.token_library(), Allocator, 2, // Allocator and StaticToken 0, PermissionSet{Permission::Global, Permission::Unseal}); + setSealingKey(imgHdr.token_library(), + StaticToken, + 1, + sizeof(void *), + PermissionSet{Permission::Global, Permission::Unseal}); + setSealingKey(imgHdr.token_library(), + Allocator, + 1, + 2 * sizeof(void *), + PermissionSet{Permission::Global, Permission::Unseal}); + constexpr size_t DynamicSealingLength = std::numeric_limits<ptraddr_t>::max() - FirstDynamicSoftware + 1; - + setSealingKey(imgHdr.allocator(), Allocator); setSealingKey(imgHdr.allocator(), FirstDynamicSoftware, DynamicSealingLength,
diff --git a/sdk/core/loader/types.h b/sdk/core/loader/types.h index 7a29847..3b96b39 100644 --- a/sdk/core/loader/types.h +++ b/sdk/core/loader/types.h
@@ -365,6 +365,7 @@ /** * The PCC-relative location of the cross-compartment call return * path, used to build the initial return addresses for threads. + * That is, "switcher_after_compartment_call". */ uint16_t crossCallReturnEntry; @@ -1097,7 +1098,7 @@ /** * Flags. The low three bits indicate the number of registers that - * should be cleared in the compartment switcher. The next two bits + * should be passed in the compartment switcher. The next two bits * indicate the interrupt status. The remaining three are currently * unused. */
diff --git a/sdk/core/switcher/entry.S b/sdk/core/switcher/entry.S index 387a2d1..b139892 100644 --- a/sdk/core/switcher/entry.S +++ b/sdk/core/switcher/entry.S
@@ -45,6 +45,49 @@ * 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) + * - "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. + * */ switcher_code_start: @@ -54,24 +97,28 @@ .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 + .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 + .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 + .section .text, "ax", @progbits .globl switcher_scheduler_entry_csp .p2align 2 switcher_scheduler_entry_csp: @@ -121,32 +168,11 @@ .endm /** - * Verify the compartment stack is valid, with the expected permissions and - * unsealed. - * This macro assumes t2 and tp are available to use. - */ -.macro check_compartment_stack_integrity reg - // Check that the caller's CSP is a tagged, unsealed capability (with at - // least load permission - we'll check the other permissions properly - // later) by loading a byte. If this doesn't work, we'll fall off this - // path into the exception handler and force unwind. - clb t2, 0(\reg) - // make sure the caller's CSP has the expected permissions - cgetperm t2, \reg - li tp, COMPARTMENT_STACK_PERMISSIONS - bne tp, t2, .Lforce_unwind - // Check that the top and base are 16-byte aligned - cgetbase t2, csp - or t2, t2, sp - andi t2, t2, 0xf - bnez t2, .Lforce_unwind -.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. + * 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 @@ -169,9 +195,12 @@ .endm /** - * Clear the hazard pointers associated with this thread. 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. + * 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) @@ -185,99 +214,293 @@ .type __Z26compartment_switcher_entryz,@function __Z26compartment_switcher_entryz: /* - * Spill caller-save registers carefully. If we find ourselves unable to do - * so, we'll return an error to the caller (via the exception path; see - * .Lhandle_error_in_switcher). The error handling path assumes that the - * first spill is to the lowest address and guaranteed to trap if any would. + * FROM: cross-call + * FROM: malice + * IRQ ASSUME: deferred (exposed as IRQ-deferring sentry; see the 'export' + * macro at the end of this file) + * 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. + */ + /* + * 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. */ cincoffset ct2, csp, -SPILL_SLOT_SIZE -.Lswitcher_entry_first_spill: +.Lswitch_entry_first_spill: + /* + * FROM: above + * ITO: .Lswitch_just_return (via .Lhandle_error_in_switcher) + */ csc cs0, SPILL_SLOT_cs0(ct2) csc cs1, SPILL_SLOT_cs1(ct2) csc cgp, SPILL_SLOT_cgp(ct2) csc cra, SPILL_SLOT_pcc(ct2) cmove csp, ct2 - // before we access any privileged state, we can verify the - // compartment's csp is valid. If not, force unwind. - // Note that this check is purely to protect the callee, not the switcher - // itself. - check_compartment_stack_integrity csp - // The caller should back up all callee saved registers. + /* + * Atlas update: + * ra, gp, s0, s1: dead (presently, redundant caller values) + * t2: dead (presently, a copy of csp) + */ + + /* + * 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 csp-authorized instruction in the + * switcher 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 -#ifndef NDEBUG - // XXX: This line is useless, only for mtdc to show up in debugging. - cmove ct2, ct2 -#endif - clear_hazard_slots ct2, ctp + // 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) - // make sure the trusted stack is still in bounds +//.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 t2, ct2 - bgeu tp, t2, .Lout_of_trusted_stack - // we are past the stacks checks. Reload ct2; tp is still as it was - cspecialr ct2, mtdc - // ctp points to the current available trusted stack frame. + 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) - // We have just entered this call, so no faults triggered during this call - // yet. + /* + * 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) - // For now, store a null export entry so that we don't ever try to pass - // switcher state to an error handler. + /* + * 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 - // Update the frame offset. - // Any fault before this point (wrong target cap, unaligned stack, etc.) is - // seen as a fault in the caller. From this point after writing the new - // tstack offset, any fault is seen as a callee fault. With a null export - // table entry on the trusted stack, a fault here will cause a forced - // unwind until we set the correct one. csh s1, TrustedStack_offset_frameoffset(ct2) - // Chop off the stack. + + /* + * 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 - bge gp, sp, .Lafter_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. +//.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 t2, s0, gp -.Lafter_zero: + 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, compartment_switcher_sealing_key - li gp, SEAL_TYPE_SealedImportTableEntries - csetaddr cs0, cs0, gp - // The target capability is in ct1. Unseal, check tag and load the entry point offset. + // Fetch the sealing key, using gp as a scratch scalar + 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 - // Load the entry point offset. If cunseal failed then this will fault and - // we will force unwind. + /* + * 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. + */ + /* + * 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) - // 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. Nothing between this point and transition to the callee - // should fault. + // 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) - // Load the minimum stack size required by the callee. +//.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) - // The stack size is in 8-byte units, so multiply by 8. + // Atlas update: tp: minimum stack size, in units of 8 bytes. slli tp, tp, 3 - // 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. + // 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. * @@ -286,11 +509,17 @@ * 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 - bgtu tp, t2, .Lstack_too_small + // LIVE OUT: mtdc + bgtu tp, t2, .Lswitch_stack_too_small - // Reserve space for unwind state and so on. + /* + * 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 @@ -299,98 +528,210 @@ // 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 - // Load the target CGP + /* + * 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) - // Load the target PCC and point to the function. + // Atlas update: gp: target compartment CGP clc cra, ExportTable_offset_pcc(ct1) cincoffset cra, cra, s0 - // Get the number of registers to zero in t2 - andi t2, tp, 0x7 - // Get the interrupt-disable bit in t1 - andi t1, tp, 0x10 + // Atlas update: ra: target function entrypoint (pcc base + offset from s0) + // Zero any unused argument registers - // The low 3 bits of the flags field contain the number of arguments to - // pass. We create a small sled that zeroes them and jump into the middle - // of it at an offset defined by the number of registers that the export - // entry told us to pass. -.Lload_zero_arguments_start: - auipcc cs0, %cheriot_compartment_hi(.Lzero_arguments_start) - cincoffset cs0, cs0, %cheriot_compartment_lo_i(.Lload_zero_arguments_start) - // Change from the number of registers to pass into the number of 2-byte - // instructions to skip. + /* + * 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 registers that we should be - // passing. + // Offset the jump target by the number of instructions to skip cincoffset cs0, cs0, t2 // Jump into the sled. cjr cs0 -.Lzero_arguments_start: +.Lswitch_zero_arguments_start: + // IFROM: above zeroRegisters a0, a1, a2, a3, a4, a5, t0 - // Enable interrupts of the interrupt-disable bit is not set in flags - bnez t1, .Lskip_interrupt_disable + + /* + * Enable interrupts if the interrupt-disable bit is not set in flags. See + * loader/types.h's InterruptStatus and ExportEntry::InterruptStatusMask + */ + andi t1, tp, 0x10 + bnez t1, .Lswitch_skip_interrupt_enable csrsi mstatus, 0x8 -.Lskip_interrupt_disable: - // Registers passed to the callee are: - // cra (c1), csp (c2), and cgp (c3) are passed unconditionally. - // ca0-ca5 (c10-c15) and ct0 (c5) are either passed as arguments or cleared - // above. This should add up to 10 registers, with the remaining 5 being - // cleared now: +.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 + */ + /* + * 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_skip_compartment_call -switcher_skip_compartment_call: - // If we are doing a forced unwind of the trusted stack then we do almost - // exactly the same as a normal unwind. We will jump here from the - // exception path (.Lforce_unwind) - + .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 argument and temporary registers. * - * The below should not fault before returning back to the caller. If a - * fault occurs there must be a serious bug elsewhere. + * 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. + */ + /* + * 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 + clear_hazard_slots ctp, ct2 - // make sure there is a frame left in the trusted stack + + /* + * 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 tp, TrustedStack_offset_frames - // Move to the previous trusted stack frame. + 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 - // 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, so would exit the thread, but we - // should instead gracefully exit the thread. - bgeu tp, t2, .Lcommon_defer_irqs_and_thread_exit - cspecialr ctp, mtdc + /* + * 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 - // Restore the stack pointer. All other spilled values are spilled there. + // Atlas update: t1: pointer to the TrustedStackFrame to bring on core + + /* + * 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) - // Update the current frame offset. + /* + * 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 in `check_compartment_stack_integrity` make it impossible for - // this to fault, but if we do fault here 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. + /* + * 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 - // we set it to stack top when we pushed to trusted stack frame + /* + * 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 @@ -402,76 +743,167 @@ csrw CSR_MSHWM, sp #endif - // Zero all registers apart from RA, GP, SP and return args. - // cra, csp and cgp needed for the compartment - // cs0 saved and restored on trusted stack - // cs1 saved and restored on trusted stack - // ca0, used for first return value - // ca1, used for second return value + // Zero all registers not holding state intended for caller; see atlas below +//.Lswitch_callee_dead_zeros: zeroAllRegistersExcept ra, sp, gp, s0, s1, a0, a1 -.Ljust_return: +.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. -.Lstack_too_small: + /* + * 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 - j switcher_skip_compartment_call + // 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) below + li a0, -ENOTENOUGHTRUSTEDSTACK + // Zero everything else + zeroAllRegistersExcept ra, sp, gp, s0, s1, a0 + cret + .size compartment_switcher_entry, . - compartment_switcher_entry - // the entry point of all exceptions and interrupts - // For now, the entire routine is run with interrupts disabled. .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: - // We do not trust the interruptee's context. We cannot use its stack in any way. - // The save reg frame we can use is fetched from the tStack. - // 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). + /* + * 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 -#ifndef NDEBUG - // XXX: This move is useless, but just for debugging in the simulator. - cmove csp, csp -#endif - // If we read out zero, we've reentered the exception and are about to - // trap. Make sure that we end up in an architectural trap loop: clobber - // mtcc, so that trapping attempts to vector to an untagged PCC, thereby - // causing another (i.e., a third) trap in spillRegisters, below. - // - // While that's a good start, it does not guarantee that we end up in a - // trap loop: the reentry will probably have put something non-zero into - // mtdc, so we wouldn't hit this, and wouldn't loop, when we take that - // third trap. (Exactly what we'd do instead is hard to say; we'd try - // spilling registers to an attacker-controlled pointer, at the very - // least.) Therefore, clobber mtcc (!) to ensure that the certainly - // upcoming third trap puts us in an architectural trap loop. This is - // slightly preferable to clearing mtdc, which would also ensure that we - // looped, because the architectural loop is tighter and involves no - // program text, making it easier for microarchitecture to detect. - bnez sp, .Lexception_entry_still_alive - cspecialw mtcc, csp -.Lexception_entry_still_alive: + /* + * 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 - // csp now points to the save reg frame that we can use. - // The guest csp (c2) is now in mtdc. Will be spilled later, but we - // spill all the other 14 registers now. + /* + * The guest sp/csp (x2/c2) is now in mtdc. Will be spilled later, but we + * spill all the other 14 registers now. + */ spillRegisters cra, cgp, ctp, ct0, ct1, ct2, cs0, cs1, ca0, ca1, ca2, ca3, ca4, ca5 - // If a thread has exited then it will set a fake value in the mcause so - // that the scheduler knows not to try to resume it. -.Lthread_exit: - // mtdc got swapped with the thread's csp, store it and clobber mtdc with - // 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. + /* + * 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 @@ -487,29 +919,42 @@ csrr t1, mcause csw t1, TrustedStack_offset_mcause(csp) - // If we hit one of the exception conditions that we should let - // compartments handle then deliver it to the compartment. - // CHERI exception code. + /* + * 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 - // Misaligned instruction, instruction access, illegal instruction, - // breakpoint, misaligned load, load fault, misaligned store, and store - // access faults are in the range 0-7 + /* + * 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 ca5, compartment_switcher_sealing_key - li gp, 10 - csetaddr ca5, ca5, gp - cseal ca0, csp, ca5 // sealed trusted stack + /* + * 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 @@ -517,57 +962,114 @@ 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. - // cra, csp and cgp needed for the scheduler compartment - // ca0, used for the sealed trusted stack argument - // ca1, used for mcause - // ca2, used for mepc - // ca3, used for mtval zeroAllRegistersExcept ra, sp, gp, a0, a1, a2, a3 // Call the scheduler. This returns the new thread in ca0. cjalr cra - // The scheduler may change interrupt posture or may trap, 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 install_context. - // Switch onto the new thread's trusted stack - LoadCapPCC ct0, compartment_switcher_sealing_key - li gp, SEAL_TYPE_SealedTrustedStacks - csetaddr ct0, ct0, gp - cunseal csp, ca0, ct0 +.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, using gp as a scratch scalar + LoadCapPCC csp, .Lsealing_key_trusted_stacks + cunseal csp, ca0, csp + // Atlas update: sp: unsealed target thread trusted stack pointer + 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. + /* + * 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. + /* + * 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. + /* + * 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) - bne t0, t1, .Linstall_context + // 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 -// Install context expects csp and mtdc to point to the trusted stack and for -// ct2 to be the pcc to jump to. All other registers are in unspecified states -// and will be overwritten when we install the context. -.Linstall_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 @@ -577,198 +1079,347 @@ csrw CSR_MSHWMB, ra #endif cspecialw mepcc, ct2 - // csp (c2) will be loaded last and will overwrite the trusted stack pointer - // with the thread's stack pointer. + + /* + * 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. + */ reloadRegisters 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. -.Lforce_unwind: +/** + * 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_skip_compartment_call + j switcher_after_compartment_call - -// If we have run out of trusted stack, then just restore the caller's state -// and return an error value. -.Lout_of_trusted_stack: - // Restore the spilled values - 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 return registers - li a0, -ENOTENOUGHTRUSTEDSTACK - li a1, 0 - // Zero everything else - zeroAllRegistersExcept ra, sp, gp, s0, s1, a0, a1 - cret - -// 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. -// -// On entry to this block, csp contains the trusted stack pointer, all other -// registers are undefined. -// -// The handler will have this type signature: -// enum ErrorRecoveryBehaviour compartment_error_handler(struct ErrorState *frame, -// size_t mcause, -// size_t mtval); +/** + * 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: - // We're now out of the exception path, so make sure that mtdc contains - // the trusted stack pointer. + /* + * 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 - // Store an error value in return registers, which will be passed to the - // caller on unwind. They are currently undefined, if we leave this path - // for a forced unwind then we will return whatever is in ca0 and ca1 to - // the caller so must ensure that we don't leak anything. - li a0, -1 - li a1, 0 + /* + * Atlas update: + * mtdc: pointer to TrustedStack + * sp: (still) pointer to TrustedStack + */ - // 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. +//.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) - // See if we can find a handler: + // 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 - beq tp, t1, .Lset_mcause_and_exit_thread - addi tp, tp, -TrustedStackFrame_size + // LIVE OUT: sp + beq tp, t1, .Lcommon_thread_exit - // ctp points to the current available trusted stack frame. + 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) - // 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). +//.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 - // If there isn't enough space on the stack, see if there's a stackless - // handler. - beqz t1, .Ltry_stackless_handler + /* + * 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) - // 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. + // 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) - // A value of 0xffff indicates no error handler - // If we found one, use it, otherwise fall through and try to find a - // stackless handler. - li s1, 0xffff - bne s0, s1, .Lhandler_found -.Ltry_stackless_handler: +//.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. + */ + li s1, 0xffff + // 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, t0 + * Atlas: + * sp: pointer to TrustedStack + * tp: pointer to current TrustedStackFrame + * t0: interrupted thread's stack pointer + */ + 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. + /* + * 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. + /* + * 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. + */ li s1, 0xffff - beq s0, s1, .Lforce_unwind + // 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. - // Get the previous trusted stack frame - - // Load the caller's csp + /* + * 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 stack frame, then the csp field is the value on - // entry. If it's any other frame then we need to go to the previous one + /* + * 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, .Lrecovered_stack + beq s1, tp, .Lhandle_stack_recovered - // The address of the stack pointer will point to the bottom of the - // caller's save area, so we set the bounds to be the base up to the - // current address. +//.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 csp to be in ct0 + // The code that installs the context expects the target stack to be in ct0 csetboundsexact ct0, ct0, a1 -.Lrecovered_stack: +.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 -.Lhandler_found: +.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) - // If we are in a double fault, unwind now. The low bit should be 1 while - // we are handling a fault. + /* + * 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 - beqz ra, .Lforce_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. + // 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 - bgtu s1, ra, .Lforce_unwind + // 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 double-fault and force unwind. + /* + * 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. - beqz a0, .Lset_up_stack_handler + /* + * 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 - j .Linvoke_error_handler + // Atlas: sp: taget compartment invocation stack pointer + j .Lhandle_error_handler_invoke -.Lset_up_stack_handler: - // Set up the on-stack context for the callee - clc cs1, 0(csp) +.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, 0(ct0) - // Source for context copy. + 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 - // Destination for context copy cincoffset ca3, ct0, TrustedStack_offset_cra - copyContext ca3, ca2, cs1, a4 + copyContext /* dst = */ ca3, /* src = */ ca2, /* scratch = */ cs1, /* counter = */ a4 // Set up the arguments for the call cmove ca0, ct0 @@ -776,18 +1427,80 @@ csrr a2, mtval cmove csp, ca0 -.Linvoke_error_handler: - // Enable interrupts before invoking the handler +.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 registers except: - // cra is set by cjalr. csp and cgp are needed for the called compartment. - // ca0, used for the register state - // ca1, used for mcause - // ca2, used for mtval + // Clear all other registers and invoke the handler zeroAllRegistersExcept ra, sp, gp, a0, a1, a2 - // Call the handler. 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 @@ -797,69 +1510,116 @@ * actually matters and let most of this code run with IRQs enabled. */ csrci mstatus, 0x8 +//.Lhandle_error_handler_return_irqs: + // IRQ ASSUME: deferred - // Move the return value to a register that will be cleared in a forced - // unwind and zero the return registers. - move s0, a0 - // Store an error value in return registers, which will be passed to the - // caller on unwind. - li a0, -1 - li a1, 0 - // Return values are 0 for install context, 1 for forced unwind. Anything - // that is not either of these is invalid and so we should do a forced - // unwind anyway. - bne s0, zero, .Lforce_unwind + /* + * 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 - // 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. +//.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. + */ - // Load the trusted stack pointer to ct1 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. + /* + * 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 - // ctp points to the current available trusted stack frame. + // Atlas update: tp: pointer to the current available trusted stack frame. cincoffset ctp, ct1, tp - // ct0 now contains the export table for the callee + /* + * 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 - // ct0 now contains the PCC for the returning compartment. clc ct0, ExportTable_offset_pcc(ct0) - // This is the *untagged* destination pcc. Install its address into the - // real one - clc cra, 0(csp) + // 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 - // Now copy everything else from the stack into the saved context - // Source + // 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 - // Destination cincoffset ca3, ct1, TrustedStack_offset_cra - copyContext ca3, ca2, cs1, 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. + 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. + /* + * 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 - j .Linstall_context + // 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 @@ -869,27 +1629,54 @@ j .Lhandle_error .Lcommon_defer_irqs_and_thread_exit: + /* + * FROM: switcher_after_compartment_call + * IRQ REQUIRE: any + */ csrci mstatus, 0x8 - // Fall-through, now that IRQs are off +//.Lcommon_deferred_irqs_and_thread_exit: + // IRQ ASSUME: deferred - // Value 24 is reserved for custom use. -.Lset_mcause_and_exit_thread: +/** + * 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 - // The thread exit code expects the trusted stack pointer to be in csp and - // the stack pointer to be in mtdc. After thread exit, we don't need the - // stack pointer so just put zero there. + /* + * 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 - j .Lthread_exit + // 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: - auipcc ctp, %cheriot_compartment_hi(.Lswitcher_entry_first_spill) + /* + * FROM: .Lhandle_error_switcher_pcc + * IRQ REQUIRE: deferred (TrustedStack spill frame is precious) + * LIVE IN: mtdc + * + * Atlas: + * mtdc: pointer to TrustedStack + */ + auipcc ctp, %cheriot_compartment_hi(.Lswitch_entry_first_spill) cincoffset ctp, ctp, %cheriot_compartment_lo_i(.Lhandle_error_in_switcher) - bne t1, tp, .Lforce_unwind + bne t1, tp, .Lcommon_force_unwind li a0, -ENOTENOUGHSTACK li a1, 0 @@ -898,15 +1685,52 @@ * 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 - * .Linstall_context. + * .Lcommon_context_install. */ -.Linstall_return_context: - auipcc ct2, %cheriot_compartment_hi(.Ljust_return) - cincoffset ct2, ct2, %cheriot_compartment_lo_i(.Linstall_return_context) +.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) - j .Linstall_context + // 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. + */ + cmove csp, cnull + 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 @@ -934,43 +1758,82 @@ .p2align 2 .type __Z23trusted_stack_has_spacei,@function __Z23trusted_stack_has_spacei: + /* + * LIVE IN: mtdc, a0 + * + * Atlas: + * mtdc: pointer to TrustedStack (or nullptr if from buggy scheduler) + * a0: requested number of trusted stack frames + */ li a2, TrustedStackFrame_size mul a2, a0, a2 - // Load the trusted stack into a register that we will clobber on the way - // out. + // 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 .section .text, "ax", @progbits .p2align 2 .type __Z22switcher_recover_stackv,@function __Z22switcher_recover_stackv: - // Load the trusted stack pointer into a register that we will clobber in - // two instructions. + /* + * LIVE IN: mtdc + * + * Atlas: + * mtdc: pointer to TrustedStack (or nullptr if buggy scheduler) + */ + /* + * 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) - // If this is the first frame, then the recovered stack will be the stack - // on entry. If this is not the first frame then then we need to find the - // saved CSP from the caller and reset the bounds. The address of the - // saved CSP will be the value after the switcher spilled registers and so - // will be the top of the callee's stack. + // 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 beq a1, a2, 0f - // Find the previous frame's csp and reset the bounds + /* + * 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 0: + // LIVE OUT: mtdc, a0 cret .section .text, "ax", @progbits @@ -979,21 +1842,22 @@ __Z25switcher_interrupt_threadPv: // Load the unsealing key into a register that we will clobber two // instructions later. - LoadCapPCC ca1, compartment_switcher_sealing_key - li a2, SEAL_TYPE_SealedTrustedStacks - csetaddr ca1, ca1, a2 - // The target capability is in ct1. Unseal, check tag and load the entry point offset. + LoadCapPCC ca1, .Lsealing_key_trusted_stacks + /* + * The target capability is in ca0. Unseal, check tag and load the entry + * point offset. + */ cunseal ca1, ca0, ca1 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, .Lreturn + 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, .Lreturn + beq a2, a1, .Lswitcher_interrupt_thread_return // ca1 now contains the unsealed capability for the target thread. We // allow the target thread to be interrupted if (and only if) the caller is @@ -1021,7 +1885,7 @@ li a0, 42 // If the two export table entries differ, return. - bne a2, a3, .Lreturn + bne a2, a3, .Lswitcher_interrupt_thread_return // After this point, we no longer care about the values in a0, a2, and a3. // Mark the thread as interrupted. @@ -1030,7 +1894,7 @@ csw a2, TrustedStack_offset_mcause(ca1) // Return success li a0, 1 -.Lreturn: +.Lswitcher_interrupt_thread_return: zeroRegisters a1, a2, a3 cret @@ -1038,9 +1902,7 @@ .p2align 2 .type __Z23switcher_current_threadv,@function __Z23switcher_current_threadv: - LoadCapPCC ca0, compartment_switcher_sealing_key - li a1, SEAL_TYPE_SealedTrustedStacks - csetaddr ca0, ca0, a1 + LoadCapPCC ca0, .Lsealing_key_trusted_stacks cspecialr ca1, mtdc cseal ca0, ca1, ca0 li a1, 0 @@ -1067,9 +1929,9 @@ // 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. - beqz a1, .Lend + beqz a1, 0f clh a0, TrustedStack_offset_threadID(ca0) -.Lend: +0: cret
diff --git a/sdk/core/switcher/tstack.h b/sdk/core/switcher/tstack.h index dc8338b..c48f4a0 100644 --- a/sdk/core/switcher/tstack.h +++ b/sdk/core/switcher/tstack.h
@@ -10,7 +10,13 @@ struct TrustedStackFrame { - /// caller's stack + /** + * Caller's stack pointer, at time of cross-compartment entry, pointing at + * switcher's register spills (.Lswitch_entry_first_spill and following). + * + * The address of this pointer is the (upper) limit of the stack capability + * given to the callee. + */ void *csp; /** * The callee's export table. This is stored here so that we can find the @@ -28,6 +34,13 @@ uint16_t errorHandlerCount; }; +/** + * Each thread in the system has, and is identified by, its Trusted Stack. + * These structures hold an activation frame (a TrustedStackFrame) for each + * active cross-compartment call as well as a "spill" register context, used + * mostly for preemption (but also as staging space when a thread is adopting a + * new context as part of exception handlng). + */ template<size_t NFrames> struct TrustedStackGeneric {
diff --git a/sdk/core/token_library/token_unseal.S b/sdk/core/token_library/token_unseal.S index 1dccc04..a9dfd67 100644 --- a/sdk/core/token_library/token_unseal.S +++ b/sdk/core/token_library/token_unseal.S
@@ -2,15 +2,34 @@ #include <cheri-builtins.h> #include "../allocator/token.h" - .hidden __sealingkey - .type __sealingkey,@object - .section .sealing_key1,"aw",@progbits - .globl __sealingkey - .p2align 3 -__sealingkey: - .chericap 0 - .size __sealingkey, 8 +.include "assembly-helpers.s" + .hidden __sealingkey_either + .type __sealingkey_either,@object + .section .sealing_key1,"aw",@progbits + .globl __sealingkey_either + .p2align 3 +__sealingkey_either: + .chericap 0 + .size __sealingkey_either, 8 + + .hidden __sealingkey_static + .type __sealingkey_static,@object + .section .sealing_key2,"aw",@progbits + .globl __sealingkey_static + .p2align 3 +__sealingkey_static: + .chericap 0 + .size __sealingkey_static, 8 + + .hidden __sealingkey_dynamic + .type __sealingkey_dynamic,@object + .section .sealing_key3,"aw",@progbits + .globl __sealingkey_dynamic + .p2align 3 +__sealingkey_dynamic: + .chericap 0 + .size __sealingkey_dynamic, 8 .section .text,"ax",@progbits @@ -19,87 +38,74 @@ /** * The core of unsealing: * - * void *token_unseal_internal(struct SKeyStruct *, struct SObjStruct *, int); + * void *token_unseal_internal( + * struct SKeyStruct *, struct SObjStruct *, void *); */ - .Ltoken_unseal_internal: /* * Register allocation: * - * - ca0 holds a sealing key, either the user's or the real deal, and is - * replaced with the unsealed value or NULL + * - ca0 holds the user's sealing key, and is replaced with the unsealed + * value or NULL * * - ca1 holds the user's sealed object pointer * - * - a2 contains the expected sealing type. + * - ca2 holds the unsealing authority and is clobbered on failure + * explicitly and on success with a scalar (the sealed payload's length) * - * - t0 holds a copy of the user key's address field (authorized type) - * - * - t1 is used within each local computation and never holds secrets + * - a3 is used within each local computation and never holds secrets */ /* Verify key tag */ - cgettag t1, ca0 - beqz t1, .Lexit_failure + cgettag a3, ca0 + beqz a3, .Lexit_failure /* Verify key address == base and len > 0 */ - cgetbase t1, ca0 - bne a0, t1, .Lexit_failure // as-integer access to ca0 gives address - cgetlen t1, ca0 - beqz t1, .Lexit_failure + cgetbase a3, ca0 + bne a0, a3, .Lexit_failure // as-integer access to ca0 gives address + cgetlen a3, ca0 + beqz a3, .Lexit_failure /* Verify key has unseal permission */ - cgetperm t1, ca0 - andi t1, t1, CHERI_PERM_UNSEAL - beqz t1, .Lexit_failure - - /* Copy key type to scratch register */ - cgetaddr t0, ca0 - - /* - * Load unsealing root capability, to be clobbered by return value - * This faults only if something has gone very, very wrong, and exposes no - * secrets if so. - */ -.Lload_sealing_key: - auipcc ca0, %cheriot_compartment_hi(__sealingkey) - clc ca0, %cheriot_compartment_lo_i(.Lload_sealing_key)(ca0) - csetaddr ca0, ca0, a2 + cgetperm a3, ca0 + andi a3, a3, CHERI_PERM_UNSEAL + beqz a3, .Lexit_failure /* Unseal, clobbering authority */ - cunseal ca0, ca1, ca0 + cunseal ca2, ca1, ca2 /* Verify tag of unsealed form */ - cgettag t1, ca0 - beqz t1, .Lexit_failure + cgettag a3, ca2 + beqz a3, .Lexit_failure /* * Load software type tag. This will not trap, thanks to above tag check and * because IRQs are deferred (see our export entry below) */ - clw t1, TokenSObj_offset_type(ca0) + clw a3, TokenSObj_offset_type(ca2) /* Verify that the loaded value matches the address of the key. */ - bne t0, t1, .Lexit_failure + bne a0, a3, .Lexit_failure /* Subset bounds to ->data */ - // Get the top into t1 - cgettop t1, ca0 - // Move the address to the start of the data - cincoffset ca0, ca0, TokenSObj_offset_data + // Get the top into a3 + cgettop a3, ca2 + // Move the address to the start of the data, clobber the user's sealing key + cincoffset ca0, ca2, TokenSObj_offset_data // Subtract the address of the (to-be-returned-unsealed) data from the top to - // give the length. - sub t1, t1, a0 + // give the length, clobbering our unsealing key. + sub a2, a3, a0 // Set the new bounds, using an exact setting so that any errors in the // allocator's alignment turn into an untagged capability here. - csetboundsexact ca0, ca0, t1 + csetboundsexact ca0, ca0, a2 /* And that's an unwrap. */ cret .Lexit_failure: - /* Failure; clobber potential sensitive state in ca0 and return null */ - cmove ca0, cnull + /* Failure; clobber potential sensitive state in ca2 and return null */ + zeroOne a2 + zeroOne a0 cret /** @@ -113,7 +119,16 @@ .hidden _Z16token_obj_unsealP10SKeyStructP10SObjStruct .globl _Z16token_obj_unsealP10SKeyStructP10SObjStruct _Z16token_obj_unsealP10SKeyStructP10SObjStruct: - cgettype a2, ca1 + LoadCapPCC ca2, __sealingkey_either + + /* + * Backwards compatibility with CUnseal that requires address match. + * This can (and should) be removed once everyone's caught up with + * https://github.com/CHERIoT-Platform/cheriot-sail/pull/87 . + */ + cgettype a3, ca1 + csetaddr ca2, ca2, a3 + j .Ltoken_unseal_internal /** @@ -127,8 +142,8 @@ .hidden _Z23token_obj_unseal_staticP10SKeyStructP10SObjStruct .globl _Z23token_obj_unseal_staticP10SKeyStructP10SObjStruct _Z23token_obj_unseal_staticP10SKeyStructP10SObjStruct: - li a2, CheriSealTypeStaticToken - j .Ltoken_unseal_internal + LoadCapPCC ca2, __sealingkey_static + j .Ltoken_unseal_internal /** * An in-assembler implementation of @@ -138,11 +153,11 @@ * * The name has been manually mangled as per the C++ rules. */ - .hidden _Z16token_obj_unsealP10SKeyStructP10SObjStruct - .globl _Z16token_obj_unsealP10SKeyStructP10SObjStruct + .hidden _Z24token_obj_unseal_dynamicP10SKeyStructP10SObjStruct + .globl _Z24token_obj_unseal_dynamicP10SKeyStructP10SObjStruct _Z24token_obj_unseal_dynamicP10SKeyStructP10SObjStruct: - li a2, CheriSealTypeAllocator - j .Ltoken_unseal_internal + LoadCapPCC ca2, __sealingkey_dynamic + j .Ltoken_unseal_internal /* TODO: Eventually this goes away, when the assembler can generate it for us */ CHERIOT_EXPORT_LIBCALL \
diff --git a/sdk/firmware.ldscript.in b/sdk/firmware.ldscript.in index a9a3956..0730217 100644 --- a/sdk/firmware.ldscript.in +++ b/sdk/firmware.ldscript.in
@@ -163,8 +163,8 @@ # Compartment switcher end SHORT(.compartment_switcher_end - .compartment_switcher_start); # Cross-compartment call return path - SHORT(switcher_skip_compartment_call - .compartment_switcher_start); - # Compartment switcher sealing key + SHORT(switcher_after_compartment_call - .compartment_switcher_start); + # Compartment switcher sealing keys SHORT(compartment_switcher_sealing_key - .compartment_switcher_start); # Switcher's copy of the scheduler's PCC. SHORT(switcher_scheduler_entry_pcc - .compartment_switcher_start);
diff --git a/sdk/include/assembly-helpers.h b/sdk/include/assembly-helpers.h index 73b0a68..a747f09 100644 --- a/sdk/include/assembly-helpers.h +++ b/sdk/include/assembly-helpers.h
@@ -92,7 +92,7 @@ #else # define EXPORT_ASSEMBLY_NAME(name, value) # define EXPORT_ASSEMBLY_EXPRESSION(name, expression, value) -# define EXPORT_ASSEMBLY_OFFSET(structure, field, name) +# define EXPORT_ASSEMBLY_OFFSET(structure, field, value) # define EXPORT_ASSEMBLY_SIZE(structure, name, value) # define EXPORT_ASSEMBLY_OFFSET_NAMED(structure, field, value, name) #endif
diff --git a/sdk/include/platform/sunburst/platform-i2c.hh b/sdk/include/platform/sunburst/platform-i2c.hh index 2332a8a..ef93eee 100644 --- a/sdk/include/platform/sunburst/platform-i2c.hh +++ b/sdk/include/platform/sunburst/platform-i2c.hh
@@ -4,101 +4,10 @@ #include <stdint.h> /** - * The interrupts of the OpenTitan's I2C block. - * - * Documentation source can be found at: - * https://github.com/lowRISC/opentitan/blob/9ddf276c64e2974ed8e528e8b2feb00b977861de/hw/ip/i2c/doc/interfaces.md - */ -enum class OpenTitanI2cInterrupt -{ - /** - * A host mode interrupt. This is asserted whilst the Format FIFO level is - * below the low threshold. This is a level status interrupt. - */ - FormatThreshold, - /** - * A host mode interrupt. This is asserted whilst the Receive FIFO level is - * above the high threshold. This is a level status interrupt. - */ - ReceiveThreshold, - /** - * A target mode interrupt. This is asserted whilst the Aquired FIFO level - * is above the high threshold. This is a level status interrupt. - */ - AcquiredThreshold, - /** - * A host mode interrupt. This is raised if the Receive FIFO has overflowed. - */ - ReceiveOverflow, - /** - * A host mode interrupt. This is raised if there is no ACK in response to - * an address or data. - */ - Nak, - /** - * A host mode interrupt. This is raised if the SCL line drops early (not - * supported without clock synchronization). - */ - SclInterference, - /** - * A host mode interrupt. This is raised if the SDA line goes low when host - * is trying to assert high. - */ - SdaInterference, - /** - * A host mode interrupt. This is raised if target stretches the clock - * beyond the allowed timeout period. - */ - StretchTimeout, - /** - * A host mode interrupt. This is raised if the target does not assert a - * constant value of SDA during transmission. - */ - SdaUnstable, - /** - * A host and target mode interrupt. In host mode, raised if the host issues - * a repeated START or terminates the transaction by issuing STOP. In target - * mode, raised if the external host issues a STOP or repeated START. - */ - CommandComplete, - /** - * A target mode interrupt. This is raised if the target is stretching - * clocks for a read command. This is a level status interrupt. - */ - TransmitStretch, - /** - * A target mode interrupt. This is asserted whilst the Transmit FIFO level - * is below the low threshold. This is a level status interrupt. - */ - TransmitThreshold, - /** - * A target mode interrupt. This is raised if the target is stretching - * clocks due to full Aquired FIFO or zero count in targetAckControl.NBYTES - * (if enabled). This is a level status interrupt. - */ - AcquiredFull, - /** - * A target mode interrupt. This is raised if STOP is received without a - * preceding NACK during an external host read. - */ - UnexpectedStop, - /** - * A target mode interrupt. This is raised if the host stops sending the - * clock during an ongoing transaction. - */ - HostTimeout, -}; - -static constexpr uint32_t interrupt_bit(const OpenTitanI2cInterrupt Interrupt) -{ - return 1 << static_cast<uint32_t>(Interrupt); -}; - -/** * Driver for the OpenTitan's I2C block. * * Documentation source can be found at: - * https://github.com/lowRISC/opentitan/tree/9ddf276c64e2974ed8e528e8b2feb00b977861de/hw/ip/i2c + * https://github.com/lowRISC/opentitan/tree/4fe1b8dd1a09af9dbc242434481ae031955dfd85/hw/ip/i2c */ struct OpenTitanI2c { @@ -158,10 +67,119 @@ */ uint32_t targetNackCount; /** + * Controls for mid-transfer (N)ACK phase handling. + */ + uint32_t targetAckControl; + /// The data byte pending to be written to the Acquire (ACQ) FIFO. + uint32_t acquireFifoNextData; + /** * Timeout in Host-Mode for an unhandled NACK before hardware automatically * ends the transaction. */ - uint32_t targetAckControl; + uint32_t hostNackHandlerTimeout; + /// Latched events that explain why the controller halted. + uint32_t controllerEvents; + /** + * Latched events that can cause the target module to stretch the clock at + * the beginning of a read transfer. + */ + uint32_t targetEvents; + + /** + * The interrupts of the OpenTitan's I2C block. + * + * Documentation source can be found at: + * https://github.com/lowRISC/opentitan/blob/4fe1b8dd1a09af9dbc242434481ae031955dfd85/hw/ip/i2c/doc/interfaces.md + */ + enum class Interrupt + { + /** + * A host mode interrupt. This is asserted whilst the Format FIFO level + * is below the low threshold. This is a level status interrupt. + */ + FormatThreshold, + /** + * A host mode interrupt. This is asserted whilst the Receive FIFO level + * is above the high threshold. This is a level status interrupt. + */ + ReceiveThreshold, + /** + * A target mode interrupt. This is asserted whilst the Aquired FIFO + * level is above the high threshold. This is a level status interrupt. + */ + AcquiredThreshold, + /** + * A host mode interrupt. This is raised if the Receive FIFO has + * overflowed. + */ + ReceiveOverflow, + /** + * A host mode interrupt. This is raised if the controller FSM is + * halted, such as on an unexpected NACK or lost arbitration. Check the + * `controllerEvents` register for the reason. The interrupt will only + * be released when the bits in `controllerEvents` are cleared. + */ + ControllerHalt, + /** + * A host mode interrupt. This is raised if the SCL line drops early + * (not supported without clock synchronization). + */ + SclInterference, + /** + * A host mode interrupt. This is raised if the SDA line goes low when + * host is trying to assert high. + */ + SdaInterference, + /** + * A host mode interrupt. This is raised if target stretches the clock + * beyond the allowed timeout period. + */ + StretchTimeout, + /** + * A host mode interrupt. This is raised if the target does not assert a + * constant value of SDA during transmission. + */ + SdaUnstable, + /** + * A host and target mode interrupt. In host mode, raised if the host + * issues a repeated START or terminates the transaction by issuing + * STOP. In target mode, raised if the external host issues a STOP or + * repeated START. + */ + CommandComplete, + /** + * A target mode interrupt. This is raised if the target is stretching + * clocks for a read command. This is a level status interrupt. + */ + TransmitStretch, + /** + * A target mode interrupt. This is asserted whilst the Transmit FIFO + * level is below the low threshold. This is a level status interrupt. + */ + TransmitThreshold, + /** + * A target mode interrupt. This is raised if the target is stretching + * clocks due to full Aquired FIFO or zero count in + * targetAckControl.NBYTES (if enabled). This is a level status + * interrupt. + */ + AcquiredFull, + /** + * A target mode interrupt. This is raised if STOP is received without a + * preceding NACK during an external host read. + */ + UnexpectedStop, + /** + * A target mode interrupt. This is raised if the host stops sending the + * clock during an ongoing transaction. + */ + HostTimeout, + }; + + static constexpr uint32_t interrupt_bit(const Interrupt Interrupt) + { + return 1 << static_cast<uint32_t>(Interrupt); + }; /// Control Register Fields enum [[clang::flag_enum]] : uint32_t{ @@ -172,6 +190,20 @@ /// Enable I2C line loopback test If line loopback is enabled, the /// internal design sees ACQ and RX data as "1" ControlLineLoopback = 1 << 2, + /// Enable NACKing the address on a stretch timeout. This is a target + /// mode feature. If enabled, a stretch timeout will cause the device to + /// NACK the address byte. If disabled, it will ACK instead. + ControlNackAddressAfterTimeout = 1 << 3, + /// Enable ACK Control Mode, which works with the `targetAckControl` + /// register to allow software to control upper-layer (N)ACKing. + ControlAckControlEnable = 1 << 4, + /// Enable the bus monitor in multi-controller mode. + ControlMultiControllerMonitorEnable = 1 << 5, + /// If set, causes a read transfer addressed to the this target to set + /// the corresponding bit in the `targetEvents` register. While the + /// `transmitPending` field is 1, subsequent read transactions will + /// stretch the clock, even if there is data in the Transmit FIFO. + ControlTransmitStretchEnable = 1 << 6, }; /// Status Register Fields @@ -190,18 +222,15 @@ SmatusReceiveEmpty = 1 << 5, /// Target mode Transmit FIFO is full StatusTransmitFull = 1 << 6, - /// Target mode Receive FIFO is full + /// Target mode Acquired FIFO is full StatusAcquiredFull = 1 << 7, /// Target mode Transmit FIFO is empty StatusTransmitEmpty = 1 << 8, - /// Target mode Aquired FIFO is empty + /// Target mode Acquired FIFO is empty StatusAcquiredEmpty = 1 << 9, - /** - * A Host-Mode active transaction has been ended by the - * HostNackHandlerTimeout mechanism. This bit is cleared when - * Control.EnableHost is set by software to start a new transaction. - */ - StatusHostDisabledNackTimeout = 1 << 10, + /// Target mode stretching at (N)ACK phase due to zero count + /// in the `targetAckControl` register. + StatusAckControlStretch = 1 << 10, }; /// FormatData Register Fields @@ -227,12 +256,36 @@ FifoControlReceiveReset = 1 << 0, /// Format fifo reset. Write 1 to the register resets it. Read returns 0 FifoControlFormatReset = 1 << 1, - /// Aquired FIFO reset. Write 1 to the register resets it. Read returns 0 + /// Acquired FIFO reset. Write 1 to the register resets it. Read returns 0 FifoControlAcquiredReset = 1 << 7, /// Transmit FIFO reset. Write 1 to the register resets it. Read returns 0 FifoControlTransmitReset = 1 << 8, }; + /// ControllerEvents Register Fields + enum [[clang::flag_enum]] : uint32_t{ + /// Controller FSM is halted due to receiving an unexpected NACK. + ControllerEventsNack = 1 << 0, + /** + * Controller FSM is halted due to a Host-Mode active transaction being + * ended by the `hostNackHandlerTimeout` mechanism. + */ + ControllerEventsUnhandledNackTimeout = 1 << 1, + /** + * Controller FSM is halted due to a Host-Mode active transaction being + * terminated because of a bus timeout activated by `timeoutControl`. + */ + ControllerEventsBusTimeout = 1 << 2, + /** + * Controller FSM is halted due to a Host-Mode active transaction being + * terminated because of lost arbitration. + */ + ControllerEventsArbitrationLost = 1 << 3, + }; + + // Referred to as 'RX FIFO' in the documentation + static constexpr uint32_t ReceiveFifoDepth = 8; + /// Flag set when we're debugging this driver. static constexpr bool DebugOpenTitanI2c = true; @@ -257,6 +310,26 @@ return static_cast<uint16_t>(Res); } + /** + * Reset the controller Events so that the `ControllerHalt` interrupt will + * be released, allowing the I2C driver to continue after the controller + * FSM has halted due to e.g. a NACK, configured timeout, or loss of + * arbitration. + * + * Returns the (masked) result of reading the register before clearing + * the controller events, so that you can use the `controllerEvent` + * register fields to determine why the Controller FSM was halted. + */ + uint32_t reset_controller_events() volatile + { + constexpr uint32_t FieldMask = + (ControllerEventsNack | ControllerEventsUnhandledNackTimeout | + ControllerEventsBusTimeout | ControllerEventsArbitrationLost); + uint32_t events = controllerEvents & FieldMask; + controllerEvents = FieldMask; + return events; + } + /// Reset all of the fifos. void reset_fifos() volatile { @@ -336,14 +409,14 @@ return 0 != (StatusFormatEmpty & status); } - void blocking_write(const uint8_t Addr7, - const uint8_t data[], - const uint32_t NumBytes, - const bool SkipStop) volatile + [[nodiscard]] bool blocking_write(const uint8_t Addr7, + const uint8_t data[], + const uint32_t NumBytes, + const bool SkipStop) volatile { if (NumBytes == 0) { - return; + return true; } blocking_write_byte(FormatDataStart | (Addr7 << 1) | 0u); for (uint32_t i = 0; i < NumBytes - 1; ++i) @@ -352,25 +425,34 @@ } blocking_write_byte((SkipStop ? 0u : FormatDataStop) | data[NumBytes - 1]); + while (!format_is_empty()) + { + if (interrupt_is_asserted(Interrupt::ControllerHalt)) + { + reset_controller_events(); + return false; + } + } + return true; } [[nodiscard]] bool blocking_read(const uint8_t Addr7, uint8_t buf[], const uint32_t NumBytes) volatile { - for (uint32_t idx = 0; idx < NumBytes; idx += UINT8_MAX) + for (uint32_t idx = 0; idx < NumBytes; idx += ReceiveFifoDepth) { blocking_write_byte(FormatDataStart | (Addr7 << 1) | 1u); while (!format_is_empty()) {} - if (interrupt_is_asserted(OpenTitanI2cInterrupt::Nak)) + if (interrupt_is_asserted(Interrupt::ControllerHalt)) { - interrupt_clear(OpenTitanI2cInterrupt::Nak); + reset_controller_events(); return false; } uint32_t bytesRemaining = NumBytes - idx; - bool lastChunk = UINT8_MAX >= bytesRemaining; - uint8_t chunkSize = - lastChunk ? static_cast<uint8_t>(bytesRemaining) : UINT8_MAX; + bool lastChunk = ReceiveFifoDepth >= bytesRemaining; + uint8_t chunkSize = lastChunk ? static_cast<uint8_t>(bytesRemaining) + : ReceiveFifoDepth; blocking_write_byte((lastChunk ? FormatDataStop : 0) | FormatDataReadBytes | chunkSize); @@ -385,26 +467,25 @@ } /// Returns true if the given interrupt is asserted. - [[nodiscard]] bool - interrupt_is_asserted(OpenTitanI2cInterrupt interrupt) volatile + [[nodiscard]] bool interrupt_is_asserted(Interrupt interrupt) volatile { return 0 != (interruptState & interrupt_bit(interrupt)); } /// Clears the given interrupt. - void interrupt_clear(OpenTitanI2cInterrupt interrupt) volatile + void interrupt_clear(Interrupt interrupt) volatile { interruptState = interrupt_bit(interrupt); } /// Enables the given interrupt. - void interrupt_enable(OpenTitanI2cInterrupt interrupt) volatile + void interrupt_enable(Interrupt interrupt) volatile { interruptEnable = interruptEnable | interrupt_bit(interrupt); } /// Disables the given interrupt. - void interrupt_disable(OpenTitanI2cInterrupt interrupt) volatile + void interrupt_disable(Interrupt interrupt) volatile { interruptEnable = interruptEnable & ~interrupt_bit(interrupt); }
diff --git a/sdk/include/platform/sunburst/v0.2/platform-i2c.hh b/sdk/include/platform/sunburst/v0.2/platform-i2c.hh new file mode 100644 index 0000000..2332a8a --- /dev/null +++ b/sdk/include/platform/sunburst/v0.2/platform-i2c.hh
@@ -0,0 +1,421 @@ +#pragma once +#include <cdefs.h> +#include <debug.hh> +#include <stdint.h> + +/** + * The interrupts of the OpenTitan's I2C block. + * + * Documentation source can be found at: + * https://github.com/lowRISC/opentitan/blob/9ddf276c64e2974ed8e528e8b2feb00b977861de/hw/ip/i2c/doc/interfaces.md + */ +enum class OpenTitanI2cInterrupt +{ + /** + * A host mode interrupt. This is asserted whilst the Format FIFO level is + * below the low threshold. This is a level status interrupt. + */ + FormatThreshold, + /** + * A host mode interrupt. This is asserted whilst the Receive FIFO level is + * above the high threshold. This is a level status interrupt. + */ + ReceiveThreshold, + /** + * A target mode interrupt. This is asserted whilst the Aquired FIFO level + * is above the high threshold. This is a level status interrupt. + */ + AcquiredThreshold, + /** + * A host mode interrupt. This is raised if the Receive FIFO has overflowed. + */ + ReceiveOverflow, + /** + * A host mode interrupt. This is raised if there is no ACK in response to + * an address or data. + */ + Nak, + /** + * A host mode interrupt. This is raised if the SCL line drops early (not + * supported without clock synchronization). + */ + SclInterference, + /** + * A host mode interrupt. This is raised if the SDA line goes low when host + * is trying to assert high. + */ + SdaInterference, + /** + * A host mode interrupt. This is raised if target stretches the clock + * beyond the allowed timeout period. + */ + StretchTimeout, + /** + * A host mode interrupt. This is raised if the target does not assert a + * constant value of SDA during transmission. + */ + SdaUnstable, + /** + * A host and target mode interrupt. In host mode, raised if the host issues + * a repeated START or terminates the transaction by issuing STOP. In target + * mode, raised if the external host issues a STOP or repeated START. + */ + CommandComplete, + /** + * A target mode interrupt. This is raised if the target is stretching + * clocks for a read command. This is a level status interrupt. + */ + TransmitStretch, + /** + * A target mode interrupt. This is asserted whilst the Transmit FIFO level + * is below the low threshold. This is a level status interrupt. + */ + TransmitThreshold, + /** + * A target mode interrupt. This is raised if the target is stretching + * clocks due to full Aquired FIFO or zero count in targetAckControl.NBYTES + * (if enabled). This is a level status interrupt. + */ + AcquiredFull, + /** + * A target mode interrupt. This is raised if STOP is received without a + * preceding NACK during an external host read. + */ + UnexpectedStop, + /** + * A target mode interrupt. This is raised if the host stops sending the + * clock during an ongoing transaction. + */ + HostTimeout, +}; + +static constexpr uint32_t interrupt_bit(const OpenTitanI2cInterrupt Interrupt) +{ + return 1 << static_cast<uint32_t>(Interrupt); +}; + +/** + * Driver for the OpenTitan's I2C block. + * + * Documentation source can be found at: + * https://github.com/lowRISC/opentitan/tree/9ddf276c64e2974ed8e528e8b2feb00b977861de/hw/ip/i2c + */ +struct OpenTitanI2c +{ + /// Interrupt State Register + uint32_t interruptState; + /// Interrupt Enable Register + uint32_t interruptEnable; + /// Interrupt Test Register + uint32_t interruptTest; + /// Alert Test Register (Unused in Sonata) + uint32_t alertTest; + /// I2C Control Register + uint32_t control; + /// I2C Live Status Register for Host and Target modes + uint32_t status; + /// I2C Read Data + uint32_t readData; + /// I2C Host Format Data + uint32_t formatData; + /// I2C FIFO control register + uint32_t fifoCtrl; + /// Host mode FIFO configuration + uint32_t hostFifoConfiguration; + /// Target mode FIFO configuration + uint32_t targetFifoConfiguration; + /// Host mode FIFO status register + uint32_t hostFifoStatus; + /// Target mode FIFO status register + uint32_t targetFifoStatus; + /// I2C Override Control Register + uint32_t override; + /// Oversampled Receive values + uint32_t values; + /** + * Detailed I2C Timings (directly corresponding to table 10 in the I2C + * Specification). + */ + uint32_t timing[5]; + /// I2C clock stretching timeout control. + uint32_t timeoutControl; + /// I2C target address and mask pairs + uint32_t targetId; + /// I2C target acquired data + uint32_t acquiredData; + /// I2C target transmit data + uint32_t transmitData; + /** + * I2C host clock generation timeout value (in units of input clock + * frequency). + */ + uint32_t hostTimeoutControl; + /// I2C target internal stretching timeout control. + uint32_t targetTimeoutControl; + /** + * Number of times the I2C target has NACK'ed a new transaction since the + * last read of this register. + */ + uint32_t targetNackCount; + /** + * Timeout in Host-Mode for an unhandled NACK before hardware automatically + * ends the transaction. + */ + uint32_t targetAckControl; + + /// Control Register Fields + enum [[clang::flag_enum]] : uint32_t{ + /// Enable Host I2C functionality + ControlEnableHost = 1 << 0, + /// Enable Target I2C functionality + ControlEnableTarget = 1 << 1, + /// Enable I2C line loopback test If line loopback is enabled, the + /// internal design sees ACQ and RX data as "1" + ControlLineLoopback = 1 << 2, + }; + + /// Status Register Fields + enum [[clang::flag_enum]] : uint32_t{ + /// Host mode Format FIFO is full + StatusFormatFull = 1 << 0, + /// Host mode Receive FIFO is full + StatusReceiveFull = 1 << 1, + /// Host mode Format FIFO is empty + StatusFormatEmpty = 1 << 2, + /// Host functionality is idle. No Host transaction is in progress + StatusHostIdle = 1 << 3, + /// Target functionality is idle. No Target transaction is in progress + StatusTargetIdle = 1 << 4, + /// Host mode Receive FIFO is empty + SmatusReceiveEmpty = 1 << 5, + /// Target mode Transmit FIFO is full + StatusTransmitFull = 1 << 6, + /// Target mode Receive FIFO is full + StatusAcquiredFull = 1 << 7, + /// Target mode Transmit FIFO is empty + StatusTransmitEmpty = 1 << 8, + /// Target mode Aquired FIFO is empty + StatusAcquiredEmpty = 1 << 9, + /** + * A Host-Mode active transaction has been ended by the + * HostNackHandlerTimeout mechanism. This bit is cleared when + * Control.EnableHost is set by software to start a new transaction. + */ + StatusHostDisabledNackTimeout = 1 << 10, + }; + + /// FormatData Register Fields + enum [[clang::flag_enum]] : uint32_t{ + /// Issue a START condition before transmitting BYTE. + FormatDataStart = 1 << 8, + /// Issue a STOP condition after this operation + FormatDataStop = 1 << 9, + /// Read BYTE bytes from I2C. (256 if BYTE==0) + FormatDataReadBytes = 1 << 10, + /** + * Do not NACK the last byte read, let the read + * operation continue + */ + FormatDataReadCount = 1 << 11, + /// Do not signal an exception if the current byte is not ACK’d + FormatDataNakOk = 1 << 12, + }; + + /// FifoControl Register Fields + enum [[clang::flag_enum]] : uint32_t{ + /// Receive fifo reset. Write 1 to the register resets it. Read returns 0 + FifoControlReceiveReset = 1 << 0, + /// Format fifo reset. Write 1 to the register resets it. Read returns 0 + FifoControlFormatReset = 1 << 1, + /// Aquired FIFO reset. Write 1 to the register resets it. Read returns 0 + FifoControlAcquiredReset = 1 << 7, + /// Transmit FIFO reset. Write 1 to the register resets it. Read returns 0 + FifoControlTransmitReset = 1 << 8, + }; + + /// Flag set when we're debugging this driver. + static constexpr bool DebugOpenTitanI2c = true; + + /// Helper for conditional debug logs and assertions. + using Debug = ConditionalDebug<DebugOpenTitanI2c, "OpenTitan I2C">; + + /** + * Performs a 32-bit integer unsigned division, rounding up. The bottom + * 16 bits of the result are then returned. + * + * As usual, a divisor of 0 is still Undefined Behavior. + */ + static uint16_t round_up_divide(uint32_t a, uint32_t b) + { + if (a == 0) + { + return 0; + } + const uint32_t Res = ((a - 1) / b) + 1; + Debug::Assert(Res <= UINT16_MAX, + "Division result too large to fit in uint16_t."); + return static_cast<uint16_t>(Res); + } + + /// Reset all of the fifos. + void reset_fifos() volatile + { + fifoCtrl = (FifoControlReceiveReset | FifoControlFormatReset | + FifoControlAcquiredReset | FifoControlTransmitReset); + } + + /// Configure the I2C block to be in host mode. + void host_mode_set() volatile + { + control = ControlEnableHost; + } + + /** + * Set the I2C timing parameters appropriately for the given bit rate. + * Distilled from: + * https://github.com/lowRISC/opentitan/blob/9ddf276c64e2974ed8e528e8b2feb00b977861de/hw/ip/i2c/doc/programmers_guide.md + */ + void speed_set(const uint32_t SpeedKhz) volatile + { + // We must round up the system clock frequency to lengthen intervals. + const uint16_t SystemClockKhz = round_up_divide(CPU_TIMER_HZ, 1000); + // We want to underestimate the clock period, to lengthen the timings. + const uint16_t ClockPeriod = (1000 * 1000) / SystemClockKhz; + + // Decide which bus mode this represents + uint32_t mode = (SpeedKhz > 100u) + (SpeedKhz > 400u); + + // Minimum fall time when V_DD is 3.3V + constexpr uint16_t MinimumFallTime = 20 * 3 / 5; + // Specification minimum timings (Table 10) in nanoseconds for each bus + // mode. + constexpr uint16_t MinimumTimeValues[5][2][3] = { + { + {4700u, 1300u, 150u}, // Low Period + {4000u, 600u, 260u}, // High Period + }, + { + // Fall time of SDA and SCL signals + {MinimumFallTime, MinimumFallTime, MinimumFallTime}, + // Rise time of SDA and SCL signals + {120, 120, 120}, + }, + { + {4700u, 600u, 260u}, // Hold time for a repeated start condition + {4000u, 600u, 260u}, // Set-up time for a repeated start condition + }, + { + {4000u, 1u, 1u}, // Data hold time + {500u, 100u, 50u}, // Data set-up time + }, + { + // Bus free time between a STOP and START condition + {4700u, 1300u, 500u}, + // Set-up time for a STOP condition + {4000u, 600u, 260u}, + }, + }; + for (uint32_t i = 0; i < 5; ++i) + { + timing[i] = + (round_up_divide(MinimumTimeValues[i][0][mode], ClockPeriod) + << 16) | + round_up_divide(MinimumTimeValues[i][1][mode], ClockPeriod); + } + } + + void blocking_write_byte(const uint32_t Fmt) volatile + { + while (0 != (StatusFormatFull & status)) {} + formatData = Fmt; + } + + /// Returns true when the format fifo is empty + [[nodiscard]] bool format_is_empty() volatile + { + return 0 != (StatusFormatEmpty & status); + } + + void blocking_write(const uint8_t Addr7, + const uint8_t data[], + const uint32_t NumBytes, + const bool SkipStop) volatile + { + if (NumBytes == 0) + { + return; + } + blocking_write_byte(FormatDataStart | (Addr7 << 1) | 0u); + for (uint32_t i = 0; i < NumBytes - 1; ++i) + { + blocking_write_byte(data[i]); + } + blocking_write_byte((SkipStop ? 0u : FormatDataStop) | + data[NumBytes - 1]); + } + + [[nodiscard]] bool blocking_read(const uint8_t Addr7, + uint8_t buf[], + const uint32_t NumBytes) volatile + { + for (uint32_t idx = 0; idx < NumBytes; idx += UINT8_MAX) + { + blocking_write_byte(FormatDataStart | (Addr7 << 1) | 1u); + while (!format_is_empty()) {} + if (interrupt_is_asserted(OpenTitanI2cInterrupt::Nak)) + { + interrupt_clear(OpenTitanI2cInterrupt::Nak); + return false; + } + uint32_t bytesRemaining = NumBytes - idx; + bool lastChunk = UINT8_MAX >= bytesRemaining; + uint8_t chunkSize = + lastChunk ? static_cast<uint8_t>(bytesRemaining) : UINT8_MAX; + + blocking_write_byte((lastChunk ? FormatDataStop : 0) | + FormatDataReadBytes | chunkSize); + while (!format_is_empty()) {} + + for (uint32_t chunkIdx = 0; chunkIdx < chunkSize; ++chunkIdx) + { + buf[idx + chunkIdx] = readData; + } + } + return true; + } + + /// Returns true if the given interrupt is asserted. + [[nodiscard]] bool + interrupt_is_asserted(OpenTitanI2cInterrupt interrupt) volatile + { + return 0 != (interruptState & interrupt_bit(interrupt)); + } + + /// Clears the given interrupt. + void interrupt_clear(OpenTitanI2cInterrupt interrupt) volatile + { + interruptState = interrupt_bit(interrupt); + } + + /// Enables the given interrupt. + void interrupt_enable(OpenTitanI2cInterrupt interrupt) volatile + { + interruptEnable = interruptEnable | interrupt_bit(interrupt); + } + + /// Disables the given interrupt. + void interrupt_disable(OpenTitanI2cInterrupt interrupt) volatile + { + interruptEnable = interruptEnable & ~interrupt_bit(interrupt); + } + + /** + * Sets the thresholds for the format and receive fifos. + */ + void host_thresholds_set(uint16_t formatThreshold, + uint16_t receiveThreshold) volatile + { + hostFifoConfiguration = + (formatThreshold & 0xfff) << 16 | (receiveThreshold & 0xfff); + } +};
diff --git a/sdk/include/stdlib.h b/sdk/include/stdlib.h index adc2143..54a9dc6 100644 --- a/sdk/include/stdlib.h +++ b/sdk/include/stdlib.h
@@ -11,6 +11,13 @@ #include <timeout.h> /** + * The SDK allocator implementation works in terms of "chunks" and has minimum + * size requirements for these. This is occasionally visible to its clients, + * as documented on interface functions below. + */ +static const size_t CHERIOTHeapMinChunkSize = 16; + +/** * `MALLOC_QUOTA` sets the quota for the current compartment for use with * malloc and free. This defaults to 4 KiB. */ @@ -135,9 +142,16 @@ }; /** - * Non-standard allocation API. Allocates `size` bytes. Blocking behaviour is - * controlled by the `flags` and the `timeout` parameters. + * Non-standard allocation API. Allocates `size` bytes. * + * The `heapCapability` quota object must have remaining capacity sufficient + * for the requested `size` as well as any padding required by the CHERIoT + * capability encoding (see its ISA document for details) and any additional + * space required by the allocator's internal layout, which may be up to + * `CHERIOTHeapMinChunkSize` bytes. Not all of these padding bytes may be + * available for use via the returned capability. + * + * Blocking behaviour is controlled by the `flags` and the `timeout` parameters. * Specifically, the `flags` parameter defines on which conditions to wait, and * the `timeout` parameter how long to wait. * @@ -172,11 +186,11 @@ * checking for arithmetic overflow. Similarly to `heap_allocate`, blocking * behaviour is controlled by the `flags` and the `timeout` parameters. * - * See `heap_allocate` for more information on the blocking behavior. One - * difference between this and `heap_allocate` is the definition of when the - * allocation cannot be satisfied under any circumstances, which is here if - * `nmemb` * `size` is larger than the total heap size, or if `nmemb` * `size` - * overflows. + * See `heap_allocate` for more information on the padding and blocking + * behavior. One difference between this and `heap_allocate` is the definition + * of when the allocation cannot be satisfied under any circumstances, which is + * here if `nmemb` * `size` is larger than the total heap size, or if `nmemb` * + * `size` overflows. * * Similarly to `heap_allocate`, `-ENOTENOUGHSTACK` may be returned if the * stack is insufficiently large to run the function. See `heap_allocate`. @@ -195,9 +209,11 @@ * provided by the first argument until a corresponding call to `heap_free`. * Note that this can be used with interior pointers. * - * This will return the size of the allocation claimed on success, 0 on error - * (if `heapCapability` or `pointer` is not valid, etc.), or `-ENOTENOUGHSTACK` - * if the stack is insufficiently large to run the function. + * This will return the size of the allocation claimed on success (which may be + * larger than the size requested in the original `heap_allocate` call; see its + * documentation for more information), 0 on error (if `heapCapability` or + * `pointer` is not valid, etc.), or `-ENOTENOUGHSTACK` if the stack is + * insufficiently large to run the function. */ ssize_t __cheri_compartment("alloc") heap_claim(struct SObjStruct *heapCapability, void *pointer);
diff --git a/sdk/lib/crt/arith64.c b/sdk/lib/crt/arith64.c index bb64553..611602c 100644 --- a/sdk/lib/crt/arith64.c +++ b/sdk/lib/crt/arith64.c
@@ -25,6 +25,7 @@ // - Add asm labels so that the compiler doesn't need to know the C++ type encodings for builtins. // - Use standard integer types. // - Add SPDX tags for license from the upstream repository (commit 426b7578ecfb5ce7c841e738613cff2a261214eb) +// - Add the implementation of __multi3() // // This file is *not* formatted, to make it easier to track changes from upstream (if there are any). @@ -54,6 +55,7 @@ int __cheri_libcall __popcountdi2(arith64_u64 a) __asm__("__popcountdi2"); arith64_u64 __cheri_libcall __udivdi3(arith64_u64 a, arith64_u64 b) __asm__("__udivdi3"); arith64_u64 __cheri_libcall __umoddi3(arith64_u64 a, arith64_u64 b) __asm__("__umoddi3"); +arith64_s64 __cheri_libcall __multi3(arith64_s64 a, arith64_s64 b) __asm__("__multi3"); typedef union { @@ -300,3 +302,9 @@ __divmoddi4(a, b, &r); return r; } + +// Return the product of a and b +[[clang::no_builtin]] arith64_s64 __multi3(arith64_s64 a, arith64_s64 b) +{ + return a * b; +} \ No newline at end of file
diff --git a/sdk/lib/unwind_error_handler/unwind.S b/sdk/lib/unwind_error_handler/unwind.S index 6c74c06..8f179bb 100644 --- a/sdk/lib/unwind_error_handler/unwind.S +++ b/sdk/lib/unwind_error_handler/unwind.S
@@ -6,7 +6,7 @@ * * If there is no registered CleanupList structure (equivalently, there's no * CHERIOT_DURING block active at the time of the fault), then this requests - * unwnding out of the compartment. Otherwise, we will longjmp() out to the + * unwinding out of the compartment. Otherwise, we will longjmp() out to the * indicated handler (that is, the CHERIOT_HANDLER block associated with the * current CHERIOT_DURING block), having reset the compartment error handler * invocation counter to zero.
diff --git a/sdk/privileged-compartment.ldscript b/sdk/privileged-compartment.ldscript index c8f1b0a..ba0fdef 100644 --- a/sdk/privileged-compartment.ldscript +++ b/sdk/privileged-compartment.ldscript
@@ -25,6 +25,7 @@ # The sealing keys for this compartment is before the import table, if they exist *(.sealing_key1*) *(.sealing_key2*) + *(.sealing_key3*) } # Lay out the compartment imports section. This will end up on PCC. .compartment_import_table : ALIGN(8)
diff --git a/tests/allocator-test.cc b/tests/allocator-test.cc index 17faf66..05a4ac8 100644 --- a/tests/allocator-test.cc +++ b/tests/allocator-test.cc
@@ -313,6 +313,13 @@ "running claims tests", MALLOC_QUOTA, quotaLeft); + /* + * Allocate sufficiently small objects that no additional alignment or + * padding requirements arise from capability encoding. Nevertheless, + * because our allocator imposes minimal chunk sizes, we cannot be sure + * that the underlying object is exactly this size. When probing, + * permit some slack, up to CHERIOTHeapMinChunkSize. + */ size_t allocSize = 128; auto mallocQuotaLeft = heap_quota_remaining(MALLOC_CAPABILITY); CHERI::Capability alloc{ @@ -322,7 +329,8 @@ auto claim = [&]() { ssize_t claimSize = heap_claim(SECOND_HEAP, alloc); claimCount++; - TEST(claimSize == allocSize, + TEST((allocSize <= claimSize) && + (claimSize <= allocSize + CHERIOTHeapMinChunkSize), "{}-byte allocation claimed as {} bytes (claim number {})", allocSize, claimSize,
diff --git a/tests/crash_recovery-test.cc b/tests/crash_recovery-test.cc index 48dc278..cc67b24 100644 --- a/tests/crash_recovery-test.cc +++ b/tests/crash_recovery-test.cc
@@ -13,8 +13,9 @@ static void test_irqs_are_enabled() { void *r = __builtin_return_address(0); - TEST(__builtin_cheri_type_get(r) == CheriSealTypeReturnSentryEnabling, - "Calling context has IRQs disabled"); + TEST_EQUAL(__builtin_cheri_type_get(r), + CheriSealTypeReturnSentryEnabling, + "Calling context has IRQs disabled"); } extern "C" enum ErrorRecoveryBehaviour