code wiki / (root) / nx_budget_alloc.nx

nx_budget_alloc.nx source

↩ module page · 176 lines · 6988 B

1// nx_budget_alloc.nx -- bits-up sovereign memory-ceiling enforcement. 2// 3// THIS is the Nishi way to make nx_budget's ceiling claim 4// load-bearing. NO Linux cgroups dependency. NO kernel 5// delegation. The substrate IS the enforcement. 6// 7// Replaces direct sys_mmap calls from cell-resident code with 8// nx_budget_mmap, which composes nx_budget_request + sys_mmap: 9// 1. Reserve bytes from the cell's declared budget 10// 2. Only if reservation succeeded, actually call sys_mmap 11// 3. Over-ceiling -> NULL; never reaches sys_mmap 12// 4. sys_mmap failure (rare) -> release the reservation + 13// return NULL 14// 15// This pattern works IDENTICALLY on: 16// - Linux (any version; no cgroups needed) 17// - Windows (when nx_os_windows ships; same code) 18// - macOS (when nx_os_darwin ships) 19// - bare metal (when nx_os_baremetal ships) 20// - WASM 21// because the enforcement is in the substrate, not the host 22// kernel. Per [[feedback-bits-up-exceed-never-match]] we never 23// delegate enforcement to a third-party (host kernel cgroups 24// counts as third-party; the substrate is sovereign). 25// 26// Distinct from nx_cgroup_v2_probe.nx which is INSPECTION-ONLY 27// for diagnostic visibility on Linux hosts. cgroups probe tells 28// the operator "this host CAN delegate to kernel cgroups if you 29// want"; THIS primitive is what the substrate actually USES, and 30// the answer is "the substrate enforces itself, never delegates." 31// 32// Driver cardinal (verbatim from operator 2026-05-20): 33// "make sure we are building bits up nishi not linux or whatever" 34// 35// genealogy_id: cardinal_2026-05-19_cooperative_resource_arbitration + 36// cardinal_2026-05-20_bits_up_nishi_not_linux + 37// nx_budget_v1 38// lineage_id: substrate_budget_alloc_v1 39// 40// nx_capability_manifest: 41// variant_class: budget_enforced_alloc 42// variant_id: budget_alloc_v1_substrate_sovereign 43// requires_isa: [rv32i, rv64imac, x86_64, cortex_m, armv7a, aarch64, avr, wasm32] 44// requires_syscalls: [mmap] 45// requires_ram_min_b: 64 46// tier_floor: NX_TIER_INF_MCU 47// tier_ceiling: NX_TIER_INF_HPC 48// cost_model: 49// flops_per_n: 1.0 // pre-alloc check + sys_mmap call 50// bytes_per_n: 1.0 // bytes allocated pass-through 51// syscalls_per_n: 1.0 // one sys_mmap per allocation 52// adversary_class: THREAT_AI_ADVERSARY 53// 54// nx_safety_envelope: 55// intended_use: "Bits-up sovereign memory-ceiling enforcement; 56// pre-alloc check refuses over-budget allocations 57// before they reach sys_mmap" 58// sil_target: SIL2 59// evidence: [pre_alloc_check, refusal_before_syscall, 60// no_kernel_dependency, host_agnostic] 61// verdict: NOT_YET_EVALUATED 62 63import "nx_syscalls.nx" 64import "nx_budget.nx" 65 66// ===== Verdicts ================================================== 67// nx_budget_mmap returns *u8 (NULL on refusal), so callers check 68// `(ptr as i64) == 0`. These verdict constants are exposed for 69// callers that want to distinguish refusal modes via the parallel 70// "_with_verdict" API below. 71 72const NX_BUDGET_ALLOC_OK: i64 = 0 73const NX_BUDGET_ALLOC_OVER: i64 = 1 74const NX_BUDGET_ALLOC_BAD_INPUT: i64 = 2 75const NX_BUDGET_ALLOC_SYSCALL_FAIL: i64 = 3 76const NX_BUDGET_ALLOC_BAD_KIND: i64 = 4 77const NX_BUDGET_ALLOC_N: i64 = 5 78 79func nx_budget_alloc_verdict_is_valid(v: i64) -> i64 { 80 if v < 0 { return 0 } 81 if v >= NX_BUDGET_ALLOC_N { return 0 } 82 return 1 83} 84 85// ===== Allocation entry ========================================= 86// nx_budget_mmap returns the allocated pointer or NULL. Callers 87// who only need success/failure semantics use this. 88 89func nx_budget_mmap(budget: *NxBudget, kind: nx_int, size: nx_size) -> *u8 { 90 if (budget as i64) == 0 { return (0 as i64) as *u8 } 91 if size <= 0 { return (0 as i64) as *u8 } 92 let rc: nx_int = nx_budget_request(budget, kind, size) 93 if rc != NX_BUDGET_OK { return (0 as i64) as *u8 } 94 let ptr: *u8 = sys_mmap(size as i64) 95 if (ptr as i64) == 0 { 96 // sys_mmap failed even though budget allowed. Roll back 97 // the reservation so used counter stays accurate. 98 nx_budget_release(budget, kind, size) 99 return (0 as i64) as *u8 100 } 101 return ptr 102} 103 104// ===== Verdict-returning variant =============================== 105// Same allocation logic; also writes a verdict code through 106// out_verdict so callers can distinguish OVER (budget) from 107// SYSCALL_FAIL (kernel mmap refused). 108 109func nx_budget_mmap_with_verdict( 110 budget: *NxBudget, kind: nx_int, size: nx_size, 111 out_verdict: *i64 112) -> *u8 { 113 if (out_verdict as i64) == 0 { 114 // Fall back to silent mode if caller didn't pass a verdict slot. 115 return nx_budget_mmap(budget, kind, size) 116 } 117 if (budget as i64) == 0 { 118 *out_verdict = NX_BUDGET_ALLOC_BAD_INPUT 119 return (0 as i64) as *u8 120 } 121 if size <= 0 { 122 *out_verdict = NX_BUDGET_ALLOC_BAD_INPUT 123 return (0 as i64) as *u8 124 } 125 let rc: nx_int = nx_budget_request(budget, kind, size) 126 if rc == NX_BUDGET_ERR_BAD_KIND { 127 *out_verdict = NX_BUDGET_ALLOC_BAD_KIND 128 return (0 as i64) as *u8 129 } 130 if rc == NX_BUDGET_ERR_OVER { 131 *out_verdict = NX_BUDGET_ALLOC_OVER 132 return (0 as i64) as *u8 133 } 134 if rc != NX_BUDGET_OK { 135 *out_verdict = NX_BUDGET_ALLOC_BAD_INPUT 136 return (0 as i64) as *u8 137 } 138 let ptr: *u8 = sys_mmap(size as i64) 139 if (ptr as i64) == 0 { 140 nx_budget_release(budget, kind, size) 141 *out_verdict = NX_BUDGET_ALLOC_SYSCALL_FAIL 142 return (0 as i64) as *u8 143 } 144 *out_verdict = NX_BUDGET_ALLOC_OK 145 return ptr 146} 147 148// ===== Logical release ========================================== 149// The substrate (V1) does not call sys_munmap -- the OS reclaims 150// process memory on exit. But cells that long-lived may want to 151// release budget reservation when a buffer is no longer needed, 152// so subsequent allocations can succeed. This decrements the 153// budget's used counter; the actual memory remains physically 154// resident until process exit. 155// 156// On systems with sys_munmap (Linux + most), a future 157// nx_budget_munmap variant will compose nx_budget_release with 158// sys_munmap. 159 160func nx_budget_release_logical( 161 budget: *NxBudget, kind: nx_int, size: nx_size 162) -> nx_int { 163 if (budget as i64) == 0 { return NX_BUDGET_ALLOC_BAD_INPUT } 164 if size <= 0 { return NX_BUDGET_ALLOC_BAD_INPUT } 165 return nx_budget_release(budget, kind, size) 166} 167 168// ===== Headroom query ========================================== 169// Convenience accessor: how many more bytes can be allocated of 170// this resource before the budget ceiling is hit? Composes 171// nx_budget_remaining for parallel-API ergonomics. 172 173func nx_budget_alloc_remaining(budget: *NxBudget, kind: nx_int) -> nx_size { 174 if (budget as i64) == 0 { return 0 } 175 return nx_budget_remaining(budget, kind) 176}