From ed1383c7362fa462d39a31b200fd24c7fcc7b38f Mon Sep 17 00:00:00 2001 From: Jacob Bramley Date: Wed, 20 Jan 2021 20:08:30 +0000 Subject: [PATCH 1/8] C64-Secure ABI demo. This demo illustrates a few calling-convention options for weak (but potentially fast) compartmentalisation across function calls. --- morello-c64-secure/Makefile.morello-purecap | 11 + morello-c64-secure/README.md | 73 ++++ morello-c64-secure/c64-secure.c | 417 ++++++++++++++++++++ morello-c64-secure/impls.s | 238 +++++++++++ 4 files changed, 739 insertions(+) create mode 100644 morello-c64-secure/Makefile.morello-purecap create mode 100644 morello-c64-secure/README.md create mode 100644 morello-c64-secure/c64-secure.c create mode 100644 morello-c64-secure/impls.s diff --git a/morello-c64-secure/Makefile.morello-purecap b/morello-c64-secure/Makefile.morello-purecap new file mode 100644 index 0000000..d45d716 --- /dev/null +++ b/morello-c64-secure/Makefile.morello-purecap @@ -0,0 +1,11 @@ +# Copyright (c) 2024 The CapableVMs "CHERI Examples" Contributors. +# SPDX-License-Identifier: MIT OR Apache-2.0 + +include ../build/Makefile.vars.morello-purecap +include ../build/Makefile.vars.common + +SHARED_SOURCES := impls.s +RUNDIR := c64-secure +export + +include ../build/Makefile.simple diff --git a/morello-c64-secure/README.md b/morello-c64-secure/README.md new file mode 100644 index 0000000..176774b --- /dev/null +++ b/morello-c64-secure/README.md @@ -0,0 +1,73 @@ +# Morello C64-Secure Example + +This example demonstrates a few procedure-call standard variants that provide +partial compartmentalisation with minimal cost. + +Full compartmentalisation usually requires register banking and explicit +management of capabilities over the trust boundary. Often, an intermediary is +required, such as a compartment manager. Such approaches have inherent code-size +and performance costs, and so a cheaper (but imperfect) alternative might be +preferred for routine function calls. + +For routine function calls, we consider the case where callee is not +deliberately malicious, but might be susceptible to data-driven attacks. Such +attacks should not have arbitrary access to the frame record (which could be +used to divert control flow), nor to the caller's stack. + +Three ABIs, with this goal, are demonstrated: + + A: "C64-Secure" (naive variant) + + Restrict csp to the stack frame, and cfp to the frame record itself. Both + capabilities must be saved (alongside clr), so the frame record has three + entries in this variant. + + This is a weak compartment because it relies on the generated code not + accessing cfp at all, other than in the function prologue and epilogue. This + can often be guaranteed, but not always. + + This variant is simple, but its security is limited by the representable + precision. If the stack bounds are large (as they typically are), the + callee's stack will typically end up covering the whole stack anyway, since + `scbnds` is used, rather than `scbndse`. However, this variant could be + effective on threads or other subsystems with small stacks. + + In the interactive demo: the bounds on csp don't actually appear to be + restricted unless you request very large stack frames. + + B: "C64-Secure" (conservative variant) + + As A, but pad stack frames to ensure that they don't overlap. This + guarantees the expected security proprties, but wastes a lot of space at the + start of the stack. + + In the interactive demo: the bounds are precise, as expected, and csp never + grants access to the frame record, but there are a large number of (padding) + locals on each stack frame. + + C: "C64-Secure v2" + + Attempt to get the security properties of B, but with no wasted space. + + - Leave cfp unrestricted (spanning the whole stack). + - Restrict csp to the current function's stack frame only. + + Restricting csp to only a single function's frame (rather than all of the + remaining stack space) means that it can be much more precisely bounded. + In the function prologue, a new csp is derived from the caller's cfp. + + As a bonus, only a two-entry stack frame is required. One slot holds clr (as + usual), and the other holds the caller's csp bounds and the cfp address. + + A disadvantage of this variant is that more code is required, but this could + be simplified if frame sizes are known to be small. The example makes a + couple of simplifying assumptions (described in the code). + + Z: "AAPCS64-cap" + + This is the procedure-call standard used by C code compiled for Morello + purecap. It is provided here for reference. + +It is not easy to call between some of these variants, so the example won't +provide all options. In practice, it would be possible to convert using a +trampoline of some sort, but that isn't implemented here. diff --git a/morello-c64-secure/c64-secure.c b/morello-c64-secure/c64-secure.c new file mode 100644 index 0000000..5d8429a --- /dev/null +++ b/morello-c64-secure/c64-secure.c @@ -0,0 +1,417 @@ +// Copyright (c) 2024 The CapableVMs "CHERI Examples" Contributors. +// See COPYRIGHT in the project root for details. +// SPDX-License-Identifier: Apache-2.0 OR MIT + +// This demo is interactive, and should be self-explanatory in use. The code in +// this file is mostly related to display and infrastructure (such as drawing +// the backtrace). + +#include +#include +#include +#include +#include +#include +#include + +void A(uint32_t locals); +void B(uint32_t locals); +void C(uint32_t locals); +void Z(uint32_t locals); +typedef void (*AbiHelperFn)(uint32_t); + +int is_in_A(uintptr_t pcc); +int is_in_B(uintptr_t pcc); +int is_in_C(uintptr_t pcc); +int is_in_Z(uintptr_t pcc); + +enum ABI { + AbiA, + AbiB, + AbiC, + AbiZ, + AbiUnknown, +}; + +const char* abi_label(enum ABI abi) { + switch (abi) { + case AbiA: return "A"; + case AbiB: return "B"; + case AbiC: return "C"; + case AbiZ: return "Z"; + case AbiUnknown: return NULL; + } +} + +enum ABI abi_from_label(const char* label) { + if ((strcmp(label, "A") == 0) || (strcmp(label, "a") == 0)) { + return AbiA; + } + if ((strcmp(label, "B") == 0) || (strcmp(label, "b") == 0)) { + return AbiB; + } + if ((strcmp(label, "C") == 0) || (strcmp(label, "c") == 0)) { + return AbiC; + } + if ((strcmp(label, "Z") == 0) || (strcmp(label, "z") == 0)) { + return AbiZ; + } + return AbiUnknown; +} + +const char* abi_desc(enum ABI abi) { + switch (abi) { + case AbiA: return "C64-Secure, with naive (insecure) csp bounds restriction."; + case AbiB: return "C64-Secure, with conservative csp bounds restriction."; + case AbiC: return "C64-Secure v2, with precisely-restricted csp (and full cfp)."; + case AbiZ: return "AAPCS64-cap, used by C code."; + case AbiUnknown: return "Unknown"; + } +} + +enum ABI abi_for(size_t pcc) { + if (is_in_A(pcc)) { + return AbiA; + } else if (is_in_B(pcc)) { + return AbiB; + } else if (is_in_C(pcc)) { + return AbiC; + } else if (is_in_Z(pcc)) { + return AbiZ; + } else { + return AbiUnknown; + } +} + +AbiHelperFn fn_for(enum ABI abi) { + switch (abi) { + case AbiA: return A; + case AbiB: return B; + case AbiC: return C; + case AbiZ: return Z; + case AbiUnknown: return NULL; + } +} + +bool can_call(enum ABI from, enum ABI to) { + // Note: All ABIs can call C code well enough for `what_next()`, because + // it doesn't need much stack. However, deep calls are likely to fail + // without a trampoline to properly convert ABIs, so we don't permit + // calls into AAPCS64-cap for this demo. + switch (from) { + case AbiA: + case AbiB: + return (to == AbiA) || (to == AbiB); + case AbiC: + return (to == AbiC); + case AbiZ: + // We have to be able to call anything from 'Z' + // (AAPCS64-cap) because it's used by C code. + return (to != AbiUnknown); + case AbiUnknown: + return false; + } +} + +int main(int argc, char *argv[]) +{ + printf("C64-Secure Demo\n"); + Z(0); + return 0; +} + +int is_in_range(size_t address, size_t start, size_t end) { + return (address >= start) && (address < end); +} + +void clear() { + printf("\033[2J\033[H"); +} + +struct Context { + uintptr_t cfp; + uintptr_t csp; + uintptr_t pcc; +}; + +typedef size_t Slot; + +// The left brackets show the stack frames, and mark the division between the +// locals and the frame records. +const char* lbracket_for(Slot slot, Slot csp_slot, Slot cfp_slot, Slot cfp_max_slot) { + if (slot == cfp_max_slot) { + return "╭"; + } else if (slot == csp_slot) { + return "╰"; + } else if (slot == cfp_slot) { + return "├"; + } else if ((slot > csp_slot) && (slot < cfp_max_slot)) { + return "│"; + } else { + return " "; + } +} + +// The right brackets show current (most recent) capabilities and how they +// relate to the stack. For all current ABIs, we draw csp and cfp. +const char* rbracket_for(Slot slot, Slot start_slot, Slot value_slot, Slot max_slot) { + bool is_value = slot == value_slot; + if (slot == start_slot) { + return is_value ? "◀╯" : " ╯"; + } else if (slot == max_slot) { + return is_value ? "◀╮" : " ╮"; + } else if ((slot > start_slot) && (slot < max_slot)) { + return is_value ? "◀┤" : " │"; + } else { + return is_value ? "◀ " : " "; + } +} + +// Draw a backtrace of all demo functions, stopping when the backtrace one of +// the functions that we know about. In practice, that's whatever C code first +// called `Z(0)` or similar. +int backtrace(struct Context frame, struct Context const* current) { + enum ABI abi = abi_for(frame.pcc); + if (abi == AbiUnknown) { + // Probably C code. Stop looking for more frames. + printf(" 0: ...\n"); + return 0; + } + + // We're going to display one slot per row, where (for simplicity) one + // slot is the size of a single capability. + const size_t slot_size_log2 = 4; + const size_t slot_size = 1 << slot_size_log2; + assert(slot_size == sizeof(uintptr_t)); // We require purecap. + + // The frame record starts at cfp for all PCS variants. + const uintptr_t* frame_record = (const uintptr_t*)(frame.cfp); + const char* frame_slot_names[3] = { "UNKNOWN", "UNKNOWN", "UNKNOWN" }; + size_t frame_record_slots; + struct Context caller; + size_t cfp_len = cheri_getlen(frame.cfp); + ptraddr_t csp_limit = cheri_getbase(frame.csp) + cheri_getlen(frame.csp); + switch (abi) { + case AbiUnknown: + // Probably C's `main`. Stop looking for more frames. + printf(" 0: ...\n"); + return 0; + case AbiA: + case AbiB: + // C64-Secure frame + assert(cheri_getoffset(frame.cfp) == 0); + frame_record_slots = 3; + caller.cfp = frame_record[0]; + caller.csp = frame_record[1]; + caller.pcc = frame_record[2]; + frame_slot_names[0] = "cfp"; + frame_slot_names[1] = "csp"; + frame_slot_names[2] = "pcc"; + break; + case AbiC: + // C64-Secure v2, with precisely-restricted csp. + frame_record_slots = 2; + ptraddr_t caller_sp = + cheri_getaddress(frame_record[0] + frame_record_slots); + caller.cfp = cheri_setaddress(frame.cfp, frame_record[0]); + caller.csp = cheri_setaddress(frame_record[0], caller_sp); + caller.pcc = frame_record[1]; + frame_slot_names[0] = "(cfp x csp)"; + frame_slot_names[1] = "pcc"; + break; + case AbiZ: + // AAPCS64-cap frame + frame_record_slots = 2; + caller.cfp = frame_record[0]; + caller.csp = frame_record[0] + frame_record_slots; + caller.pcc = frame_record[1]; + frame_slot_names[0] = "cfp"; + frame_slot_names[1] = "pcc"; + break; + } + + // Recurse before printing this frame, so we print oldest frames first. + int frame_num = backtrace(caller, current) + 1; + + // ---- Print the frame record. ---- + // + // Do all arithmetic (etc) on 16-byte slots, to keep things simple. The + // stack has to be 16-byte aligned at function boundaries so there + // should be no corner cases here (at least with this demo). + // + // This code is simplified by the assumption that all test functions + // emit their frame record first, immediately below their caller's stack + // area. This is a common arrangement, sufficient for demonstration, but + // not strictly required. + + Slot csp_slot = (Slot)(frame.csp) / slot_size; + Slot cfp_slot = (Slot)(frame.cfp) / slot_size; + Slot cfp_max_slot = cfp_slot + frame_record_slots - 1; + + size_t cur_csp = (size_t)(current->csp); + size_t cur_csp_base = (size_t)(cheri_getbase(current->csp)); + size_t cur_csp_len = (size_t)(cheri_getlen(current->csp)); + Slot cur_csp_min_slot = cur_csp_base / slot_size; + Slot cur_csp_slot = (Slot)(current->csp) / slot_size; + Slot cur_csp_max_slot = (Slot)(cur_csp_base + cur_csp_len - 1) / slot_size; + + size_t cur_cfp_base = (size_t)(cheri_getbase(current->cfp)); + size_t cur_cfp_len = (size_t)(cheri_getlen(current->cfp)); + Slot cur_cfp_min_slot = cur_cfp_base / slot_size; + Slot cur_cfp_slot = (Slot)(current->cfp) / slot_size; + Slot cur_cfp_max_slot = (Slot)(cur_cfp_base + cur_cfp_len - 1) / slot_size; + + // Skip over locals, except some at the start and end of the range. + const size_t print_locals = 9; + size_t num_locals = cfp_slot - csp_slot; + size_t skipped_locals_base = 0; + size_t skipped_locals_limit = 0; + if (num_locals > print_locals) { + // Skip an extra line to allow for "...". + size_t skip = num_locals - print_locals + 1; + skipped_locals_base = csp_slot + (print_locals / 2); + skipped_locals_limit = skipped_locals_base + skip; + } + + for (Slot slot = cfp_max_slot; slot >= csp_slot; slot--) { + size_t addr = slot * slot_size; + char buf[128]; // For two-step formatting. + + if ((slot >= skipped_locals_base) && (slot < skipped_locals_limit)) { + slot = skipped_locals_base; + } + + // Column 1: Function names and ownership brackets. + const char* lbracket = + lbracket_for(slot, csp_slot, cfp_slot, cfp_max_slot); + if (slot == (cfp_max_slot - (frame_record_slots / 2))) { + printf("%3d: %5s %s", frame_num, abi_label(abi), lbracket); + } else { + printf(" %s", lbracket); + } + + // Column 2: Stack slot contents. + printf(" %#18zx:", addr); + if (slot >= cfp_slot) { + // An entry in the frame record. + size_t index = slot - cfp_slot; + assert(index < frame_record_slots); + snprintf(buf, sizeof(buf), "%s'%d", frame_slot_names[index], frame_num - 1); + } else if ((slot >= skipped_locals_base) && (slot < skipped_locals_limit)) { + // Skipped locals (below the frame record). + snprintf(buf, sizeof(buf), "..."); + } else { + // Padding and locals (below the frame record). + snprintf(buf, sizeof(buf), "local[%zu]", slot - csp_slot); + } + printf(" %-14s", buf); + + // Column 3: Current csp/cfp ranges. + printf(" %s %s", + rbracket_for(slot, cur_csp_min_slot, cur_csp_slot, cur_csp_max_slot), + (slot == cur_csp_slot) ? "csp" : " "); + printf(" %s %s", + rbracket_for(slot, cur_cfp_min_slot, cur_cfp_slot, cur_cfp_max_slot), + (slot == cur_cfp_slot) ? "cfp" : " "); + + printf("\n"); + } + + return frame_num; +} + +struct Next { + uint32_t locals; + uintptr_t fn; +}; + +struct Next next(uintptr_t what, size_t locals) { + struct Next result; + result.locals = locals; + result.fn = what; + return result; +} + +// If we can call a demo function that uses the specified ABI, then print it as +// an option for the user (and return true). +bool maybe_print_abi_desc(enum ABI from, enum ABI to) { + if (can_call(from, to)) { + char const * label = abi_label(to); + char const * desc = abi_desc(to); + printf(" %s: %s\n", + (label ? label : "?"), + (desc ? desc : "?")); + return true; + } + return false; +} + +// From a given context, ask the user what to call next. +// +// This should be called from each demo function in a loop. The demo function +// should either call the resulting function, or return (exiting its loop) if +// it's NULL. +// +// `can_call()` says that the example ABIs can't call 'Z' (C code), but actually +// they'll all work well enough to call this helper. TODO: This might fail if +// the user requests a large number of locals. +struct Next what_next(uintptr_t cfp, uintptr_t csp, size_t pc) { + clear(); + printf("C64-Secure Demo\n"); + struct Context ctx = { cfp, csp, pc }; + backtrace(ctx, &ctx); + + enum ABI from = abi_for(pc); + + printf("\n"); + printf("Enter a decimal number to allocate at least that many locals\n"); + printf("in the next called function, then a single character to choose\n"); + printf("the function to call:\n"); + maybe_print_abi_desc(from, AbiA); + maybe_print_abi_desc(from, AbiB); + maybe_print_abi_desc(from, AbiC); + maybe_print_abi_desc(from, AbiZ); + printf(" .: None (return).\n"); + + // NULL means "return". + const struct Next ret = { 0, (uintptr_t)0 }; + size_t locals = 0; + while (1) { + int c = getchar(); + + if (c == EOF) { + exit(0); + } + + if (c == '.') { + return ret; + } + + if (isspace(c)) { + continue; + } + + char str[] = { c, '\0' }; + enum ABI abi = abi_from_label(str); + if (can_call(from, abi)) { + return next((uintptr_t)fn_for(abi), locals); + } + + bool bad_c = true; + + // Look for a digit. + const char digits[] = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9'}; + for (int i = 0; i < (sizeof(digits)/sizeof(digits[0])); i++) { + if (digits[i] == c) { + locals = locals * 10 + i; + bad_c = false; + break; + } + } + + if (bad_c) { + printf("Calling '%c' from '%s' is unimplemented.\n", toupper(c), abi_label(from)); + continue; + } + } +} diff --git a/morello-c64-secure/impls.s b/morello-c64-secure/impls.s new file mode 100644 index 0000000..d192c10 --- /dev/null +++ b/morello-c64-secure/impls.s @@ -0,0 +1,238 @@ +// Copyright (c) 2024 The CapableVMs "CHERI Examples" Contributors. +// See COPYRIGHT in the project root for details. +// SPDX-License-Identifier: Apache-2.0 OR MIT + + +// Note that these demos make use of the fact that gcvalue(c) == x. + + + .text + .balign 4 + + // A: C64-Secure, with naive (insecure) csp bounds restriction. + // + // Accept that some frames may have a csp that encompasses the frame + // record. This is the naive solution and does not waste any stack + // memory, but probably compromises the security properties of + // C64-Secure. + .global A + .type A, @function +A: + // Frame record: + // cfp + 32: CLR / previous PCC + // cfp + 16: Previous CSP + // cfp + 0: Previous CFP + mov c16, csp + str cfp, [csp, #-48]! + stp c16, clr, [csp, #16] + // Point cfp to the frame record, and restrict its bounds. + scbnds cfp, csp, #48 + + scoff c16, csp, xzr + sub x17, fp, x16 + scbnds c17, c16, x17 // Warning: Inexact! May overlap frame record. + sub x16, sp, w0, uxtw #4 // Allocate locals. + scvalue csp, c17, x16 + +1: mov c0, cfp + mov c1, csp + adr c2, 2f + bl what_next + // w0: 'locals' for the callee. + // c1: The callee's address (if any). +2: chktgd c1 + b.cc 3f + blrs c1 + b 1b // Keep going until it's time to return. +3: + // Epilogue: Unravel everything. + ldp c16, clr, [cfp, #16] + ldr cfp, [cfp] + mov csp, c16 + ret clr +A_end: + .size A, A_end-A + + + // B: C64-Secure, with conservative csp bounds restriction. + // + // Add padding below the frame record to ensure that csp does not cover + // it. This might waste substantial amounts of stack memory, especially + // near the start of the stack. + .global B + .type B, %function +B: + // Frame record: + // cfp + 32: CLR / previous PCC + // cfp + 16: Previous CSP + // cfp + 0: Previous CFP + mov c10, csp + str cfp, [csp, #-48]! + stp c10, clr, [csp, #16] + // Point cfp to the frame record, and restrict its bounds. + scbnds cfp, csp, #48 + + // The requested length is gcvalue(cfp) - gcbase(csp). + // We'll round that down to get the representable length. + scoff c10, csp, xzr + sub x11, fp, x10 + // rrlen rounds the length _up_, so use rrmask instead and round the + // length down manually. + rrmask x12, x11 + and x12, x11, x12 + // We assume that since the original stack must have been larger than + // this, its base is suitably aligned. + // TODO: Prove that this is sound. + scbndse c10, c10, x12 + sub x12, x12, w0, uxtw #4 // Allocate locals. + scoff csp, c10, x12 + +1: mov c0, cfp + mov c1, csp + adr c2, 2f + bl what_next + // w0: 'locals' for the callee. + // c1: The callee's address (if any). +2: chktgd c1 + b.cc 3f + blrs c1 + b 1b // Keep going until it's time to return. +3: + // Epilogue: Unravel everything. + ldp c10, clr, [cfp, #16] + ldr cfp, [cfp] + mov csp, c10 + ret clr +B_end: + .size B, B_end-B + + + // C: C64-Secure v2, with precisely-restricted csp (and full cfp). + // + // Give CFP full-stack bounds, and derive a smaller, exact CSP for each + // function. + // + // This assumes that, on entry, CFP's bounds extend to the stack limit. + .global C + .type C, %function +C: + // Frame record: + // cfp + 16: CLR / previous PCC + // cfp + 0: Previous CFP with previous CSP's bounds + mov c16, csp + scvalue c16, c16, fp + stp c16, clr, [csp, #-32]! + // Move the value of csp into cfp, but keep cfp's bounds. + mov x16, sp + scvalue cfp, cfp, x16 + + // Derive a new csp from the new cfp. + // TODO: For now, we just allow 16KB per function, but we could pick the + // longest length that guarantees 16-byte alignment (E=4). + mov x16, #(16 * 1024) + sub x17, sp, x16 + scvalue c17, cfp, x17 + scbndse c17, c17, x16 + sub x16, sp, w0, uxtw #4 // Allocate locals. + scvalue csp, c17, x16 + // TODO: The very last section of stack will be unusable because + // scbnds{e} fails if the required bounds exceed the bounds of . + +1: mov c0, cfp + mov c1, csp + adr c2, 2f + bl what_next + // w0: 'locals' for the callee. + // c1: The callee's address (if any). +2: chktgd c1 + b.cc 3f + blrs c1 + b 1b // Keep going until it's time to return. +3: + // Epilogue: Unravel everything. + ldp c16, clr, [cfp] + add x17, fp, #32 // Drop locals and the stack frame. + scvalue cfp, cfp, x16 + scvalue c16, c16, x17 + mov csp, c16 + ret clr +C_end: + .size C, C_end-C + + + // Z: AAPCS64-cap, with no csp bounds restriction. + .global Z + .type Z, %function +Z: + // Frame record: + // cfp + 16: CLR / previous PCC + // cfp + 0: Previous CFP + stp cfp, clr, [csp, #-32]! + mov cfp, csp + + // Allocate locals. + // Morello has no 'sub , , ` form, so we have to negate + // the length explicitly. + neg x0, x0, lsl #4 + add csp, csp, x0 + +1: mov c0, cfp + mov c1, csp + adr c2, 2f + bl what_next + // w0: 'locals' for the callee. + // c1: The callee's address (if any). +2: chktgd c1 + b.cc 3f + blrs c1 + b 1b // Keep going until it's time to return. +3: + // Epilogue: Unravel everything. + mov csp, cfp + ldp cfp, clr, [csp], #32 + ret clr +Z_end: + .size Z, Z_end-Z + + + // Helpers for mapping a code address to a given demo function. This is + // how we identify which ABI was used. + + .global is_in_A + .type is_in_A, %function +is_in_A: + adr c1, A + gcvalue x1, c1 + add x2, x1, #(A_end-A) + b is_in_range + .size is_in_A, .-is_in_A + + + .global is_in_B + .type is_in_B, %function +is_in_B: + adr c10, B + gcvalue x1, c10 + add x2, x1, #(B_end-B) + b is_in_range + .size is_in_B, .-is_in_B + + + .global is_in_C + .type is_in_C, %function +is_in_C: + adr c10, C + gcvalue x1, c10 + add x2, x1, #(C_end-C) + b is_in_range + .size is_in_C, .-is_in_C + + + .global is_in_Z + .type is_in_Z, %function +is_in_Z: + adr c10, Z + gcvalue x1, c10 + add x2, x1, #(Z_end-Z) + b is_in_range + .size is_in_Z, .-is_in_Z From 2e1674bc28a894ef999c5a7953b15f95acfb45b9 Mon Sep 17 00:00:00 2001 From: Jacob Bramley Date: Fri, 19 Apr 2024 18:40:30 +0100 Subject: [PATCH 2/8] fixup! C64-Secure ABI demo. Add the new demo to .buildbot.sh. --- .buildbot.sh | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/.buildbot.sh b/.buildbot.sh index fd8dee4..3749619 100755 --- a/.buildbot.sh +++ b/.buildbot.sh @@ -12,7 +12,7 @@ echo "Checking clang-format..." CLANG_FORMAT=$CHERI_DIR/morello-sdk/bin/clang-format find . -iname "*.c" -o -iname "*.h" -o -iname "*.cpp" -o -iname "*.hpp" | xargs "$CLANG_FORMAT" --dry-run -Werror -echo "Checking that all the purecap examples build for all platforms..." +echo "Checking that all examples build for all platforms..." PLATFORMS='riscv64-purecap morello-purecap' # TODO: Add "example_allocators". for dir in . employee shared_objects timsort; do @@ -24,7 +24,7 @@ for dir in . employee shared_objects timsort; do popd done -echo "Checking that all the hybrid examples build on Morello..." +echo "Checking that all the Morello hybrid examples build..." platform='morello-hybrid' for dir in hybrid hybrid/ddc_compartment_switching syscall-restrict; do pushd "$dir" @@ -33,6 +33,15 @@ for dir in hybrid hybrid/ddc_compartment_switching syscall-restrict; do popd done +echo "Checking that all the Morello purecap examples build..." +platform='morello-purecap' +for dir in morello-c64-secure; do + pushd "$dir" + make -f Makefile.$platform clean + make -f Makefile.$platform all + popd +done + export SSHPORT=10021 echo "Running tests for 'riscv64-purecap' using QEMU." From eed4d19bdd235fc401e5cd85158652f94fd1ba1c Mon Sep 17 00:00:00 2001 From: Jacob Bramley Date: Fri, 19 Apr 2024 18:41:17 +0100 Subject: [PATCH 3/8] fixup! C64-Secure ABI demo. Consistently use c10 as a scratch in the 'is_in_A' helper. Also, briefly document how they work. --- morello-c64-secure/impls.s | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/morello-c64-secure/impls.s b/morello-c64-secure/impls.s index d192c10..ad19fe8 100644 --- a/morello-c64-secure/impls.s +++ b/morello-c64-secure/impls.s @@ -197,12 +197,16 @@ Z_end: // Helpers for mapping a code address to a given demo function. This is // how we identify which ABI was used. + // + // Each of these take the address to query in c0. That, and the + // per-function bounds, are passed in the tail-call to the `is_in_range` + // helper. .global is_in_A .type is_in_A, %function is_in_A: - adr c1, A - gcvalue x1, c1 + adr c10, A + gcvalue x1, c10 add x2, x1, #(A_end-A) b is_in_range .size is_in_A, .-is_in_A From d9b2fb6b8ce480373eb0df72a4354bb45a894c44 Mon Sep 17 00:00:00 2001 From: Jacob Bramley Date: Fri, 19 Apr 2024 18:42:39 +0100 Subject: [PATCH 4/8] fixup! C64-Secure ABI demo. Replace `cheri/cheric.h` with `cheriintrin.h`. --- morello-c64-secure/c64-secure.c | 33 +++++++++++++++++---------------- 1 file changed, 17 insertions(+), 16 deletions(-) diff --git a/morello-c64-secure/c64-secure.c b/morello-c64-secure/c64-secure.c index 5d8429a..c4158d8 100644 --- a/morello-c64-secure/c64-secure.c +++ b/morello-c64-secure/c64-secure.c @@ -7,7 +7,8 @@ // the backtrace). #include -#include +#include +#include #include #include #include @@ -20,10 +21,10 @@ void C(uint32_t locals); void Z(uint32_t locals); typedef void (*AbiHelperFn)(uint32_t); -int is_in_A(uintptr_t pcc); -int is_in_B(uintptr_t pcc); -int is_in_C(uintptr_t pcc); -int is_in_Z(uintptr_t pcc); +bool is_in_A(uintptr_t pcc); +bool is_in_B(uintptr_t pcc); +bool is_in_C(uintptr_t pcc); +bool is_in_Z(uintptr_t pcc); enum ABI { AbiA, @@ -120,7 +121,7 @@ int main(int argc, char *argv[]) return 0; } -int is_in_range(size_t address, size_t start, size_t end) { +bool is_in_range(size_t address, size_t start, size_t end) { return (address >= start) && (address < end); } @@ -189,8 +190,8 @@ int backtrace(struct Context frame, struct Context const* current) { const char* frame_slot_names[3] = { "UNKNOWN", "UNKNOWN", "UNKNOWN" }; size_t frame_record_slots; struct Context caller; - size_t cfp_len = cheri_getlen(frame.cfp); - ptraddr_t csp_limit = cheri_getbase(frame.csp) + cheri_getlen(frame.csp); + size_t cfp_len = cheri_length_get(frame.cfp); + ptraddr_t csp_limit = cheri_base_get(frame.csp) + cheri_length_get(frame.csp); switch (abi) { case AbiUnknown: // Probably C's `main`. Stop looking for more frames. @@ -199,7 +200,7 @@ int backtrace(struct Context frame, struct Context const* current) { case AbiA: case AbiB: // C64-Secure frame - assert(cheri_getoffset(frame.cfp) == 0); + assert(cheri_offset_get(frame.cfp) == 0); frame_record_slots = 3; caller.cfp = frame_record[0]; caller.csp = frame_record[1]; @@ -212,9 +213,9 @@ int backtrace(struct Context frame, struct Context const* current) { // C64-Secure v2, with precisely-restricted csp. frame_record_slots = 2; ptraddr_t caller_sp = - cheri_getaddress(frame_record[0] + frame_record_slots); - caller.cfp = cheri_setaddress(frame.cfp, frame_record[0]); - caller.csp = cheri_setaddress(frame_record[0], caller_sp); + cheri_address_get(frame_record[0] + frame_record_slots); + caller.cfp = cheri_address_set(frame.cfp, frame_record[0]); + caller.csp = cheri_address_set(frame_record[0], caller_sp); caller.pcc = frame_record[1]; frame_slot_names[0] = "(cfp x csp)"; frame_slot_names[1] = "pcc"; @@ -249,14 +250,14 @@ int backtrace(struct Context frame, struct Context const* current) { Slot cfp_max_slot = cfp_slot + frame_record_slots - 1; size_t cur_csp = (size_t)(current->csp); - size_t cur_csp_base = (size_t)(cheri_getbase(current->csp)); - size_t cur_csp_len = (size_t)(cheri_getlen(current->csp)); + size_t cur_csp_base = (size_t)(cheri_base_get(current->csp)); + size_t cur_csp_len = (size_t)(cheri_length_get(current->csp)); Slot cur_csp_min_slot = cur_csp_base / slot_size; Slot cur_csp_slot = (Slot)(current->csp) / slot_size; Slot cur_csp_max_slot = (Slot)(cur_csp_base + cur_csp_len - 1) / slot_size; - size_t cur_cfp_base = (size_t)(cheri_getbase(current->cfp)); - size_t cur_cfp_len = (size_t)(cheri_getlen(current->cfp)); + size_t cur_cfp_base = (size_t)(cheri_base_get(current->cfp)); + size_t cur_cfp_len = (size_t)(cheri_length_get(current->cfp)); Slot cur_cfp_min_slot = cur_cfp_base / slot_size; Slot cur_cfp_slot = (Slot)(current->cfp) / slot_size; Slot cur_cfp_max_slot = (Slot)(cur_cfp_base + cur_cfp_len - 1) / slot_size; From df3d7dc3914cec798a1c47da5732547bc734a337 Mon Sep 17 00:00:00 2001 From: Jacob Bramley Date: Fri, 19 Apr 2024 18:44:39 +0100 Subject: [PATCH 5/8] fixup! C64-Secure ABI demo. Run clang-format. --- morello-c64-secure/c64-secure.c | 751 ++++++++++++++++++-------------- 1 file changed, 416 insertions(+), 335 deletions(-) diff --git a/morello-c64-secure/c64-secure.c b/morello-c64-secure/c64-secure.c index c4158d8..4420f2d 100644 --- a/morello-c64-secure/c64-secure.c +++ b/morello-c64-secure/c64-secure.c @@ -8,11 +8,11 @@ #include #include +#include #include -#include #include +#include #include -#include #include void A(uint32_t locals); @@ -26,325 +26,397 @@ bool is_in_B(uintptr_t pcc); bool is_in_C(uintptr_t pcc); bool is_in_Z(uintptr_t pcc); -enum ABI { - AbiA, - AbiB, - AbiC, - AbiZ, - AbiUnknown, +enum ABI +{ + AbiA, + AbiB, + AbiC, + AbiZ, + AbiUnknown, }; -const char* abi_label(enum ABI abi) { - switch (abi) { - case AbiA: return "A"; - case AbiB: return "B"; - case AbiC: return "C"; - case AbiZ: return "Z"; - case AbiUnknown: return NULL; - } +const char *abi_label(enum ABI abi) +{ + switch (abi) + { + case AbiA: + return "A"; + case AbiB: + return "B"; + case AbiC: + return "C"; + case AbiZ: + return "Z"; + case AbiUnknown: + return NULL; + } } -enum ABI abi_from_label(const char* label) { - if ((strcmp(label, "A") == 0) || (strcmp(label, "a") == 0)) { - return AbiA; - } - if ((strcmp(label, "B") == 0) || (strcmp(label, "b") == 0)) { - return AbiB; - } - if ((strcmp(label, "C") == 0) || (strcmp(label, "c") == 0)) { - return AbiC; - } - if ((strcmp(label, "Z") == 0) || (strcmp(label, "z") == 0)) { - return AbiZ; - } - return AbiUnknown; +enum ABI abi_from_label(const char *label) +{ + if ((strcmp(label, "A") == 0) || (strcmp(label, "a") == 0)) + { + return AbiA; + } + if ((strcmp(label, "B") == 0) || (strcmp(label, "b") == 0)) + { + return AbiB; + } + if ((strcmp(label, "C") == 0) || (strcmp(label, "c") == 0)) + { + return AbiC; + } + if ((strcmp(label, "Z") == 0) || (strcmp(label, "z") == 0)) + { + return AbiZ; + } + return AbiUnknown; } -const char* abi_desc(enum ABI abi) { - switch (abi) { - case AbiA: return "C64-Secure, with naive (insecure) csp bounds restriction."; - case AbiB: return "C64-Secure, with conservative csp bounds restriction."; - case AbiC: return "C64-Secure v2, with precisely-restricted csp (and full cfp)."; - case AbiZ: return "AAPCS64-cap, used by C code."; - case AbiUnknown: return "Unknown"; - } +const char *abi_desc(enum ABI abi) +{ + switch (abi) + { + case AbiA: + return "C64-Secure, with naive (insecure) csp bounds restriction."; + case AbiB: + return "C64-Secure, with conservative csp bounds restriction."; + case AbiC: + return "C64-Secure v2, with precisely-restricted csp (and full cfp)."; + case AbiZ: + return "AAPCS64-cap, used by C code."; + case AbiUnknown: + return "Unknown"; + } } -enum ABI abi_for(size_t pcc) { - if (is_in_A(pcc)) { - return AbiA; - } else if (is_in_B(pcc)) { - return AbiB; - } else if (is_in_C(pcc)) { - return AbiC; - } else if (is_in_Z(pcc)) { - return AbiZ; - } else { - return AbiUnknown; - } +enum ABI abi_for(size_t pcc) +{ + if (is_in_A(pcc)) + { + return AbiA; + } + else if (is_in_B(pcc)) + { + return AbiB; + } + else if (is_in_C(pcc)) + { + return AbiC; + } + else if (is_in_Z(pcc)) + { + return AbiZ; + } + else + { + return AbiUnknown; + } } -AbiHelperFn fn_for(enum ABI abi) { - switch (abi) { - case AbiA: return A; - case AbiB: return B; - case AbiC: return C; - case AbiZ: return Z; - case AbiUnknown: return NULL; - } +AbiHelperFn fn_for(enum ABI abi) +{ + switch (abi) + { + case AbiA: + return A; + case AbiB: + return B; + case AbiC: + return C; + case AbiZ: + return Z; + case AbiUnknown: + return NULL; + } } -bool can_call(enum ABI from, enum ABI to) { - // Note: All ABIs can call C code well enough for `what_next()`, because - // it doesn't need much stack. However, deep calls are likely to fail - // without a trampoline to properly convert ABIs, so we don't permit - // calls into AAPCS64-cap for this demo. - switch (from) { - case AbiA: - case AbiB: - return (to == AbiA) || (to == AbiB); - case AbiC: - return (to == AbiC); - case AbiZ: - // We have to be able to call anything from 'Z' - // (AAPCS64-cap) because it's used by C code. - return (to != AbiUnknown); - case AbiUnknown: - return false; - } +bool can_call(enum ABI from, enum ABI to) +{ + // Note: All ABIs can call C code well enough for `what_next()`, because + // it doesn't need much stack. However, deep calls are likely to fail + // without a trampoline to properly convert ABIs, so we don't permit + // calls into AAPCS64-cap for this demo. + switch (from) + { + case AbiA: + case AbiB: + return (to == AbiA) || (to == AbiB); + case AbiC: + return (to == AbiC); + case AbiZ: + // We have to be able to call anything from 'Z' + // (AAPCS64-cap) because it's used by C code. + return (to != AbiUnknown); + case AbiUnknown: + return false; + } } int main(int argc, char *argv[]) { - printf("C64-Secure Demo\n"); - Z(0); - return 0; + printf("C64-Secure Demo\n"); + Z(0); + return 0; } -bool is_in_range(size_t address, size_t start, size_t end) { - return (address >= start) && (address < end); +bool is_in_range(size_t address, size_t start, size_t end) +{ + return (address >= start) && (address < end); } -void clear() { - printf("\033[2J\033[H"); +void clear() +{ + printf("\033[2J\033[H"); } -struct Context { - uintptr_t cfp; - uintptr_t csp; - uintptr_t pcc; +struct Context +{ + uintptr_t cfp; + uintptr_t csp; + uintptr_t pcc; }; typedef size_t Slot; // The left brackets show the stack frames, and mark the division between the // locals and the frame records. -const char* lbracket_for(Slot slot, Slot csp_slot, Slot cfp_slot, Slot cfp_max_slot) { - if (slot == cfp_max_slot) { - return "╭"; - } else if (slot == csp_slot) { - return "╰"; - } else if (slot == cfp_slot) { - return "├"; - } else if ((slot > csp_slot) && (slot < cfp_max_slot)) { - return "│"; - } else { - return " "; - } +const char *lbracket_for(Slot slot, Slot csp_slot, Slot cfp_slot, Slot cfp_max_slot) +{ + if (slot == cfp_max_slot) + { + return "╭"; + } + else if (slot == csp_slot) + { + return "╰"; + } + else if (slot == cfp_slot) + { + return "├"; + } + else if ((slot > csp_slot) && (slot < cfp_max_slot)) + { + return "│"; + } + else + { + return " "; + } } // The right brackets show current (most recent) capabilities and how they // relate to the stack. For all current ABIs, we draw csp and cfp. -const char* rbracket_for(Slot slot, Slot start_slot, Slot value_slot, Slot max_slot) { - bool is_value = slot == value_slot; - if (slot == start_slot) { - return is_value ? "◀╯" : " ╯"; - } else if (slot == max_slot) { - return is_value ? "◀╮" : " ╮"; - } else if ((slot > start_slot) && (slot < max_slot)) { - return is_value ? "◀┤" : " │"; - } else { - return is_value ? "◀ " : " "; - } +const char *rbracket_for(Slot slot, Slot start_slot, Slot value_slot, Slot max_slot) +{ + bool is_value = slot == value_slot; + if (slot == start_slot) + { + return is_value ? "◀╯" : " ╯"; + } + else if (slot == max_slot) + { + return is_value ? "◀╮" : " ╮"; + } + else if ((slot > start_slot) && (slot < max_slot)) + { + return is_value ? "◀┤" : " │"; + } + else + { + return is_value ? "◀ " : " "; + } } // Draw a backtrace of all demo functions, stopping when the backtrace one of // the functions that we know about. In practice, that's whatever C code first // called `Z(0)` or similar. -int backtrace(struct Context frame, struct Context const* current) { - enum ABI abi = abi_for(frame.pcc); - if (abi == AbiUnknown) { - // Probably C code. Stop looking for more frames. - printf(" 0: ...\n"); - return 0; - } - - // We're going to display one slot per row, where (for simplicity) one - // slot is the size of a single capability. - const size_t slot_size_log2 = 4; - const size_t slot_size = 1 << slot_size_log2; - assert(slot_size == sizeof(uintptr_t)); // We require purecap. - - // The frame record starts at cfp for all PCS variants. - const uintptr_t* frame_record = (const uintptr_t*)(frame.cfp); - const char* frame_slot_names[3] = { "UNKNOWN", "UNKNOWN", "UNKNOWN" }; - size_t frame_record_slots; - struct Context caller; - size_t cfp_len = cheri_length_get(frame.cfp); - ptraddr_t csp_limit = cheri_base_get(frame.csp) + cheri_length_get(frame.csp); - switch (abi) { - case AbiUnknown: - // Probably C's `main`. Stop looking for more frames. - printf(" 0: ...\n"); - return 0; - case AbiA: - case AbiB: - // C64-Secure frame - assert(cheri_offset_get(frame.cfp) == 0); - frame_record_slots = 3; - caller.cfp = frame_record[0]; - caller.csp = frame_record[1]; - caller.pcc = frame_record[2]; - frame_slot_names[0] = "cfp"; - frame_slot_names[1] = "csp"; - frame_slot_names[2] = "pcc"; - break; - case AbiC: - // C64-Secure v2, with precisely-restricted csp. - frame_record_slots = 2; - ptraddr_t caller_sp = - cheri_address_get(frame_record[0] + frame_record_slots); - caller.cfp = cheri_address_set(frame.cfp, frame_record[0]); - caller.csp = cheri_address_set(frame_record[0], caller_sp); - caller.pcc = frame_record[1]; - frame_slot_names[0] = "(cfp x csp)"; - frame_slot_names[1] = "pcc"; - break; - case AbiZ: - // AAPCS64-cap frame - frame_record_slots = 2; - caller.cfp = frame_record[0]; - caller.csp = frame_record[0] + frame_record_slots; - caller.pcc = frame_record[1]; - frame_slot_names[0] = "cfp"; - frame_slot_names[1] = "pcc"; - break; - } - - // Recurse before printing this frame, so we print oldest frames first. - int frame_num = backtrace(caller, current) + 1; - - // ---- Print the frame record. ---- - // - // Do all arithmetic (etc) on 16-byte slots, to keep things simple. The - // stack has to be 16-byte aligned at function boundaries so there - // should be no corner cases here (at least with this demo). - // - // This code is simplified by the assumption that all test functions - // emit their frame record first, immediately below their caller's stack - // area. This is a common arrangement, sufficient for demonstration, but - // not strictly required. - - Slot csp_slot = (Slot)(frame.csp) / slot_size; - Slot cfp_slot = (Slot)(frame.cfp) / slot_size; - Slot cfp_max_slot = cfp_slot + frame_record_slots - 1; - - size_t cur_csp = (size_t)(current->csp); - size_t cur_csp_base = (size_t)(cheri_base_get(current->csp)); - size_t cur_csp_len = (size_t)(cheri_length_get(current->csp)); - Slot cur_csp_min_slot = cur_csp_base / slot_size; - Slot cur_csp_slot = (Slot)(current->csp) / slot_size; - Slot cur_csp_max_slot = (Slot)(cur_csp_base + cur_csp_len - 1) / slot_size; - - size_t cur_cfp_base = (size_t)(cheri_base_get(current->cfp)); - size_t cur_cfp_len = (size_t)(cheri_length_get(current->cfp)); - Slot cur_cfp_min_slot = cur_cfp_base / slot_size; - Slot cur_cfp_slot = (Slot)(current->cfp) / slot_size; - Slot cur_cfp_max_slot = (Slot)(cur_cfp_base + cur_cfp_len - 1) / slot_size; - - // Skip over locals, except some at the start and end of the range. - const size_t print_locals = 9; - size_t num_locals = cfp_slot - csp_slot; - size_t skipped_locals_base = 0; - size_t skipped_locals_limit = 0; - if (num_locals > print_locals) { - // Skip an extra line to allow for "...". - size_t skip = num_locals - print_locals + 1; - skipped_locals_base = csp_slot + (print_locals / 2); - skipped_locals_limit = skipped_locals_base + skip; - } - - for (Slot slot = cfp_max_slot; slot >= csp_slot; slot--) { - size_t addr = slot * slot_size; - char buf[128]; // For two-step formatting. - - if ((slot >= skipped_locals_base) && (slot < skipped_locals_limit)) { - slot = skipped_locals_base; - } - - // Column 1: Function names and ownership brackets. - const char* lbracket = - lbracket_for(slot, csp_slot, cfp_slot, cfp_max_slot); - if (slot == (cfp_max_slot - (frame_record_slots / 2))) { - printf("%3d: %5s %s", frame_num, abi_label(abi), lbracket); - } else { - printf(" %s", lbracket); - } - - // Column 2: Stack slot contents. - printf(" %#18zx:", addr); - if (slot >= cfp_slot) { - // An entry in the frame record. - size_t index = slot - cfp_slot; - assert(index < frame_record_slots); - snprintf(buf, sizeof(buf), "%s'%d", frame_slot_names[index], frame_num - 1); - } else if ((slot >= skipped_locals_base) && (slot < skipped_locals_limit)) { - // Skipped locals (below the frame record). - snprintf(buf, sizeof(buf), "..."); - } else { - // Padding and locals (below the frame record). - snprintf(buf, sizeof(buf), "local[%zu]", slot - csp_slot); - } - printf(" %-14s", buf); - - // Column 3: Current csp/cfp ranges. - printf(" %s %s", - rbracket_for(slot, cur_csp_min_slot, cur_csp_slot, cur_csp_max_slot), - (slot == cur_csp_slot) ? "csp" : " "); - printf(" %s %s", - rbracket_for(slot, cur_cfp_min_slot, cur_cfp_slot, cur_cfp_max_slot), - (slot == cur_cfp_slot) ? "cfp" : " "); - - printf("\n"); - } - - return frame_num; +int backtrace(struct Context frame, struct Context const *current) +{ + enum ABI abi = abi_for(frame.pcc); + if (abi == AbiUnknown) + { + // Probably C code. Stop looking for more frames. + printf(" 0: ...\n"); + return 0; + } + + // We're going to display one slot per row, where (for simplicity) one + // slot is the size of a single capability. + const size_t slot_size_log2 = 4; + const size_t slot_size = 1 << slot_size_log2; + assert(slot_size == sizeof(uintptr_t)); // We require purecap. + + // The frame record starts at cfp for all PCS variants. + const uintptr_t *frame_record = (const uintptr_t *) (frame.cfp); + const char *frame_slot_names[3] = {"UNKNOWN", "UNKNOWN", "UNKNOWN"}; + size_t frame_record_slots; + struct Context caller; + size_t cfp_len = cheri_length_get(frame.cfp); + ptraddr_t csp_limit = cheri_base_get(frame.csp) + cheri_length_get(frame.csp); + switch (abi) + { + case AbiUnknown: + // Probably C's `main`. Stop looking for more frames. + printf(" 0: ...\n"); + return 0; + case AbiA: + case AbiB: + // C64-Secure frame + assert(cheri_offset_get(frame.cfp) == 0); + frame_record_slots = 3; + caller.cfp = frame_record[0]; + caller.csp = frame_record[1]; + caller.pcc = frame_record[2]; + frame_slot_names[0] = "cfp"; + frame_slot_names[1] = "csp"; + frame_slot_names[2] = "pcc"; + break; + case AbiC: + // C64-Secure v2, with precisely-restricted csp. + frame_record_slots = 2; + ptraddr_t caller_sp = cheri_address_get(frame_record[0] + frame_record_slots); + caller.cfp = cheri_address_set(frame.cfp, frame_record[0]); + caller.csp = cheri_address_set(frame_record[0], caller_sp); + caller.pcc = frame_record[1]; + frame_slot_names[0] = "(cfp x csp)"; + frame_slot_names[1] = "pcc"; + break; + case AbiZ: + // AAPCS64-cap frame + frame_record_slots = 2; + caller.cfp = frame_record[0]; + caller.csp = frame_record[0] + frame_record_slots; + caller.pcc = frame_record[1]; + frame_slot_names[0] = "cfp"; + frame_slot_names[1] = "pcc"; + break; + } + + // Recurse before printing this frame, so we print oldest frames first. + int frame_num = backtrace(caller, current) + 1; + + // ---- Print the frame record. ---- + // + // Do all arithmetic (etc) on 16-byte slots, to keep things simple. The + // stack has to be 16-byte aligned at function boundaries so there + // should be no corner cases here (at least with this demo). + // + // This code is simplified by the assumption that all test functions + // emit their frame record first, immediately below their caller's stack + // area. This is a common arrangement, sufficient for demonstration, but + // not strictly required. + + Slot csp_slot = (Slot) (frame.csp) / slot_size; + Slot cfp_slot = (Slot) (frame.cfp) / slot_size; + Slot cfp_max_slot = cfp_slot + frame_record_slots - 1; + + size_t cur_csp = (size_t) (current->csp); + size_t cur_csp_base = (size_t) (cheri_base_get(current->csp)); + size_t cur_csp_len = (size_t) (cheri_length_get(current->csp)); + Slot cur_csp_min_slot = cur_csp_base / slot_size; + Slot cur_csp_slot = (Slot) (current->csp) / slot_size; + Slot cur_csp_max_slot = (Slot) (cur_csp_base + cur_csp_len - 1) / slot_size; + + size_t cur_cfp_base = (size_t) (cheri_base_get(current->cfp)); + size_t cur_cfp_len = (size_t) (cheri_length_get(current->cfp)); + Slot cur_cfp_min_slot = cur_cfp_base / slot_size; + Slot cur_cfp_slot = (Slot) (current->cfp) / slot_size; + Slot cur_cfp_max_slot = (Slot) (cur_cfp_base + cur_cfp_len - 1) / slot_size; + + // Skip over locals, except some at the start and end of the range. + const size_t print_locals = 9; + size_t num_locals = cfp_slot - csp_slot; + size_t skipped_locals_base = 0; + size_t skipped_locals_limit = 0; + if (num_locals > print_locals) + { + // Skip an extra line to allow for "...". + size_t skip = num_locals - print_locals + 1; + skipped_locals_base = csp_slot + (print_locals / 2); + skipped_locals_limit = skipped_locals_base + skip; + } + + for (Slot slot = cfp_max_slot; slot >= csp_slot; slot--) + { + size_t addr = slot * slot_size; + char buf[128]; // For two-step formatting. + + if ((slot >= skipped_locals_base) && (slot < skipped_locals_limit)) + { + slot = skipped_locals_base; + } + + // Column 1: Function names and ownership brackets. + const char *lbracket = lbracket_for(slot, csp_slot, cfp_slot, cfp_max_slot); + if (slot == (cfp_max_slot - (frame_record_slots / 2))) + { + printf("%3d: %5s %s", frame_num, abi_label(abi), lbracket); + } + else + { + printf(" %s", lbracket); + } + + // Column 2: Stack slot contents. + printf(" %#18zx:", addr); + if (slot >= cfp_slot) + { + // An entry in the frame record. + size_t index = slot - cfp_slot; + assert(index < frame_record_slots); + snprintf(buf, sizeof(buf), "%s'%d", frame_slot_names[index], frame_num - 1); + } + else if ((slot >= skipped_locals_base) && (slot < skipped_locals_limit)) + { + // Skipped locals (below the frame record). + snprintf(buf, sizeof(buf), "..."); + } + else + { + // Padding and locals (below the frame record). + snprintf(buf, sizeof(buf), "local[%zu]", slot - csp_slot); + } + printf(" %-14s", buf); + + // Column 3: Current csp/cfp ranges. + printf(" %s %s", rbracket_for(slot, cur_csp_min_slot, cur_csp_slot, cur_csp_max_slot), + (slot == cur_csp_slot) ? "csp" : " "); + printf(" %s %s", rbracket_for(slot, cur_cfp_min_slot, cur_cfp_slot, cur_cfp_max_slot), + (slot == cur_cfp_slot) ? "cfp" : " "); + + printf("\n"); + } + + return frame_num; } -struct Next { - uint32_t locals; - uintptr_t fn; +struct Next +{ + uint32_t locals; + uintptr_t fn; }; -struct Next next(uintptr_t what, size_t locals) { - struct Next result; - result.locals = locals; - result.fn = what; - return result; +struct Next next(uintptr_t what, size_t locals) +{ + struct Next result; + result.locals = locals; + result.fn = what; + return result; } // If we can call a demo function that uses the specified ABI, then print it as // an option for the user (and return true). -bool maybe_print_abi_desc(enum ABI from, enum ABI to) { - if (can_call(from, to)) { - char const * label = abi_label(to); - char const * desc = abi_desc(to); - printf(" %s: %s\n", - (label ? label : "?"), - (desc ? desc : "?")); - return true; - } - return false; +bool maybe_print_abi_desc(enum ABI from, enum ABI to) +{ + if (can_call(from, to)) + { + char const *label = abi_label(to); + char const *desc = abi_desc(to); + printf(" %s: %s\n", (label ? label : "?"), (desc ? desc : "?")); + return true; + } + return false; } // From a given context, ask the user what to call next. @@ -356,63 +428,72 @@ bool maybe_print_abi_desc(enum ABI from, enum ABI to) { // `can_call()` says that the example ABIs can't call 'Z' (C code), but actually // they'll all work well enough to call this helper. TODO: This might fail if // the user requests a large number of locals. -struct Next what_next(uintptr_t cfp, uintptr_t csp, size_t pc) { - clear(); - printf("C64-Secure Demo\n"); - struct Context ctx = { cfp, csp, pc }; - backtrace(ctx, &ctx); - - enum ABI from = abi_for(pc); - - printf("\n"); - printf("Enter a decimal number to allocate at least that many locals\n"); - printf("in the next called function, then a single character to choose\n"); - printf("the function to call:\n"); - maybe_print_abi_desc(from, AbiA); - maybe_print_abi_desc(from, AbiB); - maybe_print_abi_desc(from, AbiC); - maybe_print_abi_desc(from, AbiZ); - printf(" .: None (return).\n"); - - // NULL means "return". - const struct Next ret = { 0, (uintptr_t)0 }; - size_t locals = 0; - while (1) { - int c = getchar(); - - if (c == EOF) { - exit(0); - } - - if (c == '.') { - return ret; - } - - if (isspace(c)) { - continue; - } - - char str[] = { c, '\0' }; - enum ABI abi = abi_from_label(str); - if (can_call(from, abi)) { - return next((uintptr_t)fn_for(abi), locals); - } - - bool bad_c = true; - - // Look for a digit. - const char digits[] = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9'}; - for (int i = 0; i < (sizeof(digits)/sizeof(digits[0])); i++) { - if (digits[i] == c) { - locals = locals * 10 + i; - bad_c = false; - break; - } - } - - if (bad_c) { - printf("Calling '%c' from '%s' is unimplemented.\n", toupper(c), abi_label(from)); - continue; - } - } +struct Next what_next(uintptr_t cfp, uintptr_t csp, size_t pc) +{ + clear(); + printf("C64-Secure Demo\n"); + struct Context ctx = {cfp, csp, pc}; + backtrace(ctx, &ctx); + + enum ABI from = abi_for(pc); + + printf("\n"); + printf("Enter a decimal number to allocate at least that many locals\n"); + printf("in the next called function, then a single character to choose\n"); + printf("the function to call:\n"); + maybe_print_abi_desc(from, AbiA); + maybe_print_abi_desc(from, AbiB); + maybe_print_abi_desc(from, AbiC); + maybe_print_abi_desc(from, AbiZ); + printf(" .: None (return).\n"); + + // NULL means "return". + const struct Next ret = {0, (uintptr_t) 0}; + size_t locals = 0; + while (1) + { + int c = getchar(); + + if (c == EOF) + { + exit(0); + } + + if (c == '.') + { + return ret; + } + + if (isspace(c)) + { + continue; + } + + char str[] = {c, '\0'}; + enum ABI abi = abi_from_label(str); + if (can_call(from, abi)) + { + return next((uintptr_t) fn_for(abi), locals); + } + + bool bad_c = true; + + // Look for a digit. + const char digits[] = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9'}; + for (int i = 0; i < (sizeof(digits) / sizeof(digits[0])); i++) + { + if (digits[i] == c) + { + locals = locals * 10 + i; + bad_c = false; + break; + } + } + + if (bad_c) + { + printf("Calling '%c' from '%s' is unimplemented.\n", toupper(c), abi_label(from)); + continue; + } + } } From 73485f1e63609c6be691b323fbb89dfb5b01b056 Mon Sep 17 00:00:00 2001 From: Jacob Bramley Date: Wed, 8 May 2024 11:56:11 +0100 Subject: [PATCH 6/8] fixup! C64-Secure ABI demo. Reorganise .buildbot.sh build stage by target. --- .buildbot.sh | 26 +++++++++++--------------- 1 file changed, 11 insertions(+), 15 deletions(-) diff --git a/.buildbot.sh b/.buildbot.sh index 3749619..8e2b844 100755 --- a/.buildbot.sh +++ b/.buildbot.sh @@ -12,33 +12,29 @@ echo "Checking clang-format..." CLANG_FORMAT=$CHERI_DIR/morello-sdk/bin/clang-format find . -iname "*.c" -o -iname "*.h" -o -iname "*.cpp" -o -iname "*.hpp" | xargs "$CLANG_FORMAT" --dry-run -Werror -echo "Checking that all examples build for all platforms..." -PLATFORMS='riscv64-purecap morello-purecap' +echo "Checking that all riscv64-purecap examples build..." # TODO: Add "example_allocators". for dir in . employee shared_objects timsort; do pushd "$dir" - for platform in $PLATFORMS; do - make -f Makefile.$platform clean - make -f Makefile.$platform all - done + make -f Makefile.riscv64-purecap clean + make -f Makefile.riscv64-purecap all popd done -echo "Checking that all the Morello hybrid examples build..." -platform='morello-hybrid' +echo "Checking that all morello-hybrid examples build..." for dir in hybrid hybrid/ddc_compartment_switching syscall-restrict; do pushd "$dir" - make -f Makefile.$platform clean - make -f Makefile.$platform all + make -f Makefile.morello-hybrid clean + make -f Makefile.morello-hybrid all popd done -echo "Checking that all the Morello purecap examples build..." -platform='morello-purecap' -for dir in morello-c64-secure; do +echo "Checking that all morello-purecap examples build..." +# TODO: Add "example_allocators". +for dir in . employee shared_objects timsort morello-c64-secure; do pushd "$dir" - make -f Makefile.$platform clean - make -f Makefile.$platform all + make -f Makefile.morello-purecap clean + make -f Makefile.morello-purecap all popd done From 512da360225968b135d9abdb3986bf77ec7dcbd6 Mon Sep 17 00:00:00 2001 From: Jacob Bramley Date: Wed, 8 May 2024 15:41:07 +0100 Subject: [PATCH 7/8] fixup! C64-Secure ABI demo. Reword the TODO asking for a proof. --- morello-c64-secure/impls.s | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/morello-c64-secure/impls.s b/morello-c64-secure/impls.s index ad19fe8..131e565 100644 --- a/morello-c64-secure/impls.s +++ b/morello-c64-secure/impls.s @@ -81,8 +81,9 @@ B: rrmask x12, x11 and x12, x11, x12 // We assume that since the original stack must have been larger than - // this, its base is suitably aligned. - // TODO: Prove that this is sound. + // this, its base is suitably aligned. Whilst this is intuitively true, + // and sufficient for demonstration purpose, we have not proven this + // property. scbndse c10, c10, x12 sub x12, x12, w0, uxtw #4 // Allocate locals. scoff csp, c10, x12 From 437b84fa478c659d58dd3ef436f2bc248afc5d10 Mon Sep 17 00:00:00 2001 From: Jacob Bramley Date: Wed, 8 May 2024 15:57:49 +0100 Subject: [PATCH 8/8] fixup! C64-Secure ABI demo. Clarify `what_next` comment. --- morello-c64-secure/c64-secure.c | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/morello-c64-secure/c64-secure.c b/morello-c64-secure/c64-secure.c index 4420f2d..3301ca8 100644 --- a/morello-c64-secure/c64-secure.c +++ b/morello-c64-secure/c64-secure.c @@ -421,13 +421,20 @@ bool maybe_print_abi_desc(enum ABI from, enum ABI to) // From a given context, ask the user what to call next. // -// This should be called from each demo function in a loop. The demo function -// should either call the resulting function, or return (exiting its loop) if -// it's NULL. +// This is the interactive logic of the example, responsible for presenting +// available options, asking the user what to do next, and then passing that +// information back to the demo itself (A, B, C, ...). The actual ABI logic is +// in the demo functions. // -// `can_call()` says that the example ABIs can't call 'Z' (C code), but actually -// they'll all work well enough to call this helper. TODO: This might fail if -// the user requests a large number of locals. +// `what_next` should be called by each demo function (A, B, C, ...) in a loop. +// Each time it is called, `what_next` will prompt the user, and return another +// demo function to call (with a given number of stacked locals), or NULL if the +// calling demo function should return. +// +// Note: Since this is implemented in C, it uses AAPCS64-cap, which we +// respresent as 'Z'. `can_call()` says that the example ABIs can't call 'Z', +// but in practice they'll all work well enough to call this helper. +// TODO: This might fail if the user requests a large number of locals. struct Next what_next(uintptr_t cfp, uintptr_t csp, size_t pc) { clear();