code wiki / (root) / nx_nxa_secdump_gate.nx

nx_nxa_secdump_gate.nx source

↩ module page · 294 lines · 13643 B

1// nx_nxa_secdump_gate.nx -- THE GATE FOR THE NXA SECTION INSPECTOR. 2// 3// SUBJECT: the nx_nxa_secdump ELF, forked for real against NXA containers this gate BUILDS AT 4// RUNTIME under /tmp/nx_nxa_secdump_gate/. Nothing here reads a production asset, so the gate 5// cannot report on a fixture it shares with a beat (the gate-fixture law), and it cannot be 6// silently re-baselined by someone re-baking ref9d. 7// 8// WHY EACH TOOTH EXISTS. The organ's whole value is that its NEGATIVE answers are trustworthy: 9// a reader that says MATCH on a corrupt section, or OK on a container it could not parse, is 10// worse than no reader at all -- it would launder a broken asset as verified. So every refusal 11// path is bitten with a fixture built to trigger exactly that path, and the good fixture is 12// carried alongside as the positive control, because A GUARD THAT REFUSES EVERYTHING PASSES 13// EVERY NEGATIVE TEST. 14// 15// The fixtures are constructed with nxa_check2 -- the format's ONE checksum definition, the same 16// one the subject uses -- so the gate cannot drift from the organ by re-implementing the rule it 17// is testing. That is deliberate and it is a REAL limitation, stated rather than hidden: this 18// gate proves the reader agrees with the format library, not that the format library is right. 19// The checksum itself is nx_nxa's to prove. 20// 21// nx_nxa_secdump_gate [subject_elf] 22// license_tier: ORIGINAL No hw writes (Rule 26). expect_exit: 0 23import "nx_syscalls.nx" 24import "nx_gate_verdict.nx" 25import "nx_tool_run.nx" 26import "nx_nxa.nx" 27 28// The subject defaults to the BUILD artifact rather than _offc, because this organ is not yet 29// promoted; pass a path to point the gate at a promoted twin once it is. Which one ran is 30// ANNOUNCED, never assumed -- a gate that will not say what it forked cannot be audited. 31const NG_SUBJECT_DEFAULT: *u8 = "/volume1/homes/elderwesto/nishihost/buildroot/_build/nx_nxa_secdump.sov.elf" 32 33// the subject's exit contract, restated here so a change to it breaks this gate LOUDLY 34const NG_OK: i64 = 0 35const NG_E_USAGE: i64 = 2 36const NG_E_BAD: i64 = 3 37const NG_E_NOTAG: i64 = 4 38const NG_E_CKSUM: i64 = 5 39const NG_E_EXEC: i64 = 127 40 41const NG_DIR: *u8 = "/tmp/nx_nxa_secdump_gate" 42const NG_GOOD: *u8 = "/tmp/nx_nxa_secdump_gate/good.nxa" 43const NG_CORRUPT: *u8 = "/tmp/nx_nxa_secdump_gate/corrupt.nxa" 44const NG_BADMAG: *u8 = "/tmp/nx_nxa_secdump_gate/badmagic.nxa" 45 46const NG_MODEDIR: i64 = 493 47const NG_CAPCAP: i64 = 262144 48const NG_ARGV_BYTES: i64 = 64 49const NG_SCRATCH: i64 = 16 50 51// ---- the fixture container, every dimension named ---- 52const NG_HDR: i64 = 32 53const NG_DIRENT: i64 = 32 54const NG_WORD: i64 = 8 55const NG_NSEC: i64 = 2 56const NG_A_WORDS: i64 = 3 57const NG_B_WORDS: i64 = 2 58const NG_OFF_A: i64 = 96 // NG_HDR + NG_NSEC*NG_DIRENT 59const NG_OFF_B: i64 = 120 // NG_OFF_A + NG_A_WORDS*NG_WORD 60const NG_FLEN: i64 = 136 // NG_OFF_B + NG_B_WORDS*NG_WORD 61const NG_A_W0: i64 = 11 62const NG_A_W1: i64 = 22 63const NG_A_W2: i64 = 33 64const NG_B_W0: i64 = 44 65const NG_B_W1: i64 = 55 66const NG_A_W0_MUT: i64 = 12 // one word changed -> the section checksum must stop matching 67const NG_MAGIC_MUT: i64 = 88 // 'X' -- breaks NXANIM01 at byte 0 68const NG_CHECK_SEED: i64 = 1 69const NG_DIR_WORDS: i64 = 8 // NG_NSEC * 4 70const NG_BYTE: i64 = 256 71const NG_BYTE_MASK: i64 = 255 72const NG_TOP_BYTE: i64 = 7 73const NG_VER: i64 = 1 74 75const NG_TAG_A: *u8 = "AAAA" 76const NG_TAG_B: *u8 = "BBBB" 77const NG_TAG_Z: *u8 = "ZZZZ" 78const NG_WORDS2: *u8 = "2" 79const NG_NONNUM: *u8 = "xx" 80 81func ng_slen(s: *u8) -> i64 { var n: i64 = 0; while s[n] != (0 as u8) { n = n + 1 } return n } 82func ng_wr64(b: *u8, off: i64, v: i64) -> i64 { 83 var m: i64 = v 84 var i: i64 = 0 85 while i < NG_WORD { b[off + i] = ((m & NG_BYTE_MASK) as u8); m = m / NG_BYTE; i = i + 1 } 86 return 0 87} 88// signed values need the raw byte lanes, so write the checksum through a pointer cast instead 89func ng_wrw(b: *u8, off: i64, v: i64) -> i64 { 90 let p: *i64 = ((b as i64) + off) as *i64 91 p[0] = v 92 return 0 93} 94func ng_wrtag(b: *u8, off: i64, t: *u8) -> i64 { 95 var i: i64 = 0 96 while i < 4 { b[off + i] = t[i]; i = i + 1 } 97 var k: i64 = 4 98 while k < NG_WORD { b[off + k] = (0 as u8); k = k + 1 } 99 return 0 100} 101func ng_exists(path: *u8, want: i64) -> i64 { 102 let lp: *i64 = sys_mmap(NG_SCRATCH) as *i64 103 let b: *u8 = sys_read_file(path, lp) 104 if (b as i64) == 0 { return 0 } 105 if lp[0] != want { return 0 } 106 return 1 107} 108// substring search over a captured buffer -- the capture is not NUL-terminated, so the length 109// travels with it rather than being rediscovered 110func ng_has(buf: *u8, n: i64, pat: *u8) -> i64 { 111 let m: i64 = ng_slen(pat) 112 if m == 0 { return 0 } 113 if n < m { return 0 } 114 var i: i64 = 0 115 while i <= n - m { 116 var k: i64 = 0 117 var hit: i64 = 1 118 while k < m { if buf[i + k] != pat[k] { hit = 0; k = m } else { k = k + 1 } } 119 if hit == 1 { return 1 } 120 i = i + 1 121 } 122 return 0 123} 124// build one fixture. mutate_payload flips a word of section A; mutate_magic breaks byte 0. 125// The checksums are computed AFTER the payload mutation is NOT applied, so a corrupt fixture is 126// a container whose stored check no longer describes its bytes -- exactly the production defect. 127func ng_build(path: *u8, mutate_payload: i64, mutate_magic: i64) -> i64 { 128 let b: *u8 = sys_mmap(NG_FLEN) 129 // payloads first, at their true offsets 130 ng_wrw(b, NG_OFF_A, NG_A_W0) 131 ng_wrw(b, NG_OFF_A + NG_WORD, NG_A_W1) 132 ng_wrw(b, NG_OFF_A + NG_WORD*2, NG_A_W2) 133 ng_wrw(b, NG_OFF_B, NG_B_W0) 134 ng_wrw(b, NG_OFF_B + NG_WORD, NG_B_W1) 135 let pa: *i64 = ((b as i64) + NG_OFF_A) as *i64 136 let pb: *i64 = ((b as i64) + NG_OFF_B) as *i64 137 let cka: i64 = nxa_check2(NG_CHECK_SEED, pa, NG_A_WORDS) 138 let ckb: i64 = nxa_check2(NG_CHECK_SEED, pb, NG_B_WORDS) 139 // directory 140 ng_wrtag(b, NG_HDR, NG_TAG_A) 141 ng_wrw(b, NG_HDR + 8, NG_OFF_A) 142 ng_wrw(b, NG_HDR + 16, NG_A_WORDS) 143 ng_wrw(b, NG_HDR + 24, cka) 144 ng_wrtag(b, NG_HDR + NG_DIRENT, NG_TAG_B) 145 ng_wrw(b, NG_HDR + NG_DIRENT + 8, NG_OFF_B) 146 ng_wrw(b, NG_HDR + NG_DIRENT + 16, NG_B_WORDS) 147 ng_wrw(b, NG_HDR + NG_DIRENT + 24, ckb) 148 // header, then the TOC check over the directory as written 149 ng_wrw(b, 0, nxa_magic()) 150 ng_wrw(b, 8, NG_VER) 151 ng_wrw(b, 16, NG_NSEC) 152 let tb: *i64 = ((b as i64) + NG_HDR) as *i64 153 ng_wrw(b, 24, nxa_check2(NG_CHECK_SEED, tb, NG_DIR_WORDS)) 154 // mutations land AFTER every checksum is stored, which is what makes them detectable 155 if mutate_payload == 1 { ng_wrw(b, NG_OFF_A, NG_A_W0_MUT) } 156 if mutate_magic == 1 { b[0] = (NG_MAGIC_MUT as u8) } 157 let fd: i64 = sys_openat_wr(path, MODE_0644) 158 if fd < 0 { return 0 - 1 } 159 let wr: i64 = sys_write(fd, b, NG_FLEN) 160 sys_close(fd) 161 if wr != NG_FLEN { return 0 - 1 } 162 return NG_FLEN 163} 164// fork the subject with up to three arguments; returns the child exit code, fills cap/olen 165func ng_run(subject: *u8, a1: *u8, a2: *u8, a3: *u8, cap: *u8, olen: *i64) -> i64 { 166 let av: *i64 = sys_mmap(NG_ARGV_BYTES) as *i64 167 av[0] = subject as i64 168 av[1] = a1 as i64 169 av[2] = a2 as i64 170 av[3] = a3 as i64 171 av[4] = 0 172 return tr_run_capture(subject, av, cap, NG_CAPCAP, olen) 173} 174 175func main(argc: i64, argv: *i64) -> i64 { 176 let ctr: *i64 = gv_ctr() 177 gv_head("nx_nxa_secdump gate -- the NXA reader's REFUSALS are the product, so every one is bitten" as *u8) 178 var subject: *u8 = NG_SUBJECT_DEFAULT 179 if argc >= 2 { subject = argv[1] as *u8 } 180 gv_puts(" subject: " as *u8) 181 gv_puts(subject) 182 gv_puts("\n\n" as *u8) 183 184 sys_mkdir(NG_DIR, NG_MODEDIR) 185 let ra: i64 = ng_build(NG_GOOD, 0, 0) 186 let rb: i64 = ng_build(NG_CORRUPT, 1, 0) 187 let rc9: i64 = ng_build(NG_BADMAG, 0, 1) 188 // ANTI-VACUITY: assert the fixtures reached the condition -- written, and readable back at 189 // exactly the size the container arithmetic predicts -- BEFORE asserting any outcome. 190 var setup: i64 = 0 191 if ra == NG_FLEN { if rb == NG_FLEN { if rc9 == NG_FLEN { 192 if ng_exists(NG_GOOD, NG_FLEN) == 1 { if ng_exists(NG_CORRUPT, NG_FLEN) == 1 { if ng_exists(NG_BADMAG, NG_FLEN) == 1 { setup = 1 } } } 193 } } } 194 gv_check("setup-three-fixtures-built-and-read-back-at-the-predicted-136-bytes (anti-vacuity)" as *u8, setup, ctr) 195 196 let olen: *i64 = sys_mmap(NG_SCRATCH) as *i64 197 let cap: *u8 = sys_mmap(NG_CAPCAP) 198 199 // ---- T1/T2: the GOOD container parses, verifies, and its partition SUMS ---- 200 let e1: i64 = ng_run(subject, NG_GOOD, 0 as *u8, 0 as *u8, cap, olen) 201 let n1: i64 = olen[0] 202 gv_puts(" [T1] good rc=" as *u8) 203 gv_num(e1) 204 gv_puts(" bytes=" as *u8) 205 gv_num(n1) 206 gv_puts("\n" as *u8) 207 // A 127 here means the subject never executed, so every other tooth would be measuring the 208 // harness. Named first, and named for what it is. 209 var exec_ok: i64 = 1 210 if e1 == NG_E_EXEC { exec_ok = 0 } 211 gv_check("neg-control-subject-actually-executed (not 127, not a harness sentinel)" as *u8, exec_ok, ctr) 212 var t1: i64 = 0 213 if e1 == NG_OK { t1 = 1 } 214 gv_check("a-valid-container-verifies-and-exits-OK" as *u8, t1, ctr) 215 var t2: i64 = 0 216 if ng_has(cap, n1, "verdict=OK" as *u8) == 1 { if ng_has(cap, n1, "residual=0" as *u8) == 1 { t2 = 1 } } 217 gv_check("the-byte-partition-SUMS-on-a-valid-container (residual=0 printed, not inferred)" as *u8, t2, ctr) 218 var t2b: i64 = 0 219 if ng_has(cap, n1, "toc_check=MATCH" as *u8) == 1 { if ng_has(cap, n1, "verify=MATCH" as *u8) == 1 { t2b = 1 } } 220 gv_check("both-checksum-layers-recomputed-and-reported (toc and section)" as *u8, t2b, ctr) 221 222 // ---- T3/T4: a section whose bytes no longer match its stored check must be CAUGHT ---- 223 let e3: i64 = ng_run(subject, NG_CORRUPT, 0 as *u8, 0 as *u8, cap, olen) 224 let n3: i64 = olen[0] 225 gv_puts(" [T3] corrupt-payload rc=" as *u8) 226 gv_num(e3) 227 gv_puts("\n" as *u8) 228 var t3: i64 = 0 229 if e3 == NG_E_CKSUM { t3 = 1 } 230 gv_check("a-mutated-payload-word-is-CAUGHT-and-exits-5 (the reader cannot launder a corrupt asset)" as *u8, t3, ctr) 231 var t4: i64 = 0 232 if ng_has(cap, n3, "verify=MISMATCH" as *u8) == 1 { if ng_has(cap, n3, "verdict=CORRUPT" as *u8) == 1 { t4 = 1 } } 233 gv_check("the-corrupt-section-is-NAMED-in-the-output-not-only-in-the-exit-code" as *u8, t4, ctr) 234 // BITE: the same reader, the same fixture shape, one word apart -- corrupt fires, good silent. 235 var b_bad: i64 = 0 236 if e3 == NG_E_CKSUM { b_bad = 1 } 237 var b_good: i64 = 1 238 if e1 == NG_OK { b_good = 0 } 239 gv_bite("neg-control-one-mutated-word-fires-and-the-unmutated-twin-does-not" as *u8, b_bad, b_good, ctr) 240 241 // ---- T5: a broken magic is a CONTAINER refusal, a different exit from a checksum refusal. 242 // Two refusals that collapse into one code would make the reason unrecoverable by a caller. 243 let e5: i64 = ng_run(subject, NG_BADMAG, 0 as *u8, 0 as *u8, cap, olen) 244 let n5: i64 = olen[0] 245 gv_puts(" [T5] bad-magic rc=" as *u8) 246 gv_num(e5) 247 gv_puts("\n" as *u8) 248 var t5: i64 = 0 249 if e5 == NG_E_BAD { t5 = 1 } 250 gv_check("a-broken-magic-refuses-as-BAD-CONTAINER-3-not-as-a-checksum-5 (the reason survives)" as *u8, t5, ctr) 251 var t5b: i64 = 0 252 if ng_has(cap, n5, "BAD-CONTAINER" as *u8) == 1 { t5b = 1 } 253 gv_check("the-container-refusal-names-itself-in-the-output" as *u8, t5b, ctr) 254 255 // ---- T6: an absent tag is its OWN exit, distinct from a corrupt file and from success ---- 256 let e6: i64 = ng_run(subject, NG_GOOD, NG_TAG_Z, 0 as *u8, cap, olen) 257 let n6: i64 = olen[0] 258 gv_puts(" [T6] absent-tag rc=" as *u8) 259 gv_num(e6) 260 gv_puts("\n" as *u8) 261 var t6: i64 = 0 262 if e6 == NG_E_NOTAG { t6 = 1 } 263 if ng_has(cap, n6, "TAG-ABSENT" as *u8) == 0 { t6 = 0 } 264 gv_check("a-tag-that-is-not-in-the-directory-exits-4-and-says-TAG-ABSENT" as *u8, t6, ctr) 265 266 // ---- T7: a non-numeric window REFUSES rather than silently defaulting ---- 267 let e7: i64 = ng_run(subject, NG_GOOD, NG_TAG_A, NG_NONNUM, cap, olen) 268 gv_puts(" [T7] non-numeric-nwords rc=" as *u8) 269 gv_num(e7) 270 gv_puts("\n" as *u8) 271 var t7: i64 = 0 272 if e7 == NG_E_USAGE { t7 = 1 } 273 gv_check("a-non-numeric-window-REFUSES-2-rather-than-publishing-a-default-nobody-asked-for" as *u8, t7, ctr) 274 275 // ---- T8: a truncated word window DECLARES that it is a prefix, and its own denominator ---- 276 let e8: i64 = ng_run(subject, NG_GOOD, NG_TAG_A, NG_WORDS2, cap, olen) 277 let n8: i64 = olen[0] 278 var t8: i64 = 0 279 if e8 == NG_OK { if ng_has(cap, n8, "shown=2 of=3" as *u8) == 1 { if ng_has(cap, n8, "PREFIX OF ITS OWN COUNT" as *u8) == 1 { t8 = 1 } } } 280 gv_check("a-truncated-word-window-prints-its-denominator-AND-declares-itself-a-prefix" as *u8, t8, ctr) 281 // BITE: the prefix marker must be ABSENT when the window covers the whole section, or it is 282 // decoration rather than a measurement. 283 let e8b: i64 = ng_run(subject, NG_GOOD, NG_TAG_B, NG_WORDS2, cap, olen) 284 let n8b: i64 = olen[0] 285 var b8_bad: i64 = 0 286 if ng_has(cap, n8b, "shown=2 of=2" as *u8) == 1 { if ng_has(cap, n8b, "PREFIX OF ITS OWN COUNT" as *u8) == 0 { b8_bad = 1 } } 287 var b8_good: i64 = 1 288 if t8 == 1 { b8_good = 0 } 289 gv_bite("neg-control-the-prefix-marker-is-ABSENT-on-a-complete-window-and-PRESENT-on-a-cut-one" as *u8, b8_bad, b8_good, ctr) 290 291 let rcx: i64 = gv_verdict("NXA-SECDUMP-GATE" as *u8, ctr, "the reader agrees with nx_nxa on valid data and refuses, by distinct exit, on each corruption class" as *u8) 292 sys_exit(rcx) 293 return rcx 294}