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}