code wiki / _hdl_build / nx_cap_detect_lib.nx
nx_cap_detect_lib.nx source
↩ module page · 138 lines · 6631 B
1// nx_cap_detect_lib.nx -- the SILENT-TRUNCATION CAP detector CORE, shared (DRY, rule 15) by the CLI sweep
2// (nx_janitor_caps) and the gate (nx_janitor_caps_gate). Pure text analysis over a .nx source buffer; no
3// syscalls, no I/O -- the caller supplies the bytes. The detector flags the exact bug class that caused the
4// seg-store "stored-not-served" outage: a fixed-count array whose literal count ALSO bounds a fill loop, in a
5// file that reads an unbounded source. See nx_janitor_caps.nx for the full rationale.
6// license_tier: ORIGINAL | genealogy_id: nishi_cap_detect_lib_2026_07_15
7import "nx_syscalls.nx"
8
9// floor: below this a fixed array is provably-bounded scratch (argc, fd-sets, small enums), not a cap.
10const JC_MIN_COUNT: i64 = 32
11
12// buf[pos..] starts with pat[0..plen)? (caller ensures pos+plen <= n)
13func jc_match_at(buf: *u8, pos: i64, pat: *u8, plen: i64) -> i64 {
14 var j: i64=0; while j<plen { if buf[pos+j]!=pat[j] { return 0 } j=j+1 } return 1
15}
16// does pat (len plen) occur anywhere in buf[0..n)?
17func jc_find_sub(buf: *u8, n: i64, pat: *u8, plen: i64) -> i64 {
18 if plen<=0 { return 0 }
19 var i: i64=0; while i+plen<=n { if jc_match_at(buf,i,pat,plen)==1 { return 1 } i=i+1 } return 0
20}
21// (3) does the file read an UNBOUNDED source? (permissive gate -- over-including just yields a candidate)
22func jc_has_src(buf: *u8, n: i64) -> i64 {
23 if jc_find_sub(buf,n,"sys_read(" as *u8,9)==1 { return 1 }
24 if jc_find_sub(buf,n,"getdents" as *u8,8)==1 { return 1 }
25 if jc_find_sub(buf,n,"fetch" as *u8,5)==1 { return 1 }
26 if jc_find_sub(buf,n,"recvfrom" as *u8,8)==1 { return 1 }
27 if jc_find_sub(buf,n,"ss_readall" as *u8,10)==1 { return 1 }
28 return 0
29}
30// starting at pos (right after a '<' or '>'): skip one optional '=' then spaces, parse a decimal,
31// return 1 iff it equals N exactly (full digit-run compare gives the non-digit right-boundary for free).
32func jc_lit_after(buf: *u8, n: i64, pos: i64, N: i64) -> i64 {
33 var i: i64=pos
34 if i<n { if buf[i]==(61 as u8) { i=i+1 } } // optional '='
35 var sp: i64=1
36 while sp==1 { if i<n { if buf[i]==(32 as u8) { i=i+1 } else { sp=0 } } else { sp=0 } }
37 if i>=n { return 0 }
38 let c0: i64=buf[i] as i64
39 if c0<48 { return 0 }
40 if c0>57 { return 0 }
41 var v: i64=0; var go: i64=1
42 while go==1 { if i<n { let d: i64=buf[i] as i64; if d<48 { go=0 } else { if d>57 { go=0 } else { v=v*10+(d-48); i=i+1 } } } else { go=0 } }
43 if v==N { return 1 }
44 return 0
45}
46// (2) is there a '< N' | '<= N' | '>= N' | '> N' bound on literal N anywhere in the file?
47// '<<' and '>>' (shifts) are skipped so a shift amount is never mistaken for a loop bound.
48func jc_has_bound(buf: *u8, n: i64, N: i64) -> i64 {
49 var i: i64=0
50 while i<n {
51 let c: i64=buf[i] as i64
52 if c==60 {
53 var ok: i64=1
54 if i+1<n { if buf[i+1]==(60 as u8) { ok=0 } } // first '<' of a '<<' shift
55 if i>0 { if buf[i-1]==(60 as u8) { ok=0 } } // second '<' of a '<<' shift
56 if ok==1 { if jc_lit_after(buf,n,i+1,N)==1 { return 1 } }
57 } else { if c==62 {
58 var ok2: i64=1
59 if i+1<n { if buf[i+1]==(62 as u8) { ok2=0 } } // first '>' of a '>>' shift
60 if i>0 { if buf[i-1]==(62 as u8) { ok2=0 } } // second '>' of a '>>' shift
61 if ok2==1 { if jc_lit_after(buf,n,i+1,N)==1 { return 1 } }
62 } }
63 i=i+1
64 }
65 return 0
66}
67// copy src[0..n) into dst, replacing STRING-LITERAL and //-COMMENT content with spaces (newlines preserved),
68// so the cap scan never matches inside a string or comment -- only real CODE allocates. Handles \" escapes.
69// A genuinely SOTA source scanner ignores non-code; without this, a detector's own test fixtures self-flag.
70func jc_strip_noncode(src: *u8, n: i64, dst: *u8) -> i64 {
71 var i: i64 = 0
72 var mode: i64 = 0 // 0=code 1=in-string 2=in-linecomment
73 while i < n {
74 let c: i64 = src[i] as i64
75 if mode == 0 {
76 if c == 34 {
77 dst[i] = 34 as u8; mode = 1
78 } else { if c == 47 {
79 if i + 1 < n { if src[i + 1] == (47 as u8) { mode = 2 } }
80 dst[i] = src[i]
81 } else {
82 dst[i] = src[i]
83 } }
84 } else { if mode == 1 {
85 if c == 92 {
86 dst[i] = 32 as u8
87 if i + 1 < n { dst[i + 1] = 32 as u8 }
88 i = i + 1
89 } else { if c == 34 {
90 dst[i] = 34 as u8; mode = 0
91 } else {
92 dst[i] = 32 as u8
93 } }
94 } else {
95 if c == 10 { dst[i] = 10 as u8; mode = 0 } else { dst[i] = 32 as u8 }
96 } }
97 i = i + 1
98 }
99 return n
100}
101
102// (1)+(2): scan every sys_mmap( ... ) arg; for a literal N right after a '*' with N>=floor, if a matching
103// bound on N exists in the file, return N (the offending cap). Returns 0 if none. Paren-depth tracked so a
104// nested call's ')' does not prematurely end the arg. A VARIABLE after '*' (data-driven size) yields no
105// literal -> never flags -- the doctrine-compliant form is invisible to the detector.
106func jc_mmap_scan(buf: *u8, n: i64) -> i64 {
107 var i: i64=0
108 while i+9<=n {
109 if jc_match_at(buf,i,"sys_mmap(" as *u8,9)==1 {
110 var j: i64=i+9
111 var depth: i64=1
112 var go: i64=1
113 while go==1 {
114 if j>=n { go=0 } else {
115 let c: i64=buf[j] as i64
116 if c==40 { depth=depth+1; j=j+1 } else {
117 if c==41 { depth=depth-1; if depth==0 { go=0 } else { j=j+1 } } else {
118 if c==42 {
119 var k: i64=j+1
120 var sp: i64=1
121 while sp==1 { if k<n { if buf[k]==(32 as u8) { k=k+1 } else { sp=0 } } else { sp=0 } }
122 var hasdig: i64=0
123 if k<n { let d0: i64=buf[k] as i64; if d0>=48 { if d0<=57 { hasdig=1 } } }
124 if hasdig==1 {
125 var v: i64=0; var g2: i64=1
126 while g2==1 { if k<n { let d: i64=buf[k] as i64; if d<48 { g2=0 } else { if d>57 { g2=0 } else { v=v*10+(d-48); k=k+1 } } } else { g2=0 } }
127 if v>=JC_MIN_COUNT { if jc_has_bound(buf,n,v)==1 { return v } }
128 }
129 j=j+1
130 } else { j=j+1 }
131 } }
132 }
133 }
134 }
135 i=i+1
136 }
137 return 0
138}