code wiki / (root) / nx_jpegmem_gate.nx

nx_jpegmem_gate.nx source

↩ module page · 156 lines · 7524 B

1// nx_jpegmem_gate.nx -- THE JPEG DECODE MEMORY GATE: repeated decodes are FLAT-RSS and overrun-free. 2// 3// Subject: _offc/nx_jpegmem_probe.elf forked over a REAL banked donor texture (never a synthetic 4// stand-in -- the asset class that exposed the defect IS the donor corpus). The probe decodes the 5// same jpeg N times in one process, prints VmRSS after each round, and surfaces any ARENA-OVERRUN 6// diagnostic from the allocator canary. 7// 8// WHY THIS GATE EXISTS (measured 2026-08-23, nx_jpegmem_probe): nx_jpeg_idct_8x8 and the zigzag 9// inverse-table builders each allocated 512 B PER 8x8 BLOCK; 512 > NXA_SMALL_MAX routes past the 10// bump arena to the page allocator, so every block cost a 4 KiB page nothing freed -- 11// 3,203,232 kB RSS growth PER 4096x4096 decode (it OOM-killed a build VM). Separately, 12// NX_JPEG_DEC_CTX_BYTES=112 under-sized the 15-field NxJpegDecCtx, so every decode wrote 8 B past 13// its context allocation (canary: ARENA-OVERRUN prev_alloc_size=112). After the root fixes the 14// probe measures ~50 kB/round, bounded by bump-arena chunk growth. 15// 16// FAIL DIRECTION IS PROVEN BY MUTATION, not by a synthetic neg-control tooth: nx_gate_bite with 17// nx_jpeg_idct.nx as the mutated source and nx_jpegmem_probe as the rebuilt subject restores the 18// per-block allocation and the flat-rss tooth MUST go RED (bite receipt in the ship record). 19// NOT ROSTER-ADMITTED on purpose: a real decode takes seconds and the roster beat kills at 20// ~1.8 s (the voxchunk precedent) -- run via the job lane / ship loop. 21 22import "nx_syscalls.nx" 23import "nx_gate_verdict.nx" 24import "nx_gatekit_lib.nx" 25 26const JMG_ELF: *u8 = "_offc/nx_jpegmem_probe.elf" 27const JMG_FIXTURE: *u8 = "knowledge/rigcorpus/fbx/textures/toon3d8/Ludovisions_Toon3D8_Eyes_D.jpg" 28const JMG_ROUNDS_ARG: *u8 = "3" 29const JMG_ROUNDS: i64 = 3 30// Capture bound: the probe prints one short line per round plus a preamble; each ARENA-OVERRUN 31// block is ~300 B with its ring dump, so 64 KiB holds any realistic diagnostic spray whole -- 32// and a truncated capture FAILS the parse tooth rather than passing silently. 33const JMG_CAP: i64 = 65536 34// Subject timeout: the roster's own declared per-gate default (GRR_MS_DEFAULT in the gate-roster 35// plane). A hanging subject is not a slow subject; it must produce a verdict. 36const JMG_MS: i64 = 60000 37// JPEG SOI marker bytes -- the fixture must be a real JPEG or every downstream tooth is vacuous. 38const JMG_SOI0: i64 = 255 39const JMG_SOI1: i64 = 216 40 41// find token `t` in buf[0..n), starting at `from`; returns index after the token, or -1. 42func jmg_find(buf: *u8, n: i64, from: i64, t: *u8) -> i64 { 43 var tl: i64 = 0 44 while t[tl] != (0 as u8) { tl = tl + 1 } 45 var i: i64 = from 46 while i + tl <= n { 47 var k: i64 = 0 48 var hit: i64 = 1 49 while k < tl { 50 if buf[i + k] != t[k] { hit = 0; k = tl } else { k = k + 1 } 51 } 52 if hit == 1 { return i + tl } 53 i = i + 1 54 } 55 return 0 - 1 56} 57 58// parse a non-negative decimal at buf[i..); returns value, or -1 if no digit is present. 59func jmg_num(buf: *u8, n: i64, i: i64) -> i64 { 60 var v: i64 = 0 61 var seen: i64 = 0 62 var j: i64 = i 63 var run: i64 = 1 64 while run == 1 { 65 if j >= n { run = 0 } else { 66 let c: i64 = buf[j] as i64 67 if c >= 48 { 68 if c <= 57 { v = v * 10 + (c - 48); seen = 1; j = j + 1 } else { run = 0 } 69 } else { run = 0 } 70 } 71 } 72 if seen == 0 { return 0 - 1 } 73 return v 74} 75 76func main(argc: i64, argv: *i64) -> i64 { 77 let ctr: *i64 = gv_ctr() 78 79 // fixture is a real JPEG (SOI) -- a missing or non-jpeg fixture must fail HERE, loudly. 80 let flp: *i64 = sys_mmap(16) as *i64 81 let fb: *u8 = sys_read_file(JMG_FIXTURE, flp) 82 var soi_ok: i64 = 0 83 if (fb as i64) != 0 { 84 if flp[0] > 1 { 85 if (fb[0] as i64) == JMG_SOI0 { if (fb[1] as i64) == JMG_SOI1 { soi_ok = 1 } } 86 } 87 } 88 gv_check("fixture-exists-and-is-a-real-jpeg (SOI marker; a vacuous fixture cannot pass)" as *u8, soi_ok == 1, ctr) 89 90 let outbuf: *u8 = sys_mmap(JMG_CAP) 91 let outlen: *i64 = sys_mmap(16) as *i64 92 outlen[0] = 0 93 let rc: i64 = gk_run_capture_ms(JMG_ELF, JMG_FIXTURE, JMG_ROUNDS_ARG, 0 as *u8, 0 as *u8, JMG_MS, outbuf, JMG_CAP, outlen) 94 let n: i64 = outlen[0] 95 gv_check("probe-forked-and-exited-zero (127 = elf absent, the LM-026 stale-offc tell)" as *u8, rc == 0, ctr) 96 gv_check("probe-produced-output (a silent subject proves nothing)" as *u8, n > 0, ctr) 97 98 // walk every round line: round=K rc=R w=W h=H rss_kb=S 99 var rounds_seen: i64 = 0 100 var all_rc0: i64 = 1 101 var wh_ok: i64 = 1 102 var rss_first: i64 = 0 - 1 103 var rss_last: i64 = 0 - 1 104 var pos: i64 = 0 105 var scanning: i64 = 1 106 while scanning == 1 { 107 let a: i64 = jmg_find(outbuf, n, pos, "round=" as *u8) 108 if a < 0 { scanning = 0 } else { 109 let rcp: i64 = jmg_find(outbuf, n, a, " rc=" as *u8) 110 let wp: i64 = jmg_find(outbuf, n, a, " w=" as *u8) 111 let hp: i64 = jmg_find(outbuf, n, a, " h=" as *u8) 112 let sp: i64 = jmg_find(outbuf, n, a, " rss_kb=" as *u8) 113 if rcp < 0 { scanning = 0 } else { if sp < 0 { scanning = 0 } else { 114 let rv: i64 = jmg_num(outbuf, n, rcp) 115 let wv: i64 = jmg_num(outbuf, n, wp) 116 let hv: i64 = jmg_num(outbuf, n, hp) 117 let sv: i64 = jmg_num(outbuf, n, sp) 118 if rv != 0 { all_rc0 = 0 } 119 if wv <= 0 { wh_ok = 0 } 120 if hv <= 0 { wh_ok = 0 } 121 if rss_first < 0 { rss_first = sv } 122 rss_last = sv 123 rounds_seen = rounds_seen + 1 124 pos = sp 125 } } 126 } 127 } 128 gv_puts(" rounds_seen=" as *u8); gv_num(rounds_seen) 129 gv_puts(" rss_first_kb=" as *u8); gv_num(rss_first) 130 gv_puts(" rss_last_kb=" as *u8); gv_num(rss_last) 131 gv_puts("\n" as *u8) 132 133 gv_check("fixture-reached-the-condition (all rounds decoded, rc=0, real dims)" as *u8, rounds_seen == JMG_ROUNDS, ctr) 134 gv_check("every-round-rc-zero" as *u8, all_rc0 == 1, ctr) 135 gv_check("every-round-real-dims" as *u8, wh_ok == 1, ctr) 136 137 // THE LEAK TOOTH. The only legitimate growth across rounds is bump-arena chunk allocation; 138 // the arena grows in NXA_CHUNK quanta (imported from nx_syscalls -- ONE owner for that 139 // number), so (rounds-1) chunks is a hard ceiling that is derived, not picked. Pre-fix this 140 // measured 3,203,232 kB per round; post-fix ~50 kB. 141 let bound_kb: i64 = (JMG_ROUNDS - 1) * (NXA_CHUNK / 1024) 142 var grew_kb: i64 = 0 - 1 143 if rss_first >= 0 { if rss_last >= rss_first { grew_kb = rss_last - rss_first } } 144 gv_puts(" grew_kb=" as *u8); gv_num(grew_kb) 145 gv_puts(" bound_kb=" as *u8); gv_num(bound_kb) 146 gv_puts("\n" as *u8) 147 gv_check("flat-rss-within-arena-chunk-quantum (the 3.2 GB/decode leak cannot recur)" as *u8, grew_kb >= 0, ctr) 148 gv_check("flat-rss-bound-holds" as *u8, grew_kb <= bound_kb, ctr) 149 150 // THE OVERRUN TOOTH. The allocator canary prints ARENA-OVERRUN when any allocation is written 151 // past; the 112-byte NxJpegDecCtx under-size fired it on EVERY decode pre-fix. 152 let ov: i64 = jmg_find(outbuf, n, 0, "ARENA-OVERRUN" as *u8) 153 gv_check("arena-overrun-absent (the canary stayed silent across every round)" as *u8, ov < 0, ctr) 154 155 return gv_verdict("NX-JPEGMEM" as *u8, ctr, "repeated decodes hold flat RSS and a silent canary, proven on a real donor texture" as *u8) 156}