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}