code wiki / (root) / nx_autofix_lib_t138.nx

nx_autofix_lib_t138.nx source

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