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}