code wiki / (root) / nx_boundscheck.nx

nx_boundscheck.nx source

↩ module page · 228 lines · 11409 B

1// nx_boundscheck.nx -- LN3 RAW-POINTER PROVENANCE fixture organ (lang.plan rung LN3, watch 2// symbol bck_ptr_provenance on /compare/lang row "Raw-pointer access prevented"). 3// 4// WHAT THIS IS. The runnable witness for the compiler's --ptrprov declared mode: a `let`-bound 5// sys_mmap(<const>) pointer's indexing is bounds-checked like a typed array (nx_parse.nx, the 6// LN3 block). Each phase is selected by argv so a single trap ends exactly one phase and the 7// gate (nx_boundscheck_gate) can assert every outcome independently: 8// 9// nx_boundscheck ok in-range read+write+alias walk -> exit 0 under BOTH modes 10// (the positive control: a deny mode that refuses everything fails here) 11// nx_boundscheck read the 2026-07-08 READ-TO-NUL class (CWE-125): a scan with no length 12// bound walks off a 64-byte allocation. Default build: the over-read 13// SURVIVES on arena/page slack (exit 0 -- reading bytes that belong to 14// a NEIGHBORING allocation, the silent-wrong-answer this rung exists to 15// kill). --ptrprov build: traps exit 71 (NX_TRAP_BOUNDS) at index 64. 16// nx_boundscheck write CWE-787, the corruption primitive: s[10] on a 10-element allocation. 17// Default: lands in slack, exit 0. --ptrprov: traps exit 71. 18// 19// The sibling witness nx_boundscheck_constidx.nx carries the COMPILE-TIME leg (a constant 20// out-of-range index refused before any binary exists). 21// 22// license_tier: ORIGINAL No hw writes (Rule 26). 23import "nx_syscalls.nx" 24 25const BCK_ALLOC: i64 = 64 26const BCK_FILL: i64 = 7 27const BCK_W_ELEMS: i64 = 10 28// The mmap argument must be a SINGLE const/literal: provenance capture keys on VK_CONST_INT, 29// and a `N * 8` argument is a parse-time BINOP value, not a folded constant -- passing it would 30// make this fixture silently unbound (a vacuous fixture, the exact class gv_bite exists to catch). 31const BCK_W_BYTES: i64 = 80 32 33// THE WATCH SYMBOL and the READ-leg fixture in one: the allocation, the terminator-free fill, 34// and the unbounded scan all live in ONE function because provenance is bound where the compiler 35// can SEE the allocation -- a parameter would honestly carry no extent (declared floor). 36func bck_ptr_provenance() -> i64 { 37 let buf: *u8 = sys_mmap(BCK_ALLOC) 38 var i: i64 = 0 39 while i < BCK_ALLOC { buf[i] = BCK_FILL as u8; i = i + 1 } 40 // No terminator was ever written: the producer half of the 2026-07-08 bug. 41 var n: i64 = 0 42 while buf[n] != (0 as u8) { n = n + 1 } 43 return n 44} 45 46// CWE-787 leg: the store lands one element past the allocation's extent. 47func bck_write_overrun() -> i64 { 48 let s: *i64 = sys_mmap(BCK_W_BYTES) as *i64 49 var i: i64 = 0 50 while i <= BCK_W_ELEMS { s[i] = i; i = i + 1 } 51 return 0 52} 53 54// Positive control: in-range reads and writes, plus the alias inherit (`let q = w`) -- must 55// complete under BOTH modes or the checker is refusing valid programs. 56func bck_inrange_control() -> i64 { 57 let buf: *u8 = sys_mmap(BCK_ALLOC) 58 var i: i64 = 0 59 while i < BCK_ALLOC { buf[i] = 1 as u8; i = i + 1 } 60 var s: i64 = 0 61 var j: i64 = 0 62 while j < BCK_ALLOC { s = s + (buf[j] as i64); j = j + 1 } 63 let w: *i64 = sys_mmap(BCK_W_BYTES) as *i64 64 w[BCK_W_ELEMS - 1] = s 65 let q: *i64 = w 66 if q[BCK_W_ELEMS - 1] == BCK_ALLOC { return 0 } 67 return 1 68} 69 70// ===================== LN7: SOUND BOUNDS-CHECK ELISION (bck_elide_dominated) ================= 71// The RUNNABLE WITNESSES for the elision rung. The capability itself is the optimizer pass in 72// runtime/nx_bck_elide.nx; these are the programs whose BEHAVIOUR must be identical with and 73// without --bckelide, and whose ASSEMBLY must differ only where an elision is provably sound. 74// 75// Two of the three legs are OUT-OF-BOUNDS ON PURPOSE and MUST STILL TRAP. They are the point: 76// an unsound elision does not crash, it silently reads or writes memory it had no right to, so 77// the only tooth that can catch it is one where a check the pass must NOT remove is the sole 78// thing standing between the program and a real overrun. 79// 80// BCK_E_ELEMS must equal the literal in the array types below. Nothing in the language lets 81// this file assert that, so the GATE pins it from both sides instead: the in-range leg exits 0 82// (which fails if the const is too LARGE) and the two trap legs exit NX_TRAP_BOUNDS (which 83// fails if it is too SMALL). 84const BCK_E_ELEMS: i64 = 64 85const BCK_E_SMALL: i64 = 8 86const BCK_E_PERF_ITERS: i64 = 10000000 87 88// WITNESS A -- a genuinely DOMINATED re-check, the case the rung exists to speed up. 89// `let i` is SSA, so all three accesses name ONE index value id, and the second and third 90// checks sit in blocks dominated by the first check's ok block. Under --bckelide the two 91// re-checks become unconditional; the FIRST is always kept. The returned value must be 92// identical either way -- that is the entire correctness claim, and main() checks it against a 93// closed form rather than against a remembered number. 94func bck_elide_dominated_witness(iters: i64) -> i64 { 95 var a: [64]i64 96 var f: i64 = 0 97 while f < BCK_E_ELEMS { a[f] = f; f = f + 1 } 98 var acc: i64 = 0 99 var k: i64 = 0 100 while k < iters { 101 let i: i64 = k % BCK_E_ELEMS 102 a[i] = a[i] + 1 // read-check, then write-check, on ONE index value id 103 acc = acc + a[i] // a third use, also dominated by the first check 104 k = k + 1 105 } 106 return acc 107} 108 109// WITNESS A2 -- the SAME access pattern as A with the DIVISION taken out of the loop. 110// A's index is `k % BCK_E_ELEMS`, and an integer remainder costs tens of cycles while a bounds 111// check costs a compare and a predictable branch -- so A's loop is dominated by an operation 112// this rung does not touch, and its wall-clock ratio understates the check's share by a lot. 113// Nesting the loops reaches exactly the same elements in the same order with the same number of 114// accesses and no divide. Kept BESIDE A rather than replacing it: both numbers are reported, 115// because removing a confound is only honest if the confounded figure stays on the record. 116func bck_elide_perf_witness(reps: i64) -> i64 { 117 var a: [64]i64 118 var f: i64 = 0 119 while f < BCK_E_ELEMS { a[f] = f; f = f + 1 } 120 var acc: i64 = 0 121 var r: i64 = 0 122 while r < reps { 123 var k: i64 = 0 124 while k < BCK_E_ELEMS { 125 let i: i64 = k 126 a[i] = a[i] + 1 127 acc = acc + a[i] 128 k = k + 1 129 } 130 r = r + 1 131 } 132 return acc 133} 134 135// WITNESS B -- a re-check that is NOT dominated, and the sharpest tooth in the rung. 136// The first access sits inside `if i < BCK_E_SMALL`, so the path where that test FAILS reaches 137// the second access having proven nothing. A pass that treats "there is an earlier check with 138// the same key" as sufficient -- without asking whether it DOMINATES -- elides the second check 139// and this function then reads a[128] out of a 64-element array with no diagnostic at all. 140// Called with an out-of-range index it MUST exit NX_TRAP_BOUNDS under every mode. 141func bck_elide_notdominated_witness(n: i64) -> i64 { 142 var a: [64]i64 143 var f: i64 = 0 144 while f < BCK_E_ELEMS { a[f] = f; f = f + 1 } 145 let i: i64 = n 146 var acc: i64 = 0 147 if i < BCK_E_SMALL { acc = acc + a[i] } 148 acc = acc + a[i] 149 return acc 150} 151 152// WITNESS C -- the LENGTH half of the key, which is the other way to build the same hole. 153// Both accesses use ONE index value id and the second IS dominated by the first, so a pass that 154// keyed only on the index would elide it. The arrays have different extents, so an index that 155// is in range for the first is out of range for the second. Called with an index between the 156// two extents it MUST exit NX_TRAP_BOUNDS. 157func bck_elide_keylen_witness(n: i64) -> i64 { 158 var big: [64]i64 159 var small: [8]i64 160 var f: i64 = 0 161 while f < BCK_E_ELEMS { big[f] = f; f = f + 1 } 162 var g: i64 = 0 163 while g < BCK_E_SMALL { small[g] = g; g = g + 1 } 164 let i: i64 = n 165 var acc: i64 = 0 166 acc = acc + big[i] 167 acc = acc + small[i] 168 return acc 169} 170 171func main(argc: i64, argv: *i64) -> i64 { 172 var ph: i64 = 111 // 'o' -- default phase is the safe control 173 if argc >= 2 { 174 let a: *u8 = argv[1] as *u8 175 ph = a[0] as i64 176 } 177 if ph == 114 { // 'r' read 178 let r: i64 = bck_ptr_provenance() 179 // Any n >= the extent proves the scan left the allocation without a trap. The exact 180 // value depends on whatever neighboring memory holds -- which is the point. 181 if r >= BCK_ALLOC { return 0 } 182 return 1 183 } 184 if ph == 119 { // 'w' write 185 return bck_write_overrun() 186 } 187 if ph == 100 { // 'd' LN7 dominated re-check, MUST stay correct 188 // The expected total is a CLOSED FORM, not a remembered number: every element is 189 // visited exactly once (iters == BCK_E_ELEMS, index k % BCK_E_ELEMS), each is 190 // incremented from f to f+1 and then accumulated, so the sum is 1+2+...+BCK_E_ELEMS. 191 // Deriving it here rather than pasting a literal means the tooth still holds if the 192 // extent above ever changes. 193 let got: i64 = bck_elide_dominated_witness(BCK_E_ELEMS) 194 let want: i64 = (BCK_E_ELEMS * (BCK_E_ELEMS + 1)) / 2 195 if got == want { return 0 } 196 return 1 197 } 198 if ph == 110 { // 'n' LN7 NOT-dominated re-check, MUST TRAP 199 // Index deliberately past the extent AND past the guard, so the guarded access is 200 // skipped entirely and the unguarded one is the only thing left to catch it. 201 bck_elide_notdominated_witness(BCK_E_ELEMS * 2) 202 return 2 // reaching here at all means the check was lost 203 } 204 if ph == 112 { // 'p' LN7 h2h COST leg -- timed by the gate 205 // Sized so the loop dominates process startup by ~3 orders of magnitude: the ratio the 206 // gate reports is then a property of the loop body, not of exec+mmap. The ratio is 207 // insensitive to this value once that holds, so it is a scale choice, not a threshold -- 208 // nothing branches on it. 209 let acc: i64 = bck_elide_dominated_witness(BCK_E_PERF_ITERS) 210 // Observing acc is what keeps the loop from being dead-code eliminated; a benchmark whose 211 // result nobody reads measures an empty loop. 212 if acc > 0 { return 0 } 213 return 3 214 } 215 if ph == 113 { // 'q' LN7 h2h COST leg, division-free (witness A2) 216 // Same element count and same access order as 'p', reps chosen so the TOTAL number of 217 // checked accesses is identical -- only the remainder is gone. 218 let acc2: i64 = bck_elide_perf_witness(BCK_E_PERF_ITERS / BCK_E_ELEMS) 219 if acc2 > 0 { return 0 } 220 return 3 221 } 222 if ph == 120 { // 'x' LN7 same index, DIFFERENT extent, MUST TRAP 223 // In range for the 64-element array, out of range for the 8-element one. 224 bck_elide_keylen_witness(BCK_E_ELEMS / 4) 225 return 2 // reaching here at all means the check was lost 226 } 227 return bck_inrange_control() // 'o' / anything else 228}