nx_read_file_gate.nx source
↩ module page · 154 lines · 8121 B
1// nx_read_file_gate.nx -- referee for sys_read_file / sys_free_file, the estate's whole-file reader.
2//
3// WHY (debt 1787076780, measured 2026-08-17/19): for a file whose size is UNKNOWABLE (lseek END <= 0 --
4// /proc files, pipes, and every EMPTY regular file, which reports 0 just the same) the reader reserved
5// 4 GiB of address space per call and the paired free could release only what was read. A daemon that
6// read an empty registry every sweep ballooned its VmSize by 4 GiB per read; the leak screens flagged it.
7// 2026-08-19 the size-unknowable path GROWS (64 KiB window, doubling, exact hand-back). These teeth pin:
8// T1 a known-size file reads whole and byte-exact (the path the compiler itself uses -- unchanged)
9// T2 an EMPTY regular file returns a non-NULL buffer with len 0 (present-and-empty is not absent)
10// T3 THE BITE: reading + freeing an empty file 32 times moves VmSize by < 1 MiB (the old reader grew
11// by 4 GiB per read -- 128 GiB here -- and could not shrink back)
12// T4 a size-unknowable /proc file reads whole (procs_blocked, the LAST line of /proc/stat, is present)
13// T5 a size-unknowable file LARGER than the first window (/proc/kallsyms, MBs) reads whole through the
14// doubling path -- precondition: readable on this kernel; SKIP otherwise, never RED
15// T6 sys_free_file on the exact mapping returns 0 (munmap success) and an absent path returns NULL/0
16// T7 neg-control: the free of NULL is a no-op (returns 0, never a crash)
17// license_tier: ORIGINAL expect_exit: 0
18import "nx_gate_verdict.nx"
19import "nx_resmon_lib.nx" // rm_read + rm_field: line-anchored "VmSize:" from /proc/self/status
20
21const RF_FIX_DIR: *u8 = "/tmp/nx_read_file_gate"
22const RF_FIX_EMPTY: *u8 = "/tmp/nx_read_file_gate/empty.bin"
23const RF_FIX_KNOWN: *u8 = "/tmp/nx_read_file_gate/known.bin"
24const RF_FIX_ABSENT: *u8 = "/tmp/nx_read_file_gate/does-not-exist.bin"
25const RF_KNOWN_BYTES: i64 = 100003 // odd, past a page and past any arena cell: the known-size path
26const RF_EMPTY_ROUNDS: i64 = 32 // 32 x 4 GiB would be 128 GiB under the old reader
27const RF_VMSIZE_TOL_KB: i64 = 1024 // 1 MiB of VmSize drift tolerated across the 32 rounds (arena cells, page rounding)
28const RF_STATUS_CAP: i64 = 8192
29const RF_PROC_STAT: *u8 = "/proc/stat"
30const RF_PROC_BIG: *u8 = "/proc/kallsyms"
31const RF_MODE_0644: i64 = 420
32
33func rf_slen(s: *u8) -> i64 { var n: i64 = 0; while s[n] != (0 as u8) { n = n + 1 } return n }
34func rf_vmsize_kb() -> i64 {
35 let b: *u8 = sys_mmap(RF_STATUS_CAP)
36 let n: i64 = rm_read("/proc/self/status" as *u8, b, RF_STATUS_CAP)
37 if n <= 0 { return 0 - 1 }
38 return rm_field(b, n, "VmSize:" as *u8)
39}
40func rf_contains(buf: *u8, n: i64, pat: *u8) -> i64 {
41 let pl: i64 = rf_slen(pat)
42 var i: i64 = 0
43 while i + pl <= n {
44 var k: i64 = 0
45 var hit: i64 = 1
46 while k < pl { if buf[i+k] != pat[k] { hit = 0; k = pl } else { k = k + 1 } }
47 if hit == 1 { return 1 }
48 i = i + 1
49 }
50 return 0
51}
52func rf_write_known() -> i64 {
53 let fd: i64 = sys_openat_wr(RF_FIX_KNOWN, RF_MODE_0644)
54 if fd < 0 { return 0 - 1 }
55 let b: *u8 = sys_mmap(RF_KNOWN_BYTES)
56 var i: i64 = 0
57 while i < RF_KNOWN_BYTES { b[i] = (65 + (i % 26)) as u8; i = i + 1 }
58 var off: i64 = 0
59 while off < RF_KNOWN_BYTES { let r: i64 = sys_write(fd, (b as i64 + off) as *u8, RF_KNOWN_BYTES - off); if r <= 0 { sys_close(fd); return 0 - 2 } off = off + r }
60 sys_close(fd)
61 return 0
62}
63
64func main(argc: i64, argv: *i64) -> i64 {
65 let ctr: *i64 = gv_ctr()
66 gv_head("nx_read_file_gate -- sys_read_file reads whole, hands back exact mappings, and never reserves 4 GiB for an empty file" as *u8)
67 sys_mkdir(RF_FIX_DIR, 493)
68 // fixtures: an empty file and a known-size file
69 let efd: i64 = sys_openat_wr(RF_FIX_EMPTY, RF_MODE_0644)
70 var fx_ok: i64 = 0
71 if efd >= 0 { sys_close(efd); if rf_write_known() == 0 { fx_ok = 1 } }
72 gv_need("fixtures written under /tmp/nx_read_file_gate" as *u8, fx_ok, ctr)
73 let ln: *i64 = sys_mmap(16) as *i64
74
75 // T1 -- known-size file: whole and byte-exact
76 var t1: i64 = 0
77 if fx_ok == 1 {
78 let kb: *u8 = sys_read_file(RF_FIX_KNOWN, ln)
79 if (kb as i64) != 0 { if ln[0] == RF_KNOWN_BYTES {
80 var ok: i64 = 1
81 var i: i64 = 0
82 while i < RF_KNOWN_BYTES { if kb[i] != ((65 + (i % 26)) as u8) { ok = 0; i = RF_KNOWN_BYTES } else { i = i + 1 } }
83 if kb[RF_KNOWN_BYTES] == (0 as u8) { } else { ok = 0 } // NUL pad for the lexer
84 t1 = ok
85 } }
86 sys_free_file(kb, ln[0])
87 gv_check("T1-known-size-file-reads-whole-byte-exact-and-NUL-padded" as *u8, t1, ctr)
88 }
89
90 // T2 -- empty regular file: non-NULL, len 0
91 var t2: i64 = 0
92 if fx_ok == 1 {
93 let eb: *u8 = sys_read_file(RF_FIX_EMPTY, ln)
94 if (eb as i64) != 0 { if ln[0] == 0 { t2 = 1 } }
95 sys_free_file(eb, ln[0])
96 gv_check("T2-empty-regular-file-is-present-and-empty-not-absent" as *u8, t2, ctr)
97 }
98
99 // T3 -- THE BITE: 32 read+free rounds on the empty file move VmSize by < RF_VMSIZE_TOL_KB
100 var t3: i64 = 0
101 if fx_ok == 1 {
102 let v0: i64 = rf_vmsize_kb()
103 var r: i64 = 0
104 while r < RF_EMPTY_ROUNDS { let xb: *u8 = sys_read_file(RF_FIX_EMPTY, ln); sys_free_file(xb, ln[0]); r = r + 1 }
105 let v1: i64 = rf_vmsize_kb()
106 var d: i64 = v1 - v0
107 if d < 0 { d = 0 - d }
108 gv_puts(" VmSize kB before=" as *u8); gv_num(v0); gv_puts(" after=" as *u8); gv_num(v1); gv_puts(" |delta|=" as *u8); gv_num(d); gv_puts(" (old reader: +4194304 per round)\n" as *u8)
109 if v0 > 0 { if v1 > 0 { if d < RF_VMSIZE_TOL_KB { t3 = 1 } } }
110 gv_check("T3-BITE-32-empty-reads-move-VmSize-under-1MiB-not-128GiB" as *u8, t3, ctr)
111 }
112
113 // T4 -- size-unknowable /proc/stat reads whole (its LAST line is present)
114 var t4: i64 = 0
115 let pb: *u8 = sys_read_file(RF_PROC_STAT, ln)
116 if (pb as i64) != 0 { if ln[0] > 0 { if rf_contains(pb, ln[0], "procs_blocked" as *u8) == 1 { t4 = 1 } } }
117 sys_free_file(pb, ln[0])
118 gv_check("T4-size-unknowable-proc-stat-reads-whole-last-line-present" as *u8, t4, ctr)
119
120 // T5 -- a size-unknowable file LARGER than the first window exercises the doubling path
121 let kfd: i64 = sys_openat_rd(RF_PROC_BIG)
122 var kpre: i64 = 0
123 if kfd >= 0 { sys_close(kfd); kpre = 1 }
124 gv_need("/proc/kallsyms readable (multi-MiB size-unknowable file for the doubling path)" as *u8, kpre, ctr)
125 if kpre == 1 {
126 var t5: i64 = 0
127 let bb: *u8 = sys_read_file(RF_PROC_BIG, ln)
128 // whole = it ends at EOF: re-read and compare lengths, and the length is past the first window
129 let ln2: *i64 = sys_mmap(16) as *i64
130 let bb2: *u8 = sys_read_file(RF_PROC_BIG, ln2)
131 gv_puts(" kallsyms bytes=" as *u8); gv_num(ln[0]); gv_puts(" second-read=" as *u8); gv_num(ln2[0]); gv_puts("\n" as *u8)
132 if (bb as i64) != 0 { if ln[0] > SYS_READ_GROW_INIT { if ln[0] == ln2[0] { if bb[ln[0]] == (0 as u8) { t5 = 1 } } } }
133 sys_free_file(bb, ln[0])
134 sys_free_file(bb2, ln2[0])
135 gv_check("T5-unknowable-file-past-the-first-window-reads-whole-through-doubling" as *u8, t5, ctr)
136 }
137
138 // T6 -- exact mapping frees with 0; absent path returns NULL and len 0
139 var t6: i64 = 0
140 if fx_ok == 1 {
141 let kb2: *u8 = sys_read_file(RF_FIX_KNOWN, ln)
142 let fr: i64 = sys_free_file(kb2, ln[0])
143 let ab: *u8 = sys_read_file(RF_FIX_ABSENT, ln)
144 if fr == 0 { if (ab as i64) == 0 { if ln[0] == 0 { t6 = 1 } } }
145 gv_check("T6-exact-mapping-frees-clean-and-absent-path-is-NULL-len0" as *u8, t6, ctr)
146 }
147
148 // T7 -- neg-control: freeing NULL is a no-op
149 var t7: i64 = 0
150 if sys_free_file(0 as *u8, 0) == 0 { t7 = 1 }
151 gv_check("T7-neg-control-free-of-NULL-is-a-no-op" as *u8, t7, ctr)
152
153 return gv_verdict("READ-FILE-GATE" as *u8, ctr, "known-size whole and exact, empty is present-not-absent, the size-unknowable path grows instead of reserving 4 GiB, and every mapping frees exactly" as *u8)
154}