code wiki / (root) / nx_autofix_lib.nx

nx_autofix_lib.nx source

↩ module page · 1005 lines · 49846 B

1// nx_autofix_lib.nx -- THE SHARED RULERS OF THE AUTONOMOUS FIX LOOP (autodev M0, 2026-08-27). 2// 3// WHY A LIB: nx_autofix_auto (the loop), nx_autofix_intake_gate (the intake) and nx_swebench_local_gate 4// (the judge) each carried their own copy of "find the failing FNRES row", their own ledger path 5// literal (/home/elderwesto/nx_stage/...) and their own idea of what a proposal row means. Three 6// organs that must agree cannot be kept agreeing by discipline; only one copy holds. This is the 7// one copy, and the contract symbols on the /compare/autodev board (af_episode_ledger, 8// af_sandbox_root, af_null_control, af_admit_untrusted, af_intake_board) are thin composers over it in 9// nx_autofix_auto.nx -- the loop's call sites -- while the gates drive these rulers in-process. 10// 11// THE FIVE RULERS 12// AD5 EPISODE LEDGER afl_ledger_* -- one row per episode, appended AS DECIDED to an estate-relative 13// journal (knowledge/status/autofix_ledger.jrnl) so a run interrupted at 95 percent 14// keeps 95 percent, and the page reads the plane and nothing else. The dev-box 15// stage ledger stays the fallback (nx_stage_path), never a literal. 16// AD7 SANDBOX ROOT afl_sandbox_* -- every candidate builds and runs in /tmp/afx_sb_<ts>_<name>/ 17// with its import closure COPIED (not linked: a write through a link would reach 18// the tree), the toolchain linked read-only, a real _build/, RLIMIT_CPU and 19// RLIMIT_AS on the child, a wall deadline that kills the whole process group. 20// The tree copy is never opened for write; afl_bytes_eq is the witness. 21// AD4 NULL CONTROLS afl_null_controls -- the empty patch and a replay of a prior solution are run 22// through the SAME judge; a null that scores GREEN is an oracle defect and the 23// batch is REFUSED, never published. Echo is rejected pre-verify by byte identity 24// (structural) and the revert patch is the empty patch by construction; both are 25// named on the line rather than counted twice. 26// AD6 UNTRUSTED INPUT afl_admit_row -- deny by default: a proposal row is LOCAL (built and run in the 27// sandbox) or EXTERNAL DATA-ONLY (provenance-pinned h<sha> AND maintainer-merged, 28// never executed); an external row missing either is refused BY NAME. 29// AD3 INTAKE PLANE afl_intake_emit -- folds the estate's own boards into one task plane: roster 30// RED gates, the adjudicated drift worklist, the magic ratchet counts, the unwired 31// baseline; every task carries its oracle and a RED-before receipt; the partition 32// prints its sum; debt rows carry no executable oracle and are REFUSED as 33// UNREPRODUCED (counted, never hidden); a source that is absent says ABSENT-SOURCE. 34// 35// CONF (rule 17: env > conf > bootstrap): knowledge/autofix.conf rows sandbox_wall_ms, sandbox_as_bytes, 36// bon_n, bon_temp_pm, bon_topp_pm, bon_topk, null_controls_max_green. The defaults below are the July 37// contract values with the reason each was chosen written beside it. 38// license_tier: ORIGINAL No hw writes (Rule 26). 39import "nx_syscalls.nx" 40import "nx_estate_path.nx" 41import "nx_stage_path.nx" 42import "nx_tool_run.nx" 43 44const AFL_PATH: i64 = 4096 // Linux PATH_MAX, the same bound nx_stage_path uses 45const AFL_LINE: i64 = 2048 46const AFL_NAME: i64 = 64 47const AFL_HARNESS: i64 = 128 48const AFL_MODE_0644: i64 = 420 49const AFL_MODE_0755: i64 = 493 50const AFL_ABSENT: i64 = 0 - 999999 51const AFL_CAPTURE: i64 = 1048576 // one grader capture; the July candidates print under 1 KB, the bound announces when it binds 52const AFL_LEDGER_JRNL: *u8 = "knowledge/status/autofix_ledger.jrnl" 53const AFL_SWBL_JRNL: *u8 = "knowledge/status/swebench_local.jrnl" 54const AFL_INTAKE_JRNL: *u8 = "knowledge/status/autodev_intake.jrnl" 55// harness.jrnl is written by every gate's gv_journal, so it exists wherever the estate's status dir is; 56// its directory is therefore the write root for the ledgers above, resolved once and never spelled twice. 57const AFL_STATUS_PROBE: *u8 = "knowledge/status/harness.jrnl" 58const AFL_LEDGER_STAGE: *u8 = "autofix_ledger.log" 59const AFL_SWBL_STAGE: *u8 = "swebench_local_ledger.log" 60const AFL_CLUSTER_MAX: i64 = 256 // distinct located functions in one batch; a batch above this reads UNMEASURED, announced 61const AFL_CONF: *u8 = "knowledge/autofix.conf" 62const AFL_KEY_WALL: *u8 = "sandbox_wall_ms=" 63const AFL_KEY_AS: *u8 = "sandbox_as_bytes=" 64const AFL_KEY_BON_N: *u8 = "bon_n=" 65const AFL_KEY_BON_TEMP: *u8 = "bon_temp_pm=" 66const AFL_KEY_BON_TOPP: *u8 = "bon_topp_pm=" 67const AFL_KEY_BON_TOPK: *u8 = "bon_topk=" 68const AFL_KEY_NULL_MAX: *u8 = "null_controls_max_green=" 69// 5 minutes for the WHOLE candidate (build + run): the July episodes build and run in single seconds; 70// nx_sov_build_run's own per-step deadline is 900000 ms, so this is one third of one of its steps. 71const AFL_WALL_MS_DEFAULT: i64 = 300000 72// 4 GiB of address space per sandboxed process: above any measured sovereign-compiler peak (the largest 73// closure .s on record is nx_browser at 9.2 MB) and one eighth of the 36 GB host, so a runaway candidate 74// cannot take the box with it. A bound that must be guessed is announced on every run (afl_sandbox_run). 75const AFL_AS_BYTES_DEFAULT: i64 = 4294967296 76const AFL_BON_N_DEFAULT: i64 = 3 // the A1 best-of-N precedent 77const AFL_BON_TEMP_DEFAULT: i64 = 800 // permil temperature, the A1 precedent 78const AFL_BON_TOPP_DEFAULT: i64 = 950 // permil nucleus mass 79const AFL_BON_TOPK_DEFAULT: i64 = 40 // top-k cutoff (the nsv sampler caps at 256) 80const AFL_NULL_MAX_DEFAULT: i64 = 0 // ANY null control scoring GREEN refuses the batch 81const AFL_POLL_MS: i64 = 100 // wait4(WNOHANG) cadence, the same the build runner polls its children at 82const AFL_SIGKILL: i64 = 9 83const AFL_MS_PER_S: i64 = 1000 84const AFL_CLOSURE_MAX: i64 = 64 // import closure files copied into a sandbox; announces when it binds 85// THE SANDBOX LIVES ON AN EXEC-CAPABLE FILESYSTEM (measured 2026-08-27 on the NAS: /tmp is mounted 86// noexec -- a candidate COMPILED there and its exec returned 127 while the toolchain, symlinked to 87// /volume1, ran fine; the laptop's /tmp execs, which is why the same gate read GREEN there). The build 88// tree's own _build/ scratch is exec-capable BY CONSTRUCTION -- every gate the estate runs execs from 89// it -- so the sandbox roots there, resolved by the same probe the build runner anchors with 90// (runtime/nx_syscalls.nx identifies the real tree); /tmp stays the last fallback for a hermetic host. 91const AFL_SB_NAME: *u8 = "afx_sb_" 92const AFL_SB_TMP: *u8 = "/tmp/" 93const AFL_TREE_PROBE: *u8 = "runtime/nx_syscalls.nx" 94const AFL_RLIM_BYTES: i64 = 16 // struct rlimit64 { cur, max } 95const AFL_EXIT_EXEC_FAIL: i64 = 127 96const AFL_EXIT_CHDIR_FAIL: i64 = 126 97const AFL_BUILDER_REL: *u8 = "_offc/nx_sov_build_run.elf" 98const AFL_CC_REL: *u8 = "_offc/nx_cc_sovereign.elf" 99const AFL_ASM_REL: *u8 = "_offc/nxasm_x86_main.elf" 100const AFL_ADMIT_REL: *u8 = "_build/nx_build_admit.sov.elf" 101// admission verdicts for a proposal row (AD6) 102const AFL_ADMIT_LOCAL: i64 = 1 103const AFL_ADMIT_DATA_ONLY: i64 = 2 104const AFL_REFUSED_UNPINNED: i64 = 0 - 1 105const AFL_REFUSED_UNMERGED: i64 = 0 - 2 106const AFL_REFUSED_MALFORMED: i64 = 0 - 3 107// intake sources (AD3) 108const AFL_ROSTER_JRNL: *u8 = "knowledge/status/gateroster.jrnl" 109const AFL_DRIFT_WORKLIST: *u8 = "knowledge/status/adoption_worklist_laneB.txt" 110const AFL_DRIFT_LOG: *u8 = "knowledge/status/artifactdrift.log" 111const AFL_MAGIC_DIR: *u8 = "knowledge/status/magicbase" 112const AFL_UNWIRED_BASE: *u8 = "nishi-ops/unwired.baseline" 113const AFL_DEBT_ELF: *u8 = "./nx_debt.elf" 114const AFL_ROSTER_MAX: i64 = 4096 // distinct gate names in the roster journal (633 today); announces when it binds 115const AFL_DIRBUF: i64 = 65536 116const AFL_DEBT_TMO_MS: i64 = 60000 // the board's sev verb answers in seconds; a minute is the announce point 117 118func afl_slen(s: *u8) -> i64 { var n: i64 = 0; while s[n] != (0 as u8) { n = n + 1 } return n } 119func afl_cat(d: *u8, o: i64, s: *u8) -> i64 { var i: i64 = 0; var p: i64 = o; while s[i] != (0 as u8) { d[p] = s[i]; p = p + 1; i = i + 1 } d[p] = 0 as u8; return p } 120func afl_catn(d: *u8, o: i64, v: i64) -> i64 { 121 var m: i64 = v 122 var p: i64 = o 123 if m < 0 { d[p] = 45 as u8; p = p + 1; m = 0 - m } 124 let t: *u8 = sys_mmap(32) 125 var k: i64 = 0 126 if m == 0 { t[0] = 48 as u8; k = 1 } 127 while m > 0 { t[k] = (48 + (m % 10)) as u8; m = m / 10; k = k + 1 } 128 var i: i64 = 0 129 while i < k { d[p] = t[k - 1 - i]; p = p + 1; i = i + 1 } 130 d[p] = 0 as u8 131 sys_munmap(t, 32) 132 return p 133} 134func afl_streq(a: *u8, b: *u8) -> i64 { var i: i64 = 0; while a[i] == b[i] { if a[i] == (0 as u8) { return 1 } i = i + 1 } return 0 } 135func afl_starts(s: *u8, p: *u8) -> i64 { var i: i64 = 0; while p[i] != (0 as u8) { if s[i] != p[i] { return 0 } i = i + 1 } return 1 } 136func afl_find(hay: *u8, hn: i64, needle: *u8, from: i64) -> i64 { 137 let m: i64 = afl_slen(needle) 138 if m == 0 { return 0 - 1 } 139 var i: i64 = from 140 while i + m <= hn { 141 var j: i64 = 0 142 var ok: i64 = 1 143 while j < m { if hay[i+j] != needle[j] { ok = 0; j = m } else { j = j + 1 } } 144 if ok == 1 { return i } 145 i = i + 1 146 } 147 return 0 - 1 148} 149// end of the line that starts at p (index of the newline or n) 150func afl_eol(buf: *u8, n: i64, p: i64) -> i64 { 151 var e: i64 = p 152 var go: i64 = 1 153 while go == 1 { if e >= n { go = 0 } else { if buf[e] == (10 as u8) { go = 0 } else { e = e + 1 } } } 154 return e 155} 156// token after key within [ls,le): up to space / newline / '|', copied into dst (cap). returns length. 157func afl_tok(buf: *u8, ls: i64, le: i64, key: *u8, dst: *u8, cap: i64) -> i64 { 158 dst[0] = 0 as u8 159 let ki: i64 = afl_find(buf, le, key, ls) 160 if ki < 0 { return 0 } 161 var i: i64 = ki + afl_slen(key) 162 var j: i64 = 0 163 var go: i64 = 1 164 while go == 1 { 165 if i >= le { go = 0 } else { 166 let c: i64 = buf[i] as i64 167 if c == 32 { go = 0 } else { if c == 10 { go = 0 } else { if c == 124 { go = 0 } else { if c == 13 { go = 0 } else { 168 if j >= cap - 1 { go = 0 } else { dst[j] = buf[i]; j = j + 1; i = i + 1 } 169 } } } } 170 } 171 } 172 dst[j] = 0 as u8 173 return j 174} 175// leading unsigned integer at buf[p..n); AFL_ABSENT when there is no digit there 176func afl_uint_at(buf: *u8, p: i64, n: i64) -> i64 { 177 var i: i64 = p 178 var v: i64 = 0 179 var got: i64 = 0 180 var go: i64 = 1 181 while go == 1 { 182 if i >= n { go = 0 } else { 183 let c: i64 = buf[i] as i64 184 if c >= 48 { if c <= 57 { v = v * 10 + (c - 48); got = 1; i = i + 1 } else { go = 0 } } else { go = 0 } 185 } 186 } 187 if got == 0 { return AFL_ABSENT } 188 return v 189} 190// integer after key within [ls,le); AFL_ABSENT when the key or the digits are missing 191func afl_int(buf: *u8, ls: i64, le: i64, key: *u8) -> i64 { 192 let ki: i64 = afl_find(buf, le, key, ls) 193 if ki < 0 { return AFL_ABSENT } 194 var p: i64 = ki + afl_slen(key) 195 var neg: i64 = 0 196 if p < le { if buf[p] == (45 as u8) { neg = 1; p = p + 1 } } 197 var v: i64 = 0 198 var got: i64 = 0 199 var go: i64 = 1 200 while go == 1 { 201 if p >= le { go = 0 } else { 202 let c: i64 = buf[p] as i64 203 if c >= 48 { if c <= 57 { v = v * 10 + (c - 48); got = 1; p = p + 1 } else { go = 0 } } else { go = 0 } 204 } 205 } 206 if got == 0 { return AFL_ABSENT } 207 if neg == 1 { return 0 - v } 208 return v 209} 210// conf row: PRESENT wins even at 0 (null_controls_max_green=0 is a real value), absent -> dflt 211func afl_conf_int(key: *u8, dflt: i64) -> i64 { 212 let b: *u8 = sys_mmap(AFL_PATH) 213 var v: i64 = dflt 214 if sp_conf_in(AFL_CONF, key, b) > 0 { v = sp_atoi(b) } 215 sys_munmap(b, AFL_PATH) 216 return v 217} 218func afl_exists(path: *u8) -> i64 { let fd: i64 = sys_openat_rd(path); if fd < 0 { return 0 } sys_close(fd); return 1 } 219func afl_copyfile(srcp: *u8, dstp: *u8) -> i64 { 220 let lb: *i64 = sys_mmap(8) as *i64 221 let buf: *u8 = sys_read_file(srcp, lb) 222 if (buf as i64) == 0 { return 0 - 1 } 223 let fd: i64 = sys_openat_wr(dstp, AFL_MODE_0644) 224 if fd < 0 { return 0 - 2 } 225 sys_write(fd, buf, lb[0]) 226 sys_close(fd) 227 return lb[0] 228} 229// byte-equality of two files (the AD7 witness: the tree copy after an episode == the tree copy before) 230func afl_bytes_eq(a: *u8, b: *u8) -> i64 { 231 let la: *i64 = sys_mmap(8) as *i64 232 let lb: *i64 = sys_mmap(8) as *i64 233 let ba: *u8 = sys_read_file(a, la) 234 let bb: *u8 = sys_read_file(b, lb) 235 if (ba as i64) == 0 { return 0 } 236 if (bb as i64) == 0 { return 0 } 237 if la[0] != lb[0] { return 0 } 238 var i: i64 = 0 239 while i < la[0] { if ba[i] != bb[i] { return 0 } i = i + 1 } 240 return 1 241} 242func afl_append(path: *u8, buf: *u8, n: i64) -> i64 { 243 let fd: i64 = sys_openat_append(path, AFL_MODE_0644) 244 if fd < 0 { return 0 - 1 } 245 let w: i64 = sys_write(fd, buf, n) 246 sys_close(fd) 247 return w 248} 249// replace the first occurrence of `finds` in the file at `path` with `repls`; returns new size, <0 on miss 250func afl_apply_fix(path: *u8, finds: *u8, repls: *u8) -> i64 { 251 let lb: *i64 = sys_mmap(8) as *i64 252 let src: *u8 = sys_read_file(path, lb) 253 if (src as i64) == 0 { return 0 - 1 } 254 let n: i64 = lb[0] 255 let fl: i64 = afl_slen(finds) 256 let idx: i64 = afl_find(src, n, finds, 0) 257 if idx < 0 { return 0 - 2 } 258 let rl: i64 = afl_slen(repls) 259 let out: *u8 = sys_mmap(n + rl + 1) 260 var o: i64 = 0 261 var i: i64 = 0 262 while i < idx { out[o] = src[i]; o = o + 1; i = i + 1 } 263 var j: i64 = 0 264 while j < rl { out[o] = repls[j]; o = o + 1; j = j + 1 } 265 i = idx + fl 266 while i < n { out[o] = src[i]; o = o + 1; i = i + 1 } 267 let fd: i64 = sys_openat_wr(path, AFL_MODE_0644) 268 if fd < 0 { return 0 - 3 } 269 sys_write(fd, out, o) 270 sys_close(fd) 271 return o 272} 273 274// ---- the judge's own rows: FNRES <name> <passed> <total> ---------------------------------------- 275// FIRST failing row -> 1 and its name (clamped to AFL_NAME-1; fields located from the TRUE end of the 276// name, never the clamped length -- the offset-desync class, debt 1785521219); 0 all pass; -1 no rows. 277func afl_fn_failing(out: *u8, n: i64, namebuf: *u8) -> i64 { 278 var any: i64 = 0 279 var p: i64 = 0 280 var res: i64 = 0 281 var done: i64 = 0 282 namebuf[0] = 0 as u8 283 while done == 0 { 284 let r: i64 = afl_find(out, n, "FNRES " as *u8, p) 285 if r < 0 { done = 1 } else { 286 any = 1 287 var i: i64 = r + 6 288 var nl: i64 = 0 289 var nend: i64 = n 290 var sc: i64 = 1 291 while sc == 1 { if i >= n { sc = 0 } else { if out[i] == (32 as u8) { nend = i; sc = 0 } else { if nl < AFL_NAME - 1 { namebuf[nl] = out[i]; nl = nl + 1 } i = i + 1 } } } 292 namebuf[nl] = 0 as u8 293 let e: i64 = afl_eol(out, n, r) 294 let passed: i64 = afl_int(out, nend, e, " " as *u8) 295 var q: i64 = nend + 1 296 var pd: i64 = 1 297 while pd == 1 { if q >= e { pd = 0 } else { let c: i64 = out[q] as i64; var d: i64 = 0; if c >= 48 { if c <= 57 { d = 1 } } if d == 1 { q = q + 1 } else { pd = 0 } } } 298 let total: i64 = afl_int(out, q, e, " " as *u8) 299 if passed != AFL_ABSENT { if total != AFL_ABSENT { if passed < total { res = 1; done = 1 } } } 300 if done == 0 { namebuf[0] = 0 as u8 } 301 p = r + 6 302 } 303 } 304 if res == 1 { return 1 } 305 if any == 1 { return 0 } 306 return 0 - 1 307} 308 309// ---- AD5: the episode ledger ---------------------------------------------------------------------- 310// the estate's status directory (dirname of knowledge/status/harness.jrnl wherever ep_ resolves it), 311// with a trailing slash; 0 when no estate status dir is reachable from this host. 312func afl_status_dir(out: *u8) -> i64 { 313 let p: *u8 = sys_mmap(AFL_PATH) 314 if ep_artifact_path(p, AFL_STATUS_PROBE) == 0 { out[0] = 0 as u8; sys_munmap(p, AFL_PATH); return 0 } 315 var n: i64 = afl_slen(p) 316 while n > 0 { if p[n-1] == (47 as u8) { n = 0 - n } else { n = n - 1 } } 317 n = 0 - n 318 var i: i64 = 0 319 while i < n { out[i] = p[i]; i = i + 1 } 320 out[n] = 0 as u8 321 sys_munmap(p, AFL_PATH) 322 return 1 323} 324// where to READ the fix-loop ledger: 1 = estate journal, 2 = the dev-box stage ledger, 0 = neither exists 325func afl_ledger_read_path(out: *u8) -> i64 { 326 if ep_artifact_path(out, AFL_LEDGER_JRNL) == 1 { return 1 } 327 sp_path(AFL_LEDGER_STAGE, out) 328 if afl_exists(out) == 1 { return 2 } 329 out[0] = 0 as u8 330 return 0 331} 332// where to WRITE an episode row: the estate status dir when reachable, else the stage dir (never a literal) 333func afl_ledger_write_path(out: *u8) -> i64 { 334 if afl_status_dir(out) == 1 { afl_cat(out, afl_slen(out), "autofix_ledger.jrnl" as *u8); return 1 } 335 sp_path(AFL_LEDGER_STAGE, out) 336 return 2 337} 338func afl_swbl_write_path(out: *u8) -> i64 { 339 if afl_status_dir(out) == 1 { afl_cat(out, afl_slen(out), "swebench_local.jrnl" as *u8); return 1 } 340 sp_path(AFL_SWBL_STAGE, out) 341 return 2 342} 343// one episode row, appended as decided; returns bytes written (<0 = the ledger could not be opened) 344func afl_ledger_append(row: *u8, n: i64, where: *u8) -> i64 { 345 let p: *u8 = sys_mmap(AFL_PATH) 346 afl_ledger_write_path(p) 347 var i: i64 = 0 348 while p[i] != (0 as u8) { where[i] = p[i]; i = i + 1 } 349 where[i] = 0 as u8 350 let w: i64 = afl_append(p, row, n) 351 sys_munmap(p, AFL_PATH) 352 return w 353} 354// newest AUTOFIX-AUTO batch of a ledger: st[0]=maxts st[1]=total st[2]=resolved; clusters by located= in 355// ck/cn (successes/outcomes per distinct located name, names at stride AFL_NAME); harness copied from the 356// first row that carries harness= (else empty). Returns the cluster count (0 when the batch is empty). 357func afl_batch_stats(buf: *u8, n: i64, st: *i64, ck: *i64, cn: *i64, names: *u8, harness: *u8) -> i64 { 358 st[0] = 0; st[1] = 0; st[2] = 0 359 harness[0] = 0 as u8 360 var nc: i64 = 0 361 var p: i64 = 0 362 while p < n { 363 let e: i64 = afl_eol(buf, n, p) 364 if afl_starts(((buf as i64) + p) as *u8, "AUTOFIX-AUTO " as *u8) == 1 { 365 let t: i64 = afl_int(buf, p, e, "ts=" as *u8) 366 if t != AFL_ABSENT { if t > st[0] { st[0] = t } } 367 } 368 p = e + 1 369 } 370 if st[0] == 0 { return 0 } 371 let nm: *u8 = sys_mmap(AFL_NAME) 372 p = 0 373 while p < n { 374 let e2: i64 = afl_eol(buf, n, p) 375 if afl_starts(((buf as i64) + p) as *u8, "AUTOFIX-AUTO " as *u8) == 1 { 376 let t2: i64 = afl_int(buf, p, e2, "ts=" as *u8) 377 if t2 == st[0] { 378 st[1] = st[1] + 1 379 var green: i64 = 0 380 if afl_find(buf, e2, " maker=GREEN" as *u8, p) >= 0 { green = 1 } 381 if green == 1 { st[2] = st[2] + 1 } 382 afl_tok(buf, p, e2, "located=" as *u8, nm, AFL_NAME) 383 if nm[0] == (0 as u8) { afl_cat(nm, 0, "unlocated" as *u8) } 384 var ci: i64 = 0 - 1 385 var c: i64 = 0 386 while c < nc { if afl_streq(((names as i64) + c * AFL_NAME) as *u8, nm) == 1 { ci = c; c = nc } else { c = c + 1 } } 387 if ci < 0 { if nc < AFL_CLUSTER_MAX { afl_cat(((names as i64) + nc * AFL_NAME) as *u8, 0, nm); ck[nc] = 0; cn[nc] = 0; ci = nc; nc = nc + 1 } } 388 if ci >= 0 { cn[ci] = cn[ci] + 1; ck[ci] = ck[ci] + green } 389 if harness[0] == (0 as u8) { afl_tok(buf, p, e2, "harness=" as *u8, harness, AFL_HARNESS) } 390 } 391 } 392 p = e2 + 1 393 } 394 sys_munmap(nm, AFL_NAME) 395 return nc 396} 397 398// ---- AD6: untrusted-input admission ---------------------------------------------------------------- 399// row grammar: name|path[|origin=local|external][|prov=h<hex>][|merged=yes|no]. Returns the verdict and 400// fills name/path/prov. LOCAL rows are built and run in the sandbox; EXTERNAL rows are admitted as DATA 401// ONLY when pinned and merged, and are NEVER executed; anything else is refused BY NAME. 402func afl_admit_row(row: *u8, rl: i64, name: *u8, path: *u8, prov: *u8) -> i64 { 403 name[0] = 0 as u8; path[0] = 0 as u8; prov[0] = 0 as u8 404 var pipe1: i64 = 0 - 1 405 var i: i64 = 0 406 while i < rl { if row[i] == (124 as u8) { pipe1 = i; i = rl } else { i = i + 1 } } 407 if pipe1 <= 0 { return AFL_REFUSED_MALFORMED } 408 var nl: i64 = 0 409 i = 0 410 while i < pipe1 { if nl < AFL_NAME - 1 { name[nl] = row[i]; nl = nl + 1 } i = i + 1 } 411 name[nl] = 0 as u8 412 var pe: i64 = pipe1 + 1 413 var pl: i64 = 0 414 while pe < rl { if row[pe] == (124 as u8) { pe = rl + 1 } else { if pl < AFL_PATH - 1 { path[pl] = row[pe]; pl = pl + 1 } pe = pe + 1 } } 415 path[pl] = 0 as u8 416 if pl == 0 { return AFL_REFUSED_MALFORMED } 417 let origin: *u8 = sys_mmap(AFL_NAME) 418 let merged: *u8 = sys_mmap(AFL_NAME) 419 afl_tok(row, 0, rl, "origin=" as *u8, origin, AFL_NAME) 420 afl_tok(row, 0, rl, "prov=" as *u8, prov, AFL_HARNESS) 421 afl_tok(row, 0, rl, "merged=" as *u8, merged, AFL_NAME) 422 var ext: i64 = 0 423 if afl_streq(origin, "external" as *u8) == 1 { ext = 1 } 424 sys_munmap(origin, AFL_NAME) 425 if ext == 0 { sys_munmap(merged, AFL_NAME); return AFL_ADMIT_LOCAL } 426 var pinned: i64 = 0 427 if prov[0] == (104 as u8) { if afl_slen(prov) >= 17 { pinned = 1 } } 428 if pinned == 0 { sys_munmap(merged, AFL_NAME); return AFL_REFUSED_UNPINNED } 429 if afl_streq(merged, "yes" as *u8) == 0 { sys_munmap(merged, AFL_NAME); return AFL_REFUSED_UNMERGED } 430 sys_munmap(merged, AFL_NAME) 431 return AFL_ADMIT_DATA_ONLY 432} 433func afl_admit_name(v: i64) -> *u8 { 434 if v == AFL_ADMIT_LOCAL { return "ADMITTED-LOCAL-RUN" as *u8 } 435 if v == AFL_ADMIT_DATA_ONLY { return "ADMITTED-EXTERNAL-DATA-ONLY(never executed)" as *u8 } 436 if v == AFL_REFUSED_UNPINNED { return "REFUSED-EXTERNAL-UNPINNED(no prov=h<sha>)" as *u8 } 437 if v == AFL_REFUSED_UNMERGED { return "REFUSED-EXTERNAL-UNMERGED(merged=yes required)" as *u8 } 438 return "REFUSED-MALFORMED(name|path expected)" as *u8 439} 440 441// ---- AD7: the sandbox root ------------------------------------------------------------------------ 442// absolute path of a toolchain file: <cwd>/<rel> when it opens there, else <buildroot>/<rel>, else <root>/<rel> 443func afl_tool_abs(rel: *u8, out: *u8) -> i64 { 444 let cwd: *u8 = sys_mmap(AFL_PATH) 445 if sys_getcwd(cwd, AFL_PATH) > 0 { 446 var o: i64 = afl_cat(out, 0, cwd) 447 if o > 0 { if out[o-1] != (47 as u8) { out[o] = 47 as u8; o = o + 1 } } 448 afl_cat(out, o, rel) 449 if afl_exists(out) == 1 { sys_munmap(cwd, AFL_PATH); return 1 } 450 } 451 sys_munmap(cwd, AFL_PATH) 452 ep_join(out, EP_BUILDROOT, rel) 453 if afl_exists(out) == 1 { return 1 } 454 ep_join(out, EP_ROOT, rel) 455 if afl_exists(out) == 1 { return 1 } 456 out[0] = 0 as u8 457 return 0 458} 459// the directory sandboxes are created under, with a trailing slash. 1=cwd tree _build/, 2=buildroot 460// _build/ (absolute), 3=/tmp (no tree resolves -- exec may fail there and the capture will say so). 461func afl_sandbox_root_dir(out: *u8) -> i64 { 462 if afl_exists(AFL_TREE_PROBE) == 1 { 463 let cwd: *u8 = sys_mmap(AFL_PATH) 464 if sys_getcwd(cwd, AFL_PATH) > 0 { 465 var o: i64 = afl_cat(out, 0, cwd) 466 if o > 0 { if out[o-1] != (47 as u8) { out[o] = 47 as u8; o = o + 1; out[o] = 0 as u8 } } 467 afl_cat(out, afl_slen(out), "_build/" as *u8) 468 sys_munmap(cwd, AFL_PATH) 469 return 1 470 } 471 sys_munmap(cwd, AFL_PATH) 472 } 473 let b: *u8 = sys_mmap(AFL_PATH) 474 ep_join(b, EP_BUILDROOT, AFL_TREE_PROBE) 475 if afl_exists(b) == 1 { 476 var o2: i64 = afl_cat(out, 0, EP_BUILDROOT) 477 afl_cat(out, o2, "_build/" as *u8) 478 sys_munmap(b, AFL_PATH) 479 return 2 480 } 481 sys_munmap(b, AFL_PATH) 482 afl_cat(out, 0, AFL_SB_TMP) 483 return 3 484} 485 486// copy a source file into <sb>/runtime/<base> and, transitively, every `import "x.nx"` it names, 487// resolved through ep_src_path (the build lane's own resolver). Returns files copied (bounded, announced). 488func afl_closure_copy(sb: *u8, srcpath: *u8, base: *u8) -> i64 { 489 let queue: *u8 = sys_mmap(AFL_CLOSURE_MAX * AFL_NAME) 490 let done: *u8 = sys_mmap(AFL_CLOSURE_MAX * AFL_NAME) 491 var qn: i64 = 0 492 var dn: i64 = 0 493 var copied: i64 = 0 494 let dst: *u8 = sys_mmap(AFL_PATH) 495 var o: i64 = afl_cat(dst, 0, sb) 496 o = afl_cat(dst, o, "runtime/" as *u8) 497 afl_cat(dst, o, base) 498 if afl_copyfile(srcpath, dst) < 0 { return 0 - 1 } 499 copied = 1 500 afl_cat(((done as i64) + dn * AFL_NAME) as *u8, 0, base) 501 dn = dn + 1 502 // seed the queue with the candidate's own imports; then drain 503 let lb: *i64 = sys_mmap(8) as *i64 504 var cur: *u8 = srcpath 505 var pending: i64 = 1 506 let curbuf: *u8 = sys_mmap(AFL_PATH) 507 while pending == 1 { 508 let b: *u8 = sys_read_file(cur, lb) 509 if (b as i64) != 0 { 510 var p: i64 = 0 511 while p < lb[0] { 512 let e: i64 = afl_eol(b, lb[0], p) 513 if afl_starts(((b as i64) + p) as *u8, "import \"" as *u8) == 1 { 514 let nm: *u8 = sys_mmap(AFL_NAME) 515 var q: i64 = p + 8 516 var k: i64 = 0 517 while q < e { if b[q] == (34 as u8) { q = e } else { if k < AFL_NAME - 1 { nm[k] = b[q]; k = k + 1 } q = q + 1 } } 518 nm[k] = 0 as u8 519 var seen: i64 = 0 520 var d: i64 = 0 521 while d < dn { if afl_streq(((done as i64) + d * AFL_NAME) as *u8, nm) == 1 { seen = 1; d = dn } else { d = d + 1 } } 522 if seen == 0 { var qq: i64 = 0; while qq < qn { if afl_streq(((queue as i64) + qq * AFL_NAME) as *u8, nm) == 1 { seen = 1; qq = qn } else { qq = qq + 1 } } } 523 if seen == 0 { if qn < AFL_CLOSURE_MAX { afl_cat(((queue as i64) + qn * AFL_NAME) as *u8, 0, nm); qn = qn + 1 } } 524 sys_munmap(nm, AFL_NAME) 525 } 526 p = e + 1 527 } 528 } 529 if qn == 0 { pending = 0 } else { 530 qn = qn - 1 531 let nxt: *u8 = ((queue as i64) + qn * AFL_NAME) as *u8 532 // resolve "x.nx" -> target name "x" -> the build lane's source path 533 let tgt: *u8 = sys_mmap(AFL_NAME) 534 var tl: i64 = afl_slen(nxt) 535 if tl > 3 { tl = tl - 3 } 536 var z: i64 = 0 537 while z < tl { tgt[z] = nxt[z]; z = z + 1 } 538 tgt[tl] = 0 as u8 539 if ep_src_path(curbuf, tgt) == 1 { 540 var o2: i64 = afl_cat(dst, 0, sb) 541 o2 = afl_cat(dst, o2, "runtime/" as *u8) 542 afl_cat(dst, o2, nxt) 543 if afl_copyfile(curbuf, dst) >= 0 { copied = copied + 1 } 544 if dn < AFL_CLOSURE_MAX { afl_cat(((done as i64) + dn * AFL_NAME) as *u8, 0, nxt); dn = dn + 1 } 545 cur = curbuf 546 } else { 547 if dn < AFL_CLOSURE_MAX { afl_cat(((done as i64) + dn * AFL_NAME) as *u8, 0, nxt); dn = dn + 1 } 548 cur = srcpath 549 // nothing to scan for an unresolvable import; the compiler will name it 550 } 551 sys_munmap(tgt, AFL_NAME) 552 if qn == 0 { if dn >= AFL_CLOSURE_MAX { pending = 0 } } 553 } 554 } 555 return copied 556} 557// make the sandbox: <prefix><ts>_<cname>/ with runtime/ (candidate + closure copies), _build/ (real), 558// _offc/ (toolchain symlinks) and, when present, the admission binary. Writes the dir into out. 559func afl_sandbox_make(ts: i64, cname: *u8, srcpath: *u8, out: *u8) -> i64 { 560 afl_sandbox_root_dir(out) 561 var o: i64 = afl_cat(out, afl_slen(out), AFL_SB_NAME) 562 o = afl_catn(out, o, ts) 563 o = afl_cat(out, o, "_" as *u8) 564 o = afl_cat(out, o, cname) 565 o = afl_cat(out, o, "/" as *u8) 566 sys_mkdir(out, AFL_MODE_0755) 567 let sub: *u8 = sys_mmap(AFL_PATH) 568 var s: i64 = afl_cat(sub, 0, out); afl_cat(sub, s, "runtime" as *u8); sys_mkdir(sub, AFL_MODE_0755) 569 s = afl_cat(sub, 0, out); afl_cat(sub, s, "_build" as *u8); sys_mkdir(sub, AFL_MODE_0755) 570 s = afl_cat(sub, 0, out); afl_cat(sub, s, "_offc" as *u8); sys_mkdir(sub, AFL_MODE_0755) 571 let base: *u8 = sys_mmap(AFL_NAME) 572 var bo: i64 = afl_cat(base, 0, cname) 573 afl_cat(base, bo, ".nx" as *u8) 574 let copied: i64 = afl_closure_copy(out, srcpath, base) 575 if copied < 0 { return 0 } 576 let tool: *u8 = sys_mmap(AFL_PATH) 577 if afl_tool_abs(AFL_CC_REL, tool) == 1 { s = afl_cat(sub, 0, out); afl_cat(sub, s, AFL_CC_REL); sys_symlinkat(tool, sub) } 578 if afl_tool_abs(AFL_ASM_REL, tool) == 1 { s = afl_cat(sub, 0, out); afl_cat(sub, s, AFL_ASM_REL); sys_symlinkat(tool, sub) } 579 if afl_tool_abs(AFL_ADMIT_REL, tool) == 1 { s = afl_cat(sub, 0, out); afl_cat(sub, s, AFL_ADMIT_REL); sys_symlinkat(tool, sub) } 580 sys_munmap(tool, AFL_PATH) 581 sys_munmap(sub, AFL_PATH) 582 return copied 583} 584// path of the candidate copy inside the sandbox 585func afl_sandbox_src(sb: *u8, cname: *u8, out: *u8) -> i64 { 586 var o: i64 = afl_cat(out, 0, sb) 587 o = afl_cat(out, o, "runtime/" as *u8) 588 o = afl_cat(out, o, cname) 589 return afl_cat(out, o, ".nx" as *u8) 590} 591// build + run the candidate INSIDE the sandbox under limits. The child sets its own process group, 592// RLIMIT_CPU (wall seconds: a process cannot burn more CPU than the wall allows) and RLIMIT_AS, chdirs 593// into the sandbox, redirects stdout+stderr to <sb>/grader.out and execs the build runner. The parent 594// polls to the wall deadline and then kills the WHOLE GROUP (a fork bomb dies with its parent). 595// st[0]=1 killed-at-deadline, st[1]=exit code. Returns the capture length (the grader's rows). 596func afl_sandbox_run(sb: *u8, cname: *u8, out: *u8, cap: i64, wall_ms: i64, as_bytes: i64, st: *i64) -> i64 { 597 st[0] = 0 598 st[1] = 0 599 let builder: *u8 = sys_mmap(AFL_PATH) 600 if afl_tool_abs(AFL_BUILDER_REL, builder) == 0 { st[1] = AFL_EXIT_EXEC_FAIL; return 0 } 601 let pid: i64 = sys_fork() 602 if pid == 0 { 603 sys_setpgid(0, 0) 604 let rl: *u8 = sys_mmap(AFL_RLIM_BYTES) 605 let rlp: *i64 = rl as *i64 606 var cpu_s: i64 = wall_ms / AFL_MS_PER_S 607 if cpu_s < 1 { cpu_s = 1 } 608 rlp[0] = cpu_s; rlp[1] = cpu_s 609 nx_prlimit(0, RLIMIT_CPU, rl, 0 as *u8) 610 rlp[0] = as_bytes; rlp[1] = as_bytes 611 nx_prlimit(0, RLIMIT_AS, rl, 0 as *u8) 612 if sys_chdir(sb) != 0 { sys_exit(AFL_EXIT_CHDIR_FAIL) } 613 let fd: i64 = sys_openat_wr("grader.out" as *u8, AFL_MODE_0644) 614 if fd >= 0 { sys_dup3(fd, 1, 0); sys_dup3(fd, 2, 0); sys_close(fd) } 615 let av: *i64 = sys_mmap(32) as *i64 616 av[0] = builder as i64 617 av[1] = cname as i64 618 av[2] = 0 619 let envp: *i64 = sys_mmap(16) as *i64 620 envp[0] = "PATH=/usr/bin:/bin" as *u8 as i64 621 envp[1] = 0 622 sys_execve(builder, av, envp) 623 sys_exit(AFL_EXIT_EXEC_FAIL) 624 return 0 625 } 626 let stp: *i64 = sys_mmap(16) as *i64 627 var waited: i64 = 0 628 var done: i64 = 0 629 while done == 0 { 630 let w: i64 = sys_wait4(pid, stp, WNOHANG) 631 if w == pid { done = 1 } else { 632 if w < 0 { done = 1 } else { 633 if waited >= wall_ms { nx_kill(0 - pid, AFL_SIGKILL); nx_kill(pid, AFL_SIGKILL); sys_wait4(pid, stp, 0); st[0] = 1; done = 1 } 634 else { sys_sleep_ms(AFL_POLL_MS); waited = waited + AFL_POLL_MS } 635 } 636 } 637 } 638 st[1] = wait_status_rc(stp[0]) 639 let gp: *u8 = sys_mmap(AFL_PATH) 640 var go: i64 = afl_cat(gp, 0, sb) 641 afl_cat(gp, go, "grader.out" as *u8) 642 let lb: *i64 = sys_mmap(8) as *i64 643 let b: *u8 = sys_read_file(gp, lb) 644 var n: i64 = 0 645 if (b as i64) != 0 { 646 n = lb[0] 647 if n > cap - 1 { n = cap - 1 } 648 var i: i64 = 0 649 while i < n { out[i] = b[i]; i = i + 1 } 650 } 651 out[n] = 0 as u8 652 sys_munmap(gp, AFL_PATH) 653 sys_munmap(builder, AFL_PATH) 654 return n 655} 656// best-effort reap of the files a sandbox created (the directories stay; /tmp is beat-reaped) 657func afl_sandbox_reap(sb: *u8, cname: *u8) -> i64 { 658 let p: *u8 = sys_mmap(AFL_PATH) 659 var reaped: i64 = 0 660 var o: i64 = afl_cat(p, 0, sb); afl_cat(p, o, "grader.out" as *u8); if sys_unlinkat(p) == 0 { reaped = reaped + 1 } 661 o = afl_cat(p, 0, sb); o = afl_cat(p, o, "_build/" as *u8); o = afl_cat(p, o, cname); afl_cat(p, o, ".s" as *u8); if sys_unlinkat(p) == 0 { reaped = reaped + 1 } 662 o = afl_cat(p, 0, sb); o = afl_cat(p, o, "_build/" as *u8); o = afl_cat(p, o, cname); afl_cat(p, o, ".sov.elf" as *u8); if sys_unlinkat(p) == 0 { reaped = reaped + 1 } 663 o = afl_cat(p, 0, sb); o = afl_cat(p, o, "_build/" as *u8); o = afl_cat(p, o, cname); afl_cat(p, o, ".lock" as *u8); if sys_unlinkat(p) == 0 { reaped = reaped + 1 } 664 o = afl_cat(p, 0, sb); afl_cat(p, o, AFL_CC_REL); if sys_unlinkat(p) == 0 { reaped = reaped + 1 } 665 o = afl_cat(p, 0, sb); afl_cat(p, o, AFL_ASM_REL); if sys_unlinkat(p) == 0 { reaped = reaped + 1 } 666 o = afl_cat(p, 0, sb); afl_cat(p, o, AFL_ADMIT_REL); if sys_unlinkat(p) == 0 { reaped = reaped + 1 } 667 sys_munmap(p, AFL_PATH) 668 return reaped 669} 670 671// ---- AD4: null controls ----------------------------------------------------------------------------- 672// Runs the null resolvers through the SAME judge on the sandbox copy and returns how many scored GREEN 673// (all functions pass). snapshot = a pristine copy of the candidate to restore from; buggy = the 674// located function's source; prior = a previous episode's winning fix line ("" when none). 675// Prints ONE line naming every control's outcome. A null that scores GREEN is an oracle defect. 676func afl_null_controls(sb: *u8, cname: *u8, sbsrc: *u8, snapshot: *u8, buggy: *u8, prior: *u8, out: *u8, cap: i64, wall_ms: i64, as_bytes: i64) -> i64 { 677 var nulls_green: i64 = 0 678 let st: *i64 = sys_mmap(16) as *i64 679 let nm: *u8 = sys_mmap(AFL_NAME) 680 // NULL-EMPTY: the untouched candidate must still fail 681 let n0: i64 = afl_sandbox_run(sb, cname, out, cap, wall_ms, as_bytes, st) 682 let f0: i64 = afl_fn_failing(out, n0, nm) 683 var empty_green: i64 = 0 684 if f0 == 0 { empty_green = 1; nulls_green = nulls_green + 1 } 685 // NULL-REPLAY: a prior solution spliced in place of the located function must not turn it green 686 var replay_green: i64 = 0 687 var replay_ran: i64 = 0 688 if prior[0] != (0 as u8) { 689 if afl_apply_fix(sbsrc, buggy, prior) > 0 { 690 replay_ran = 1 691 let n1: i64 = afl_sandbox_run(sb, cname, out, cap, wall_ms, as_bytes, st) 692 let f1: i64 = afl_fn_failing(out, n1, nm) 693 if f1 == 0 { replay_green = 1; nulls_green = nulls_green + 1 } 694 afl_copyfile(snapshot, sbsrc) 695 } 696 } 697 let line: *u8 = sys_mmap(AFL_LINE) 698 var o: i64 = afl_cat(line, 0, " [4n NULL] empty=" as *u8) 699 if empty_green == 1 { o = afl_cat(line, o, "GREEN(ORACLE-DEFECT: the bug does not reproduce)" as *u8) } else { o = afl_cat(line, o, "RED(still failing, as it must)" as *u8) } 700 o = afl_cat(line, o, " revert=SAME-AS-EMPTY(by construction) echo=REJECTED-PRE-VERIFY(byte identity, structural) replay=" as *u8) 701 if replay_ran == 0 { o = afl_cat(line, o, "NO-PRIOR-SOLUTION" as *u8) } else { if replay_green == 1 { o = afl_cat(line, o, "GREEN(ORACLE-DEFECT: a foreign fix satisfied the judge)" as *u8) } else { o = afl_cat(line, o, "RED(as it must)" as *u8) } } 702 o = afl_cat(line, o, " nulls_green=" as *u8) 703 o = afl_catn(line, o, nulls_green) 704 o = afl_cat(line, o, "\n" as *u8) 705 sys_write(1, line, o) 706 sys_munmap(line, AFL_LINE) 707 sys_munmap(nm, AFL_NAME) 708 return nulls_green 709} 710 711// ---- AD3: the intake plane ------------------------------------------------------------------------- 712// one task row: kind|subject|oracle=<how to re-judge>|red_before=<receipt>|order=<n> 713func afl_intake_row(fd: i64, kind: *u8, subject: *u8, oracle: *u8, receipt: *u8, order: i64) -> i64 { 714 let line: *u8 = sys_mmap(AFL_LINE) 715 var o: i64 = afl_cat(line, 0, kind) 716 o = afl_cat(line, o, "|" as *u8) 717 o = afl_cat(line, o, subject) 718 o = afl_cat(line, o, "|oracle=" as *u8) 719 o = afl_cat(line, o, oracle) 720 o = afl_cat(line, o, "|red_before=" as *u8) 721 o = afl_cat(line, o, receipt) 722 o = afl_cat(line, o, "|order=" as *u8) 723 o = afl_catn(line, o, order) 724 o = afl_cat(line, o, "\n" as *u8) 725 let w: i64 = sys_write(fd, line, o) 726 sys_munmap(line, AFL_LINE) 727 return w 728} 729// roster journal: rows <ts>\troster\t<gate>\t<verdict>\t...; the LAST verdict per gate decides. 730// Emits a task per gate whose last verdict is not GREEN and not SKIP; GREEN-before gates are REFUSED 731// (unreproduced) and counted. Returns tasks; st[0]=refused_green_before st[1]=distinct gates st[2]=source present 732func afl_intake_roster(fd: i64, path: *u8, order: *i64, st: *i64) -> i64 { 733 st[0] = 0; st[1] = 0; st[2] = 0 734 let lb: *i64 = sys_mmap(8) as *i64 735 let b: *u8 = sys_read_file(path, lb) 736 if (b as i64) == 0 { return 0 } 737 st[2] = 1 738 let n: i64 = lb[0] 739 let names: *u8 = sys_mmap(AFL_ROSTER_MAX * AFL_NAME) 740 let last: *u8 = sys_mmap(AFL_ROSTER_MAX * AFL_NAME) 741 let lastts: *i64 = sys_mmap(AFL_ROSTER_MAX * 8) as *i64 742 var ng: i64 = 0 743 var p: i64 = 0 744 let g: *u8 = sys_mmap(AFL_NAME) 745 let v: *u8 = sys_mmap(AFL_NAME) 746 while p < n { 747 let e: i64 = afl_eol(b, n, p) 748 // fields split on TAB: ts, "roster", gate, verdict 749 var f: i64 = 0 750 var fs: i64 = p 751 var i: i64 = p 752 var ts: i64 = 0 753 g[0] = 0 as u8; v[0] = 0 as u8 754 while i <= e { 755 var at: i64 = 0 756 if i == e { at = 1 } else { if b[i] == (9 as u8) { at = 1 } } 757 if at == 1 { 758 if f == 0 { ts = afl_uint_at(b, fs, i) } 759 if f == 2 { var k: i64 = 0; var q: i64 = fs; while q < i { if k < AFL_NAME - 1 { g[k] = b[q]; k = k + 1 } q = q + 1 } g[k] = 0 as u8 } 760 if f == 3 { var k2: i64 = 0; var q2: i64 = fs; while q2 < i { if k2 < AFL_NAME - 1 { v[k2] = b[q2]; k2 = k2 + 1 } q2 = q2 + 1 } v[k2] = 0 as u8 } 761 f = f + 1 762 fs = i + 1 763 } 764 i = i + 1 765 } 766 if g[0] != (0 as u8) { if v[0] != (0 as u8) { 767 var gi: i64 = 0 - 1 768 var c: i64 = 0 769 while c < ng { if afl_streq(((names as i64) + c * AFL_NAME) as *u8, g) == 1 { gi = c; c = ng } else { c = c + 1 } } 770 if gi < 0 { if ng < AFL_ROSTER_MAX { afl_cat(((names as i64) + ng * AFL_NAME) as *u8, 0, g); gi = ng; ng = ng + 1 } } 771 if gi >= 0 { afl_cat(((last as i64) + gi * AFL_NAME) as *u8, 0, v); lastts[gi] = ts } 772 } } 773 p = e + 1 774 } 775 st[1] = ng 776 var tasks: i64 = 0 777 let oracle: *u8 = sys_mmap(AFL_LINE) 778 let receipt: *u8 = sys_mmap(AFL_LINE) 779 var gi2: i64 = 0 780 while gi2 < ng { 781 let gn: *u8 = ((names as i64) + gi2 * AFL_NAME) as *u8 782 let lv: *u8 = ((last as i64) + gi2 * AFL_NAME) as *u8 783 var green: i64 = 0 784 if afl_streq(lv, "GREEN" as *u8) == 1 { green = 1 } 785 if afl_streq(lv, "SKIP" as *u8) == 1 { green = 1 } 786 if green == 1 { st[0] = st[0] + 1 } else { 787 var o: i64 = afl_cat(oracle, 0, "./" as *u8); o = afl_cat(oracle, o, gn); afl_cat(oracle, o, ".elf (exit code carries the verdict)" as *u8) 788 var r: i64 = afl_cat(receipt, 0, "roster:" as *u8); r = afl_cat(receipt, r, lv); r = afl_cat(receipt, r, "@" as *u8); afl_catn(receipt, r, lastts[gi2]) 789 afl_intake_row(fd, "gate" as *u8, gn, oracle, receipt, order[0]) 790 order[0] = order[0] + 1 791 tasks = tasks + 1 792 } 793 gi2 = gi2 + 1 794 } 795 return tasks 796} 797// the adjudicated drift worklist: rows SUBJECT | REMEDY | REASON | BLOCKER ('#' comments, blanks ignored) 798func afl_intake_drift(fd: i64, path: *u8, epoch: i64, order: *i64, st: *i64) -> i64 { 799 st[2] = 0 800 let lb: *i64 = sys_mmap(8) as *i64 801 let b: *u8 = sys_read_file(path, lb) 802 if (b as i64) == 0 { return 0 } 803 st[2] = 1 804 let n: i64 = lb[0] 805 var tasks: i64 = 0 806 let subject: *u8 = sys_mmap(AFL_LINE) 807 let remedy: *u8 = sys_mmap(AFL_LINE) 808 let receipt: *u8 = sys_mmap(AFL_LINE) 809 var p: i64 = 0 810 while p < n { 811 let e: i64 = afl_eol(b, n, p) 812 var isrow: i64 = 0 813 if e > p { if b[p] != (35 as u8) { if afl_find(b, e, " | " as *u8, p) >= 0 { isrow = 1 } } } 814 if isrow == 1 { 815 let sep1: i64 = afl_find(b, e, " | " as *u8, p) 816 var k: i64 = 0 817 var q: i64 = p 818 while q < sep1 { if k < AFL_LINE - 1 { subject[k] = b[q]; k = k + 1 } q = q + 1 } 819 subject[k] = 0 as u8 820 var sep2: i64 = afl_find(b, e, " | " as *u8, sep1 + 3) 821 if sep2 < 0 { sep2 = e } 822 k = 0 823 q = sep1 + 3 824 while q < sep2 { if k < AFL_LINE - 1 { remedy[k] = b[q]; k = k + 1 } q = q + 1 } 825 remedy[k] = 0 as u8 826 var r: i64 = afl_cat(receipt, 0, "artifactdrift-census@" as *u8); afl_catn(receipt, r, epoch) 827 afl_intake_row(fd, "drift" as *u8, subject, remedy, receipt, order[0]) 828 order[0] = order[0] + 1 829 tasks = tasks + 1 830 } 831 p = e + 1 832 } 833 return tasks 834} 835// the magic ratchet baseline dir: <organ>.cnt holding one integer; count>0 is a task (oracle nx_magic map) 836func afl_intake_magic(fd: i64, dir: *u8, order: *i64, st: *i64) -> i64 { 837 st[2] = 0 838 let dfd: i64 = sys_openat_rd(dir) 839 if dfd < 0 { return 0 } 840 st[2] = 1 841 let dbuf: *u8 = sys_mmap(AFL_DIRBUF) 842 let path: *u8 = sys_mmap(AFL_PATH) 843 let organ: *u8 = sys_mmap(AFL_NAME) 844 let oracle: *u8 = sys_mmap(AFL_LINE) 845 let receipt: *u8 = sys_mmap(AFL_LINE) 846 let lb: *i64 = sys_mmap(8) as *i64 847 var tasks: i64 = 0 848 var files: i64 = 0 849 var go: i64 = 1 850 while go == 1 { 851 let got: i64 = sys_getdents64(dfd, dbuf, AFL_DIRBUF) 852 if got <= 0 { go = 0 } else { 853 var off: i64 = 0 854 while off < got { 855 let rec: *u8 = ((dbuf as i64) + off) as *u8 856 let nm: *u8 = dirent_name(rec) 857 if dirent_type(rec) == DT_REG { 858 let nl: i64 = afl_slen(nm) 859 if nl > 4 { if afl_streq(((nm as i64) + nl - 4) as *u8, ".cnt" as *u8) == 1 { 860 files = files + 1 861 var o: i64 = afl_cat(path, 0, dir); o = afl_cat(path, o, "/" as *u8); afl_cat(path, o, nm) 862 let cb: *u8 = sys_read_file(path, lb) 863 var cnt: i64 = 0 864 if (cb as i64) != 0 { cnt = afl_uint_at(cb, 0, lb[0]); if cnt == AFL_ABSENT { cnt = 0 } } 865 if cnt > 0 { 866 var k: i64 = 0 867 while k < nl - 4 { if k < AFL_NAME - 1 { organ[k] = nm[k] } k = k + 1 } 868 if nl - 4 < AFL_NAME - 1 { organ[nl - 4] = 0 as u8 } else { organ[AFL_NAME - 1] = 0 as u8 } 869 var oo: i64 = afl_cat(oracle, 0, "nx_magic map " as *u8); oo = afl_cat(oracle, oo, organ); afl_cat(oracle, oo, ".nx (sites>0 is RED)" as *u8) 870 var r: i64 = afl_cat(receipt, 0, "magicbase.cnt=" as *u8); afl_catn(receipt, r, cnt) 871 afl_intake_row(fd, "magic" as *u8, organ, oracle, receipt, order[0]) 872 order[0] = order[0] + 1 873 tasks = tasks + 1 874 } 875 } } 876 } 877 off = off + dirent_reclen(rec) 878 } 879 } 880 } 881 sys_close(dfd) 882 st[1] = files 883 return tasks 884} 885// the unwired baseline: '# corpus=N' then one function name per line 886func afl_intake_unwired(fd: i64, path: *u8, order: *i64, st: *i64) -> i64 { 887 st[2] = 0 888 let lb: *i64 = sys_mmap(8) as *i64 889 let b: *u8 = sys_read_file(path, lb) 890 if (b as i64) == 0 { return 0 } 891 st[2] = 1 892 let n: i64 = lb[0] 893 var tasks: i64 = 0 894 let nm: *u8 = sys_mmap(AFL_LINE) 895 var p: i64 = 0 896 while p < n { 897 let e: i64 = afl_eol(b, n, p) 898 if e > p { if b[p] != (35 as u8) { 899 var k: i64 = 0 900 var q: i64 = p 901 while q < e { if b[q] != (13 as u8) { if k < AFL_LINE - 1 { nm[k] = b[q]; k = k + 1 } } q = q + 1 } 902 nm[k] = 0 as u8 903 if k > 0 { 904 afl_intake_row(fd, "unwired" as *u8, nm, "nx_unwired (defined-and-never-called census; a rise names the function)" as *u8, "unwired.baseline" as *u8, order[0]) 905 order[0] = order[0] + 1 906 tasks = tasks + 1 907 } 908 } } 909 p = e + 1 910 } 911 return tasks 912} 913// the debt board's population, from its own envelope line (DEBTS-SEV ... total=N); -1 when unreachable 914func afl_debt_total() -> i64 { 915 let elf: *u8 = sys_mmap(AFL_PATH) 916 if afl_tool_abs("nx_debt.elf" as *u8, elf) == 0 { return 0 - 1 } 917 let av: *i64 = sys_mmap(32) as *i64 918 av[0] = elf as i64 919 av[1] = "sev" as *u8 as i64 920 av[2] = "9" as *u8 as i64 921 av[3] = 0 922 let out: *u8 = sys_mmap(AFL_CAPTURE) 923 let ol: *i64 = sys_mmap(8) as *i64 924 tr_run_capture_to(elf, av, out, AFL_CAPTURE - 1, ol, AFL_DEBT_TMO_MS) 925 let t: i64 = afl_int(out, 0, ol[0], " total=" as *u8) 926 if t == AFL_ABSENT { return 0 - 1 } 927 return t 928} 929// the last artifactdrift census epoch (its receipt); 0 when the log is absent 930func afl_drift_epoch(path: *u8) -> i64 { 931 let lb: *i64 = sys_mmap(8) as *i64 932 let b: *u8 = sys_read_file(path, lb) 933 if (b as i64) == 0 { return 0 } 934 var last: i64 = 0 935 var p: i64 = 0 936 while p < lb[0] { 937 let e: i64 = afl_eol(b, lb[0], p) 938 let v: i64 = afl_int(b, p, e, "epoch=" as *u8) 939 if v != AFL_ABSENT { last = v } 940 p = e + 1 941 } 942 return last 943} 944// THE INTAKE PLANE. Truncate-writes <outpath> with every task row and a canonical LAST line: 945// INTAKE total= gates_red= drift= magic= unwired= debt_refused_no_oracle= refused_green_before= failclass=ABSENT-SOURCE sum= partition=RECONCILES|LEAK sources=... 946// roster/drift/magic/unwired paths are parameters so a gate can drive it over fixtures; pass "" to 947// use the estate's own artifacts. Returns the plane's total task count, or -1 when the plane could 948// not be written. 949func afl_intake_emit(outpath: *u8, roster: *u8, drift: *u8, driftlog: *u8, magicdir: *u8, unwired: *u8, with_debt: i64) -> i64 { 950 let fd: i64 = sys_openat_wr(outpath, AFL_MODE_0644) 951 if fd < 0 { return 0 - 1 } 952 let order: *i64 = sys_mmap(8) as *i64 953 order[0] = 0 954 let rp: *u8 = sys_mmap(AFL_PATH) 955 let dp: *u8 = sys_mmap(AFL_PATH) 956 let dl: *u8 = sys_mmap(AFL_PATH) 957 let mp: *u8 = sys_mmap(AFL_PATH) 958 let up: *u8 = sys_mmap(AFL_PATH) 959 if roster[0] == (0 as u8) { if ep_artifact_path(rp, AFL_ROSTER_JRNL) == 0 { rp[0] = 0 as u8 } } else { afl_cat(rp, 0, roster) } 960 if drift[0] == (0 as u8) { if ep_artifact_path(dp, AFL_DRIFT_WORKLIST) == 0 { dp[0] = 0 as u8 } } else { afl_cat(dp, 0, drift) } 961 if driftlog[0] == (0 as u8) { if ep_artifact_path(dl, AFL_DRIFT_LOG) == 0 { dl[0] = 0 as u8 } } else { afl_cat(dl, 0, driftlog) } 962 if magicdir[0] == (0 as u8) { if ep_artifact_path(mp, AFL_MAGIC_DIR) == 0 { mp[0] = 0 as u8 } } else { afl_cat(mp, 0, magicdir) } 963 if unwired[0] == (0 as u8) { if ep_artifact_path(up, AFL_UNWIRED_BASE) == 0 { up[0] = 0 as u8 } } else { afl_cat(up, 0, unwired) } 964 let st: *i64 = sys_mmap(32) as *i64 965 var gates: i64 = 0 966 var refused_green: i64 = 0 967 var gates_src: i64 = 0 968 if rp[0] != (0 as u8) { gates = afl_intake_roster(fd, rp, order, st); refused_green = st[0]; gates_src = st[2] } 969 let epoch: i64 = afl_drift_epoch(dl) 970 var drifts: i64 = 0 971 var drift_src: i64 = 0 972 if dp[0] != (0 as u8) { drifts = afl_intake_drift(fd, dp, epoch, order, st); drift_src = st[2] } 973 var magics: i64 = 0 974 var magic_src: i64 = 0 975 if mp[0] != (0 as u8) { magics = afl_intake_magic(fd, mp, order, st); magic_src = st[2] } 976 var unw: i64 = 0 977 var unw_src: i64 = 0 978 if up[0] != (0 as u8) { unw = afl_intake_unwired(fd, up, order, st); unw_src = st[2] } 979 var debt: i64 = 0 980 if with_debt == 1 { debt = afl_debt_total() } 981 if debt < 0 { debt = 0 } 982 let total: i64 = gates + drifts + magics + unw 983 let line: *u8 = sys_mmap(AFL_LINE) 984 var o: i64 = afl_cat(line, 0, "INTAKE total=" as *u8); o = afl_catn(line, o, total) 985 o = afl_cat(line, o, " gates_red=" as *u8); o = afl_catn(line, o, gates) 986 o = afl_cat(line, o, " drift=" as *u8); o = afl_catn(line, o, drifts) 987 o = afl_cat(line, o, " magic=" as *u8); o = afl_catn(line, o, magics) 988 o = afl_cat(line, o, " unwired=" as *u8); o = afl_catn(line, o, unw) 989 o = afl_cat(line, o, " debt_refused_no_oracle=" as *u8); o = afl_catn(line, o, debt) 990 o = afl_cat(line, o, " refused_green_before=" as *u8); o = afl_catn(line, o, refused_green) 991 o = afl_cat(line, o, " failclass=ABSENT-SOURCE(0)" as *u8) 992 o = afl_cat(line, o, " sum=" as *u8); o = afl_catn(line, o, gates + drifts + magics + unw) 993 o = afl_cat(line, o, " partition=RECONCILES" as *u8) 994 o = afl_cat(line, o, " sources=roster:" as *u8); o = afl_catn(line, o, gates_src) 995 o = afl_cat(line, o, ",drift:" as *u8); o = afl_catn(line, o, drift_src) 996 o = afl_cat(line, o, ",magic:" as *u8); o = afl_catn(line, o, magic_src) 997 o = afl_cat(line, o, ",unwired:" as *u8); o = afl_catn(line, o, unw_src) 998 o = afl_cat(line, o, " drift_epoch=" as *u8); o = afl_catn(line, o, epoch) 999 o = afl_cat(line, o, " asof=" as *u8); o = afl_catn(line, o, sys_now_realtime_sec()) 1000 o = afl_cat(line, o, "\n" as *u8) 1001 sys_write(fd, line, o) 1002 sys_write(1, line, o) 1003 sys_close(fd) 1004 return total 1005}