nx_livecloexec_gate.nx source
↩ module page · 445 lines · 21557 B
1// nx_livecloexec_gate.nx -- does any LIVE process hold a listening socket WITHOUT FD_CLOEXEC?
2//
3// WHY THIS EXISTS ALONGSIDE nx_cloexec_gate (debt 1785521781). That gate is excellent at what it does:
4// it calls the SHIPPING nx_http_server_listen and proves the returned fd carries FD_CLOEXEC, with a
5// control and a reader neg-control. But it measures THE PRIMITIVE, and a primitive is not a fleet:
6// (a) a LIB fix reaches an organ only on ITS NEXT REBUILD -- a daemon deployed before the fix still
7// runs the old code, and the gate is GREEN the whole time;
8// (b) a daemon that binds by some OTHER path (raw sys_socket + bind + listen instead of the shared
9// helper) is not covered by the primitive at all.
10// So nx_cloexec_gate GREEN + a wedged port are perfectly consistent, which is exactly what happened
11// 2026-07-31: the gate passed while :18098 was unreachable for every seat.
12//
13// THIS gate measures the OTHER side: it walks /proc/<pid>/fd, finds sockets, and reads the REAL
14// FD_CLOEXEC bit out of /proc/<pid>/fdinfo/<fd> (`flags:` octal, 02000000 = O_CLOEXEC). It reports what
15// is TRUE OF RUNNING PROCESSES RIGHT NOW, not what the library would do if called today.
16//
17// ★ A GREEN PRIMITIVE GATE IS NOT EVIDENCE ABOUT DEPLOYED BINARIES.
18//
19// ⚠ ADVISORY BY DESIGN, exit 0 unless the SCAN ITSELF fails. A non-CLOEXEC socket is not automatically a
20// defect: most are ACCEPTED connections (short-lived, no port to hostage) and inherited-fd risk applies
21// to LISTENERS. Distinguishing listener from accepted socket needs /proc/net/tcp inode correlation --
22// see nx_portdup for that lane. Reporting a count with an honest caveat beats a RED verdict this gate
23// cannot justify. What it DOES prove non-vacuously: the scan reached real fds and read real flags.
24//
25// license_tier: ORIGINAL No hw writes (Rule 26). expect_exit: 0
26import "nx_syscalls.nx"
27
28// 65536, not 4096. THE FIRST DRAFT CAPPED AT 4096 AND FOUND ZERO SOCKETS while printing GREEN -- the
29// live daemons on this host are pids 18552 / 24218 / 30391, ALL above that bound. An arbitrary scan
30// bound silently excluded the entire population the gate exists to measure, and the summary line still
31// said the measurement succeeded.
32// ★★ A SCAN BOUND IS A CLAIM ABOUT THE POPULATION. Pick it from the system (/proc/sys/kernel/pid_max is
33// 32768+ on Linux), never from a round number, and make ZERO FINDINGS FAIL rather than pass -- see the
34// sockets==0 tooth below, which this draft did not have and which is what caught it.
35const LC_MAXPID: i64 = 65536
36const LC_LOG: *u8 = "knowledge/status/livecloexec_gate.log"
37const LC_MODE: i64 = 420
38
39func lc_len(s: *u8) -> i64 { var n: i64 = 0; while s[n] != (0 as u8) { n = n + 1 } return n }
40func lc_p(s: *u8) -> i64 { let n: i64 = lc_len(s); sys_write(1, s, n); return 0 }
41func lc_wf(fd: i64, s: *u8) -> i64 { let n: i64 = lc_len(s); sys_write(fd, s, n); return 0 }
42func lc_pnf(fd: i64, v: i64) -> i64 {
43 let t: *u8 = sys_mmap(32)
44 var m: i64 = v
45 if m < 0 { sys_write(fd, "-" as *u8, 1); m = 0 - m }
46 var k: i64 = 0
47 if m == 0 { t[0] = 48 as u8; k = 1 }
48 while m > 0 { t[k] = (48 + (m % 10)) as u8; m = m / 10; k = k + 1 }
49 var j: i64 = k - 1
50 while j >= 0 { sys_write(fd, ((t as i64) + j) as *u8, 1); j = j - 1 }
51 return 0
52}
53func lc_pn(v: i64) -> i64 {
54 let t: *u8 = sys_mmap(32)
55 var m: i64 = v
56 if m < 0 { sys_write(1, "-" as *u8, 1); m = 0 - m }
57 var k: i64 = 0
58 if m == 0 { t[0] = 48 as u8; k = 1 }
59 while m > 0 { t[k] = (48 + (m % 10)) as u8; m = m / 10; k = k + 1 }
60 var j: i64 = k - 1
61 while j >= 0 { sys_write(1, ((t as i64) + j) as *u8, 1); j = j - 1 }
62 return 0
63}
64
65func lc_cat(dst: *u8, o0: i64, s: *u8) -> i64 {
66 var o: i64 = o0
67 var i: i64 = 0
68 while s[i] != (0 as u8) { dst[o] = s[i]; o = o + 1; i = i + 1 }
69 return o
70}
71
72func lc_catn(dst: *u8, o0: i64, v: i64) -> i64 {
73 if v <= 0 { dst[o0] = 48 as u8; return o0 + 1 }
74 let t: *u8 = sys_mmap(32)
75 var m: i64 = v
76 var k: i64 = 0
77 while m > 0 { t[k] = (48 + (m % 10)) as u8; m = m / 10; k = k + 1 }
78 var o: i64 = o0
79 var j: i64 = k - 1
80 while j >= 0 { dst[o] = t[j]; o = o + 1; j = j - 1 }
81 return o
82}
83
84// Read a small file into buf; returns bytes read, or 0-1 if unopenable (a pid that exited mid-scan is
85// normal, not an error -- /proc is a moving target and a scanner that treats churn as failure is noise).
86func lc_slurp(path: *u8, buf: *u8, cap: i64) -> i64 {
87 let fd: i64 = sys_openat_rd(path)
88 if fd < 0 { return 0 - 1 }
89 let n: i64 = sys_read(fd, buf, cap - 1)
90 sys_close(fd)
91 if n < 0 { return 0 - 1 }
92 buf[n] = 0 as u8
93 return n
94}
95
96// Does buf contain `needle`?
97func lc_has(buf: *u8, n: i64, needle: *u8) -> i64 {
98 let m: i64 = lc_len(needle)
99 if m == 0 { return 0 }
100 var i: i64 = 0
101 while i + m <= n {
102 var eq: i64 = 1
103 var j: i64 = 0
104 while j < m { if buf[i + j] != needle[j] { eq = 0; j = m } else { j = j + 1 } }
105 if eq == 1 { return 1 }
106 i = i + 1
107 }
108 return 0
109}
110
111// Parse the OCTAL value after `flags:` in an fdinfo blob. Returns 0-1 when absent.
112// OCTAL, not decimal: /proc renders fdinfo flags in base 8, so 02000000 is O_CLOEXEC. Reading it as
113// decimal would silently mis-test every fd -- the kind of unit error that makes a gate lie in one
114// direction only.
115func lc_flags(buf: *u8, n: i64) -> i64 {
116 var i: i64 = 0
117 var at: i64 = 0 - 1
118 while i + 6 <= n {
119 if buf[i] == (102 as u8) {
120 if buf[i+1] == (108 as u8) { if buf[i+2] == (97 as u8) { if buf[i+3] == (103 as u8) {
121 if buf[i+4] == (115 as u8) { if buf[i+5] == (58 as u8) { at = i + 6; i = n } } } } }
122 }
123 i = i + 1
124 }
125 if at < 0 { return 0 - 1 }
126 var p: i64 = at
127 var sk: i64 = 0
128 while sk == 0 {
129 if p < n {
130 let c: i64 = buf[p] as i64
131 if c == 32 { p = p + 1 } else { if c == 9 { p = p + 1 } else { sk = 1 } }
132 } else { sk = 1 }
133 }
134 var v: i64 = 0
135 var got: i64 = 0
136 var go: i64 = 1
137 while go == 1 {
138 if p < n {
139 let c: i64 = buf[p] as i64
140 if c >= 48 { if c <= 55 { v = v * 8 + (c - 48); got = 1; p = p + 1 } else { go = 0 } } else { go = 0 }
141 } else { go = 0 }
142 }
143 if got == 0 { return 0 - 1 }
144 return v
145}
146
147// Parse /proc/net/tcp (and tcp6) and record the inode of every socket in state 0A (LISTEN) into a
148// sorted-insert array. This is what turns an advisory count into an ACTIONABLE one: a CLEAR bit on an
149// accepted connection is harmless (short-lived, holds no port), while a CLEAR bit on a LISTENER is the
150// port-hostage mechanism itself.
151// Column layout: `sl local_address rem_address st ... inode`. st is field 3 (0-based), inode is field 9.
152// STOP-FLAG LOOPS THROUGHOUT, deliberately. An earlier draft emulated `break` with `p = eol + 1`
153// followed by `p = p - eol - 1`, which is unreadable and is the same shape as the no-op-advance
154// infinite loop already fixed once today in the @for bound trimmer. In a scanner that runs inside the
155// compiler toolchain, a hang reports NOTHING -- the build simply never returns.
156func lc_load_listeners(path: *u8, buf: *u8, cap: i64, inodes: *i64, n0: i64, maxn: i64) -> i64 {
157 let n: i64 = lc_slurp(path, buf, cap)
158 if n <= 0 { return n0 }
159 var count: i64 = n0
160
161 var line: i64 = 0
162 // Skip the header line.
163 var hs: i64 = 0
164 while hs == 0 {
165 if line >= n { hs = 1 } else {
166 if buf[line] == (10 as u8) { line = line + 1; hs = 1 } else { line = line + 1 }
167 }
168 }
169
170 while line < n {
171 // Find end of this line.
172 var eol: i64 = line
173 var es: i64 = 0
174 while es == 0 {
175 if eol >= n { es = 1 } else {
176 if buf[eol] == (10 as u8) { es = 1 } else { eol = eol + 1 }
177 }
178 }
179
180 // Walk whitespace-separated fields: st is field 3 (hex, 0A = LISTEN), inode is field 9 (dec).
181 var f: i64 = 0
182 var p: i64 = line
183 var st: i64 = 0 - 1
184 var ino: i64 = 0 - 1
185 var done: i64 = 0
186 while done == 0 {
187 var ws: i64 = 0
188 while ws == 0 {
189 if p < eol { if buf[p] == (32 as u8) { p = p + 1 } else { ws = 1 } } else { ws = 1 }
190 }
191 if p >= eol { done = 1 } else {
192 let fs: i64 = p
193 var fe: i64 = 0
194 while fe == 0 {
195 if p < eol { if buf[p] == (32 as u8) { fe = 1 } else { p = p + 1 } } else { fe = 1 }
196 }
197 if f == 3 {
198 var v: i64 = 0
199 var q: i64 = fs
200 while q < p {
201 let c: i64 = buf[q] as i64
202 if c >= 48 { if c <= 57 { v = v * 16 + (c - 48) } }
203 if c >= 65 { if c <= 70 { v = v * 16 + (c - 55) } }
204 if c >= 97 { if c <= 102 { v = v * 16 + (c - 87) } }
205 q = q + 1
206 }
207 st = v
208 }
209 if f == 9 {
210 var v2: i64 = 0
211 var q2: i64 = fs
212 while q2 < p {
213 let c2: i64 = buf[q2] as i64
214 if c2 >= 48 { if c2 <= 57 { v2 = v2 * 10 + (c2 - 48) } }
215 q2 = q2 + 1
216 }
217 ino = v2
218 done = 1
219 }
220 f = f + 1
221 }
222 }
223 if st == 10 { if ino > 0 { if count < maxn { inodes[count] = ino; count = count + 1 } } }
224 line = eol + 1
225 }
226 return count
227}
228
229func lc_is_listener(inodes: *i64, n: i64, ino: i64) -> i64 {
230 var i: i64 = 0
231 while i < n { if inodes[i] == ino { return 1 } i = i + 1 }
232 return 0
233}
234
235// Extract the inode from a `socket:[12345]` readlink target.
236func lc_link_inode(lnk: *u8, n: i64) -> i64 {
237 var br: i64 = 0 - 1
238 var i: i64 = 0
239 var s1: i64 = 0
240 while s1 == 0 {
241 if i >= n { s1 = 1 } else {
242 if lnk[i] == (91 as u8) { br = i; s1 = 1 } else { i = i + 1 }
243 }
244 }
245 if br < 0 { return 0 - 1 }
246 var p: i64 = br + 1
247 var v: i64 = 0
248 var got: i64 = 0
249 var s2: i64 = 0
250 while s2 == 0 {
251 if p >= n { s2 = 1 } else {
252 let c: i64 = lnk[p] as i64
253 if c >= 48 { if c <= 57 { v = v * 10 + (c - 48); got = 1; p = p + 1 } else { s2 = 1 } } else { s2 = 1 }
254 }
255 }
256 if got == 0 { return 0 - 1 }
257 return v
258}
259
260func main() -> i64 {
261 lc_p("=== nx_livecloexec_gate -- FD_CLOEXEC on LIVE listening sockets (not the primitive) ===\n" as *u8)
262
263 let path: *u8 = sys_mmap(256)
264 let buf: *u8 = sys_mmap(8192)
265 let lnk: *u8 = sys_mmap(512)
266 // SEPARATE buffer for comm. It must survive the fdinfo slurp that happens inside the fd loop --
267 // sharing `buf` would overwrite the process name with flags text before it is ever printed, and the
268 // offender line would name whatever fdinfo happened to contain.
269 let cbuf: *u8 = sys_mmap(256)
270
271 // Load LISTENING inodes first, so every socket fd can be classified as listener vs accepted.
272 // THIS IS WHAT MAKES THE COUNT ACTIONABLE: a CLEAR bit on an accepted connection is harmless;
273 // on a LISTENER it is the port-hostage mechanism.
274 let lino: *i64 = sys_mmap(8 * 4096) as *i64
275 var nlisten: i64 = lc_load_listeners("/proc/net/tcp" as *u8, buf, 8192, lino, 0, 4096)
276 nlisten = lc_load_listeners("/proc/net/tcp6" as *u8, buf, 8192, lino, nlisten, 4096)
277
278 // D-STATE CENSUS, added 2026-08-01 after a live IO starvation (load 191, IO=178, CPU=13) whose real
279 // signature was 64 sites.elf processes in uninterruptible IO wait. nx_resmon covers memory, nx_procchurn
280 // covers fork/ctxsw rates, and NOTHING covered stuck-in-D -- the state that best predicts an IO-starved
281 // host. This walk is already open on /proc, so the census is free: read /proc/PID/stat field 3.
282 // ★ GATE ON THE RESOURCE THE WORK ACTUALLY CONSUMES -- a load-average threshold fires for the wrong
283 // reason when 93 percent of the load is disk wait and more CPU would not help.
284 var dstate: i64 = 0
285 var scanned_pids: i64 = 0
286 var sockets: i64 = 0
287 var cloexec: i64 = 0
288 var plain: i64 = 0
289 var flagsread: i64 = 0
290 var listeners: i64 = 0
291 var listen_clear: i64 = 0
292
293 var pid: i64 = 1
294 while pid < LC_MAXPID {
295 var o: i64 = lc_cat(path, 0, "/proc/" as *u8)
296 o = lc_catn(path, o, pid)
297 o = lc_cat(path, o, "/comm" as *u8)
298 path[o] = 0 as u8
299 let cn: i64 = lc_slurp(path, cbuf, 256)
300 if cn > 0 {
301 scanned_pids = scanned_pids + 1
302 // /proc/PID/stat: state is the field AFTER the parenthesised comm. Parse from the LAST ')'
303 // because a process name may itself contain spaces or parens -- splitting from the left
304 // reads the wrong field for exactly the processes worth noticing.
305 var o4: i64 = lc_cat(path, 0, "/proc/" as *u8)
306 o4 = lc_catn(path, o4, pid)
307 o4 = lc_cat(path, o4, "/stat" as *u8)
308 path[o4] = 0 as u8
309 let sn2: i64 = lc_slurp(path, buf, 1024)
310 if sn2 > 0 {
311 var rp: i64 = 0 - 1
312 var si: i64 = 0
313 while si < sn2 { if buf[si] == (41 as u8) { rp = si } si = si + 1 }
314 if rp >= 0 {
315 var sp2: i64 = rp + 1
316 var sw2: i64 = 0
317 while sw2 == 0 { if sp2 < sn2 { if buf[sp2] == (32 as u8) { sp2 = sp2 + 1 } else { sw2 = 1 } } else { sw2 = 1 } }
318 if sp2 < sn2 { if buf[sp2] == (68 as u8) { dstate = dstate + 1 } }
319 }
320 }
321 // Walk fd 0..63 -- daemons here keep their listeners low, and a bounded sweep keeps this
322 // gate fast enough to run in a 12s deadline.
323 var fd: i64 = 0
324 while fd < 64 {
325 var o2: i64 = lc_cat(path, 0, "/proc/" as *u8)
326 o2 = lc_catn(path, o2, pid)
327 o2 = lc_cat(path, o2, "/fd/" as *u8)
328 o2 = lc_catn(path, o2, fd)
329 path[o2] = 0 as u8
330 let ln: i64 = sys_readlinkat(path, lnk, 500)
331 if ln > 0 {
332 lnk[ln] = 0 as u8
333 if lc_has(lnk, ln, "socket:" as *u8) == 1 {
334 sockets = sockets + 1
335 var o3: i64 = lc_cat(path, 0, "/proc/" as *u8)
336 o3 = lc_catn(path, o3, pid)
337 o3 = lc_cat(path, o3, "/fdinfo/" as *u8)
338 o3 = lc_catn(path, o3, fd)
339 path[o3] = 0 as u8
340 let fn2: i64 = lc_slurp(path, buf, 4096)
341 if fn2 > 0 {
342 let fl: i64 = lc_flags(buf, fn2)
343 if fl >= 0 {
344 flagsread = flagsread + 1
345 // O_CLOEXEC = 02000000 octal = 524288 decimal.
346 var isclo: i64 = 0
347 if (fl / 524288) % 2 == 1 { isclo = 1 }
348 if isclo == 1 { cloexec = cloexec + 1 } else { plain = plain + 1 }
349 // Classify: is THIS fd's inode a listening socket?
350 let ino: i64 = lc_link_inode(lnk, ln)
351 if ino > 0 {
352 if lc_is_listener(lino, nlisten, ino) == 1 {
353 listeners = listeners + 1
354 if isclo == 0 {
355 listen_clear = listen_clear + 1
356 // NAME THE OFFENDER. A count is not a fix list -- whoever acts
357 // on this needs to know WHICH binary to rebuild, and comm is
358 // already in `buf`-adjacent scope via cbuf below.
359 lc_p(" OFFENDER pid=" as *u8); lc_pn(pid)
360 lc_p(" fd=" as *u8); lc_pn(fd)
361 lc_p(" comm=" as *u8)
362 var ci: i64 = 0
363 while ci < 64 {
364 if cbuf[ci] == (0 as u8) { ci = 64 } else {
365 if cbuf[ci] == (10 as u8) { ci = 64 } else {
366 sys_write(1, ((cbuf as i64) + ci) as *u8, 1)
367 ci = ci + 1
368 }
369 }
370 }
371 lc_p("\n" as *u8)
372 }
373 }
374 }
375 }
376 }
377 }
378 }
379 fd = fd + 1
380 }
381 }
382 pid = pid + 1
383 }
384
385 lc_p(" pids scanned = " as *u8); lc_pn(scanned_pids); lc_p("\n" as *u8)
386 lc_p(" socket fds found = " as *u8); lc_pn(sockets); lc_p("\n" as *u8)
387 lc_p(" fdinfo flags read = " as *u8); lc_pn(flagsread); lc_p("\n" as *u8)
388 lc_p(" FD_CLOEXEC set = " as *u8); lc_pn(cloexec); lc_p("\n" as *u8)
389 lc_p(" FD_CLOEXEC CLEAR = " as *u8); lc_pn(plain); lc_p("\n" as *u8)
390 lc_p(" LISTEN inodes = " as *u8); lc_pn(nlisten); lc_p(" (from /proc/net/tcp + tcp6, state 0A)\n" as *u8)
391 lc_p(" fds that LISTEN = " as *u8); lc_pn(listeners); lc_p("\n" as *u8)
392 lc_p(" LISTENERS w/o CLOEXEC = " as *u8); lc_pn(listen_clear); lc_p(" <-- THE PORT-HOSTAGE POPULATION\n" as *u8)
393 lc_p(" procs in D (uninterruptible IO wait) = " as *u8); lc_pn(dstate)
394 lc_p(" (a pile-up here means DISK starvation; more CPU would not help)\n" as *u8)
395
396 // NON-VACUITY: the scan must have reached real processes AND read real flags. A gate that walked
397 // zero pids, or found sockets but could not read a single flags: line, has measured NOTHING -- and
398 // that is the failure mode worth failing on, because it is indistinguishable from a clean fleet.
399 if scanned_pids <= 0 {
400 lc_p(" VERDICT=RED scan reached ZERO pids -- the instrument measured nothing\n" as *u8)
401 return 1
402 }
403 if sockets > 0 {
404 if flagsread <= 0 {
405 lc_p(" VERDICT=RED found sockets but read ZERO flags lines -- fdinfo parse is broken\n" as *u8)
406 return 2
407 }
408 }
409 // ZERO SOCKETS ACROSS A LIVE HOST IS AN INSTRUMENT FAILURE, NOT A CLEAN FLEET. This host runs 15
410 // network daemons; if a scan of hundreds of pids finds no socket fd at all, the scan is wrong --
411 // too low a pid bound, an unreadable /proc, or a broken readlink. The FIRST DRAFT OF THIS GATE hit
412 // exactly that (4096 pid cap vs daemons at pid 18552+) and reported GREEN, which is the precise
413 // failure mode this whole gate was written to expose in ANOTHER instrument.
414 // ★★★ AN INSTRUMENT THAT FINDS NOTHING MUST PROVE IT LOOKED. Absence of findings and absence of
415 // measurement render identically unless you make one of them RED.
416 if sockets <= 0 {
417 lc_p(" VERDICT=RED zero socket fds across " as *u8); lc_pn(scanned_pids)
418 lc_p(" pids -- impossible on a host running network daemons; the SCAN is broken (pid bound too low? /proc unreadable?), not the fleet clean\n" as *u8)
419 return 3
420 }
421
422 lc_p("\n NON-VACUITY: reached real pids, read real fdinfo flags, and correlated inodes against\n" as *u8)
423 lc_p(" /proc/net/tcp LISTEN state -- every count above is measured, none assumed.\n" as *u8)
424 lc_p(" READ THE LAST LINE, NOT THE CLEAR COUNT. Most CLEAR fds are ACCEPTED connections:\n" as *u8)
425 lc_p(" short-lived, holding no port, harmless if inherited. Only a LISTENER can be held\n" as *u8)
426 lc_p(" hostage by a forked child, so LISTENERS-w/o-CLOEXEC is the only actionable number.\n" as *u8)
427 lc_p(" Measured 2026-07-31: 103 CLEAR fds but only 3 of them LISTENERS -- a 34x difference\n" as *u8)
428 lc_p(" between the alarming count and the real one. An uncorrelated count would have cried wolf.\n" as *u8)
429 lc_p(" WHY IT MATTERS: nx_cloexec_gate proves the PRIMITIVE; a lib fix reaches an organ only on\n" as *u8)
430 lc_p(" ITS next rebuild, so GREEN there and a wedged port here are perfectly consistent.\n" as *u8)
431 // TEETH = the scan's own assertions: reached real pids, and read real flags. The listener count is
432 // REPORTED, not graded -- 3 unfixed listeners is a fix list, not a broken instrument.
433 lc_p("NX-LIVECLOEXEC-GATE passed 2/2 verdict=GREEN (listeners_without_cloexec=" as *u8)
434 lc_pn(listen_clear); lc_p(" reported, not graded)\n" as *u8)
435 let llg: i64 = sys_openat_append(LC_LOG, LC_MODE)
436 if llg >= 0 {
437 lc_wf(llg, "NX-LIVECLOEXEC-GATE passed=2 total=2 sockets=" as *u8); lc_pnf(llg, sockets)
438 lc_wf(llg, " listeners=" as *u8); lc_pnf(llg, listeners)
439 lc_wf(llg, " listeners_no_cloexec=" as *u8); lc_pnf(llg, listen_clear)
440 lc_wf(llg, " dstate=" as *u8); lc_pnf(llg, dstate)
441 lc_wf(llg, " verdict=GREEN\n" as *u8)
442 sys_close(llg)
443 }
444 return 0
445}