memcap.nx
buildroot/runtime/memcap.nx
about
memcap.nx -- CHERI-style memory capabilities, pure software.
Pointer-with-bounds-and-permissions, implemented entirely in
software. When Nishi silicon ships with hardware capabilities
(CHERI-RISC-V extension, or our own), the compiler recognises
MemCap<T> and emits single-instruction capability ops; the
software runtime becomes a no-op or is replaced by the hardware
path. Same NishiLang source, silicon-accelerated backend.
This is the 40-year bet: build invariants in software now, keep
the HW/SW interface stable, upgrade silicon when we can fab it.
Distinct from runtime/cap.nx: that file covers ACCESS
capabilities (who can call fs_open), which are coarse-grained
and language-level. This file covers MEMORY capabilities (what
bytes a pointer can touch), which are fine-grained and per-
allocation. Both systems plug into the Nishi silicon story:
access caps become compartment IDs, memory caps become CHERI
pointer tags.
Reference papers / systems:
Watson et al 2015 "CHERI: A Hybrid Capability-System
Architecture for Scalable Software Compartmentalization"
Woodruff et al 2014 "The CHERI capability model"
Nienhuis et al 2020 "Rigorous engineering for hardware security"
Arm Morello -- production CHERI-Armv8 (2022)
RISC-V CHERI extension -- ratified draft (2024)
Layout (32 bytes in software):
+ 0 .. + 7 : address (i64, the actual pointer)
+ 8 .. + 15 : base (i64, lower bound inclusive)
+ 16 .. + 23 : length (i64, bytes from base)
+ 24 .. + 31 : perms + tag (packed i64)
bit 0 : READ (load permitted)
bit 1 : WRITE (store permitted)
bit 2 : EXEC (jump permitted -- future)
bit 3 : CAP_LOAD (can load a capability through)
bit 4 : CAP_STORE (can store a capability through)
bit 5 : SEAL (non-deref, unforgeable handle)
bit 8 : VALID_TAG (mandatory; cleared on raw memcpy)
dependencies 2 imports · 1 importers
imports: syscalls.nxnx_assert.nx
imported by: nx_caps.nx
call flow from main pre-order; caps 40 nodes / depth 6 declared; ↻ = already shown
structs
| 66 | struct MemCap { |
consts
| 52 | const MCAP_READ: i64 = 0x001 |
| 53 | const MCAP_WRITE: i64 = 0x002 |
| 54 | const MCAP_EXEC: i64 = 0x004 |
| 55 | const MCAP_CAP_LOAD: i64 = 0x008 |
| 56 | const MCAP_CAP_STORE: i64 = 0x010 |
| 57 | const MCAP_SEAL: i64 = 0x020 |
| 58 | const MCAP_VALID_TAG: i64 = 0x100 |
| 61 | const MCAP_RW: i64 = 0x103 // READ | WRITE | VALID_TAG |
| 62 | const MCAP_RO: i64 = 0x101 // READ | VALID_TAG |
| 73 | const MEMCAP_BYTES: i64 = 32 |
functions
| 78 | func memcap_new(addr: i64, base: i64, length: i64, perms: i64) -> *MemCap { |
| 90 | func memcap_alloc(size: i64) -> *MemCap { |
| 98 | func memcap_valid(c: *MemCap) -> i64 { |
| 105 | func memcap_can_read(c: *MemCap, offset: i64, n: i64) -> i64 { |
| 115 | func memcap_can_write(c: *MemCap, offset: i64, n: i64) -> i64 { |
| 127 | func memcap_load_i64(c: *MemCap, offset: i64) -> i64 { |
| 135 | func memcap_store_i64(c: *MemCap, offset: i64, val: i64) -> i64 { |
| 144 | func memcap_load_u8(c: *MemCap, offset: i64) -> i64 { |
| 152 | func memcap_store_u8(c: *MemCap, offset: i64, val: i64) -> i64 { |
| 167 | func memcap_subrange(parent: *MemCap, sub_offset: i64, sub_len: i64) -> *MemCap { |
| 181 | func memcap_restrict(c: *MemCap, new_perms: i64) -> *MemCap { |
| 191 | func memcap_seal(c: *MemCap) -> *MemCap { |
| 199 | func main() -> i64 { |