code wiki / (root) / nx_ownership_gate.nx

nx_ownership_gate.nx source

↩ module page · 229 lines · 15186 B

1// nx_ownership_gate.nx -- GATE for LN4 ownership moves (own_check_move) and LN5 use-after-free 2// (own_check_uaf). ONE gate for both rungs because there is ONE checker: a move and a release are 3// the same event -- the name stops naming a live buffer -- and two gates over one state machine would 4// be two rulers for one invariant. 5// 6// THE RUNGS' DONE-RULES: a use after `__move(p)` is a compile error naming the local; a use after 7// `sys_munmap(p, n)` is a compile error naming the local; the existing corpus builds unchanged under 8// the default mode. 9// 10// SUBJECT IS argv[1] AND THERE IS NO DEFAULT. A gate that forks a compiler it was not given measures 11// whatever happens to be installed and prints a fully-formed verdict about it -- the sibling compiler 12// gates default to the live builder, and that default is exactly how a stale binary earns a green. 13// Refusing costs one argument and removes the whole failure class, so this gate exits 3 (UNKNOWN -- 14// "I could not look", never an acquittal) rather than guess. 15// 16// THE BITE IS TWO REAL COMPILERS, not a synthesised mutant: GREEN on a challenger built from a tree 17// carrying the rung, RED on _offc/nx_cc_sovereign.elf (the live builder, which has no --ownership). 18// 19// Data captures in /tmp/nxown/, RUNNABLES under _build/ -- the NAS mounts /tmp noexec, so a runnable 20// staged there passes every compile tooth and fails every run tooth with no diagnostic at all. 21// Usage: nx_ownership_gate <compiler_elf> (CWD = the tree root) 22// license_tier: ORIGINAL No hw writes (Rule 26). 23import "nx_syscalls.nx" 24import "nx_gate_verdict.nx" 25import "nx_ccgate_lib.nx" 26import "nx_ccbuild_lib.nx" 27import "nx_lineconf_lib.nx" 28 29const OWNG_USAGE_EXIT: i64 = 3 30const OWNG_CONF: *u8 = "knowledge/ownership_gate.conf\x00" 31 32func main(argc: i64, argv: *i64) -> i64 { 33 // SUBJECT RESOLUTION, THREE WAYS, AND IT SAYS WHICH ONE IT USED. argv[1] wins; else the DECLARED 34 // row in knowledge/ownership_gate.conf (read line-anchored by the one conf reader, lcf_str_of); 35 // else exit 3 UNKNOWN. A baked-in default is the failure class -- the gate would measure whatever 36 // binary happens to be installed and print a confident verdict about it. A bare refusal was the 37 // opposite failure: MEASURED here, nx_gate_bite reported UNCONTROLLED and the gate roster forks 38 // its rows BARE, so the gate was unrunnable by every harness in the estate. A declaration answers 39 // both, and printing the provenance is what stops the declaration becoming the silent default. 40 // Path buffer is CCG_PATH_CAP -- the estate's existing per-run path cap, not a fresh guess. 41 ccg_anchor_root() 42 var cc: *u8 = 0 as *u8 43 var how: *u8 = "argv\x00" 44 if argc >= 2 { cc = argv[1] as *u8 } 45 if argc < 2 { 46 let cbuf: *u8 = sys_mmap(CCG_PATH_CAP) 47 // KEY WITHOUT THE '=' -- lcf_find appends it itself (nx_lineconf_lib.nx:32 requires buf[i+k] 48 // to be '=' AFTER the key matches). Passing "subject=" searched for `subject==` and missed, 49 // and the miss surfaced as a confident "no subject declared" REFUSAL rather than an error: 50 // a format mismatch fails as a wrong answer, never as a diagnostic. Read the parser's 51 // contract before choosing the producer's format. 52 let n: i64 = lcf_str_of(OWNG_CONF, "subject\x00" as *u8, cbuf, CCG_PATH_CAP) 53 if n > 0 { cc = cbuf; how = "conf\x00" } 54 } 55 if (cc as i64) == 0 { 56 gv_puts("nx_ownership_gate: REFUSED -- no compiler given and no subject declared.\n" as *u8) 57 gv_puts(" usage: nx_ownership_gate <compiler_elf> e.g. _build/nx_own_cc.elf (a challenger) or _offc/nx_cc_sovereign.elf (the live builder)\n" as *u8) 58 gv_puts(" or declare one: a `subject=<path>` row in knowledge/ownership_gate.conf, which is what every harness that forks this gate BARE will read.\n" as *u8) 59 gv_puts(" why refused rather than defaulted: a compiler gate that picks its own subject reports a confident verdict about whatever binary happens to be installed. That is how a stale artifact earns a green, and it is indistinguishable from a real measurement afterwards.\n" as *u8) 60 gv_puts("verdict=UNKNOWN\n" as *u8) 61 sys_exit(OWNG_USAGE_EXIT) 62 return OWNG_USAGE_EXIT 63 } 64 let FLAG: *u8 = "--ownership\x00" 65 sys_mkdir("/tmp/nxown\x00" as *u8, CCG_MODE_X) 66 let pid: i64 = ccg_pid() 67 68 // hazard (h) / positive control (c) / alias (a) / double-free (f) x default (d) / mode (m) 69 let p_hds: *u8 = ccg_path("/tmp/nxown/hd_\x00" as *u8, pid, ".s\x00" as *u8) 70 let p_hde: *u8 = ccg_path("/tmp/nxown/hd_\x00" as *u8, pid, ".err\x00" as *u8) 71 let p_hdelf: *u8 = ccg_path("_build/nxown_hd_\x00" as *u8, pid, ".elf\x00" as *u8) 72 let p_hms: *u8 = ccg_path("/tmp/nxown/hm_\x00" as *u8, pid, ".s\x00" as *u8) 73 let p_hme: *u8 = ccg_path("/tmp/nxown/hm_\x00" as *u8, pid, ".err\x00" as *u8) 74 let p_hmelf: *u8 = ccg_path("_build/nxown_hm_\x00" as *u8, pid, ".elf\x00" as *u8) 75 let p_cds: *u8 = ccg_path("/tmp/nxown/cd_\x00" as *u8, pid, ".s\x00" as *u8) 76 let p_cde: *u8 = ccg_path("/tmp/nxown/cd_\x00" as *u8, pid, ".err\x00" as *u8) 77 let p_cdelf: *u8 = ccg_path("_build/nxown_cd_\x00" as *u8, pid, ".elf\x00" as *u8) 78 let p_cms: *u8 = ccg_path("/tmp/nxown/cm_\x00" as *u8, pid, ".s\x00" as *u8) 79 let p_cme: *u8 = ccg_path("/tmp/nxown/cm_\x00" as *u8, pid, ".err\x00" as *u8) 80 let p_cmelf: *u8 = ccg_path("_build/nxown_cm_\x00" as *u8, pid, ".elf\x00" as *u8) 81 let p_ads: *u8 = ccg_path("/tmp/nxown/ad_\x00" as *u8, pid, ".s\x00" as *u8) 82 let p_ade: *u8 = ccg_path("/tmp/nxown/ad_\x00" as *u8, pid, ".err\x00" as *u8) 83 let p_adelf: *u8 = ccg_path("_build/nxown_ad_\x00" as *u8, pid, ".elf\x00" as *u8) 84 let p_ams: *u8 = ccg_path("/tmp/nxown/am_\x00" as *u8, pid, ".s\x00" as *u8) 85 let p_ame: *u8 = ccg_path("/tmp/nxown/am_\x00" as *u8, pid, ".err\x00" as *u8) 86 let p_amelf: *u8 = ccg_path("_build/nxown_am_\x00" as *u8, pid, ".elf\x00" as *u8) 87 let p_fds: *u8 = ccg_path("/tmp/nxown/fd_\x00" as *u8, pid, ".s\x00" as *u8) 88 let p_fde: *u8 = ccg_path("/tmp/nxown/fd_\x00" as *u8, pid, ".err\x00" as *u8) 89 let p_fdelf: *u8 = ccg_path("_build/nxown_fd_\x00" as *u8, pid, ".elf\x00" as *u8) 90 let p_fms: *u8 = ccg_path("/tmp/nxown/fm_\x00" as *u8, pid, ".s\x00" as *u8) 91 let p_fme: *u8 = ccg_path("/tmp/nxown/fm_\x00" as *u8, pid, ".err\x00" as *u8) 92 let p_fmelf: *u8 = ccg_path("_build/nxown_fm_\x00" as *u8, pid, ".elf\x00" as *u8) 93 let p_asm: *u8 = ccg_path("_build/nxown_asm_\x00" as *u8, pid, ".elf\x00" as *u8) 94 95 let hfx: *u8 = "runtime/nx_ownership.nx\x00" 96 let cfx: *u8 = "runtime/nx_ownership_ok.nx\x00" 97 let afx: *u8 = "runtime/nx_ownership_alias.nx\x00" 98 let ffx: *u8 = "runtime/nx_ownership_dfree.nx\x00" 99 100 let ctr: *i64 = gv_ctr() 101 gv_head("=== nx_ownership_gate -- LN4 own_check_move + LN5 own_check_uaf: --ownership refuses use-after-move and use-after-free naming the local and the line that ended its life, and every accept idiom still compiles and runs ===" as *u8) 102 gv_puts(" compiler_under_test=" as *u8) 103 gv_puts(cc) 104 gv_puts(" subject_resolved_by=" as *u8) 105 gv_puts(how) 106 gv_puts("\n" as *u8) 107 108 // ---- default mode: the record that all four hazards are LEGAL SOURCE TODAY ---- 109 let bhd: i64 = ccg_build(cc, 0 as *u8, hfx, p_hds, p_hdelf, p_hde, p_asm) 110 let bcd: i64 = ccg_build(cc, 0 as *u8, cfx, p_cds, p_cdelf, p_cde, p_asm) 111 let bad: i64 = ccg_build(cc, 0 as *u8, afx, p_ads, p_adelf, p_ade, p_asm) 112 let bfd: i64 = ccg_build(cc, 0 as *u8, ffx, p_fds, p_fdelf, p_fde, p_asm) 113 var hd_run: i64 = 0 - 1 114 var cd_run: i64 = 0 - 1 115 var ad_run: i64 = 0 - 1 116 var fd_run: i64 = 0 - 1 117 if bhd == 0 { hd_run = ccg_phase(p_hdelf, 0 as *u8) } 118 if bcd == 0 { cd_run = ccg_phase(p_cdelf, 0 as *u8) } 119 if bad == 0 { ad_run = ccg_phase(p_adelf, 0 as *u8) } 120 if bfd == 0 { fd_run = ccg_phase(p_fdelf, 0 as *u8) } 121 ccg_val("hazard_default_build_rc" as *u8, bhd) 122 ccg_val("hazard_default_exit" as *u8, hd_run) 123 ccg_val("control_default_build_rc" as *u8, bcd) 124 ccg_val("control_default_exit" as *u8, cd_run) 125 ccg_val("alias_default_build_rc" as *u8, bad) 126 ccg_val("alias_default_exit" as *u8, ad_run) 127 ccg_val("dfree_default_build_rc" as *u8, bfd) 128 ccg_val("dfree_default_exit" as *u8, fd_run) 129 130 var t1: i64 = 0 131 if bhd == 0 { t1 = 1 } 132 gv_check("T1 neg-control-use-after-move-and-use-after-free-COMPILE-under-the-default (they are legal source today -- the gap this rung closes)" as *u8, t1, ctr) 133 var t2: i64 = 0 134 if hd_run == 0 { t2 = 1 } 135 gv_check("T2 neg-control-hazard-RUNS-exit0-under-the-default (right by an arena accident, not by correctness: 64 bytes is below NXA_SMALL_MAX so the release is a no-op)" as *u8, t2, ctr) 136 var t3: i64 = 0 137 if bcd == 0 { t3 = 1 } 138 gv_check("T3 positive-control-compiles-under-the-default" as *u8, t3, ctr) 139 var t4: i64 = 0 140 if cd_run == 0 { t4 = 1 } 141 gv_check("T4 positive-control-runs-exit0-under-the-default" as *u8, t4, ctr) 142 var t5: i64 = 0 143 if bad == 0 { if ad_run == 0 { t5 = 1 } } 144 gv_check("T5 neg-control-alias-use-after-free-compiles-AND-runs-exit0-under-the-default" as *u8, t5, ctr) 145 var t6: i64 = 0 146 if bfd == 0 { if fd_run == 0 { t6 = 1 } } 147 gv_check("T6 neg-control-double-free-compiles-AND-runs-exit0-under-the-default (a double free is completely invisible at runtime today)" as *u8, t6, ctr) 148 149 // ---- ownership mode ---- 150 let bhm: i64 = ccg_build(cc, FLAG, hfx, p_hms, p_hmelf, p_hme, p_asm) 151 let bcm: i64 = ccg_build(cc, FLAG, cfx, p_cms, p_cmelf, p_cme, p_asm) 152 let bam: i64 = ccg_build(cc, FLAG, afx, p_ams, p_amelf, p_ame, p_asm) 153 let bfm: i64 = ccg_build(cc, FLAG, ffx, p_fms, p_fmelf, p_fme, p_asm) 154 var cm_run: i64 = 0 - 1 155 if bcm == 0 { cm_run = ccg_phase(p_cmelf, 0 as *u8) } 156 ccg_val("hazard_mode_build_rc" as *u8, bhm) 157 ccg_val("control_mode_build_rc" as *u8, bcm) 158 ccg_val("control_mode_exit" as *u8, cm_run) 159 ccg_val("alias_mode_build_rc" as *u8, bam) 160 ccg_val("dfree_mode_build_rc" as *u8, bfm) 161 162 // BOUND TO ITS DENOMINATOR, and the baseline control is what found this: `bhm != 0` on its own 163 // PASSES on any compiler that refuses this fixture for ANY reason -- MEASURED on the live builder, 164 // which refuses it merely because `__move` is not a name it knows. A refusal the mode did not 165 // cause is not evidence for the mode, so the claim is the PAIR: clean under the default, refused 166 // under --ownership. 167 var t7: i64 = 0 168 if bhm != 0 { if bhd == 0 { t7 = 1 } } 169 gv_check("T7 ownership-REFUSES-a-hazard-unit-THE-DEFAULT-ACCEPTED (the two rungs done-rule bound to its denominator: the MODE is what refuses, not broken source)" as *u8, t7, ctr) 170 let t8: i64 = ccg_file_has(p_hme, "capability=own-check-move\x00" as *u8) 171 gv_check("T8 move-refusal-stamps-capability-own-check-move (WHICH rule fired -- a refusal for some other reason is not evidence for this rung)" as *u8, t8, ctr) 172 let t9: i64 = ccg_file_has(p_hme, "use of 'buf' after its ownership was moved away\x00" as *u8) 173 gv_check("T9 move-refusal-names-the-dead-local-by-name" as *u8, t9, ctr) 174 let t10a: i64 = ccg_file_has(p_hme, "moved away on line \x00" as *u8) 175 let t10b: i64 = ccg_file_has(p_hme, "moved away on line 0\x00" as *u8) 176 var t10: i64 = 0 177 if t10a == 1 { if t10b == 0 { t10 = 1 } } 178 ccg_val("move_line_clause_present" as *u8, t10a) 179 ccg_val("move_line_is_zero" as *u8, t10b) 180 gv_check("T10 move-refusal-names-a-REAL-move-line-not-zero (both ends of the defect: the use AND the statement that ended the local's life)" as *u8, t10, ctr) 181 let t11: i64 = ccg_file_has(p_hme, "capability=own-check-uaf\x00" as *u8) 182 gv_check("T11 release-refusal-stamps-capability-own-check-uaf-in-the-SAME-unit (the two halves are independently attributable, not one rule wearing two names)" as *u8, t11, ctr) 183 let t12a: i64 = ccg_file_has(p_hme, "released on line \x00" as *u8) 184 let t12b: i64 = ccg_file_has(p_hme, "released on line 0\x00" as *u8) 185 var t12: i64 = 0 186 if t12a == 1 { if t12b == 0 { t12 = 1 } } 187 ccg_val("release_line_clause_present" as *u8, t12a) 188 ccg_val("release_line_is_zero" as *u8, t12b) 189 gv_check("T12 release-refusal-names-a-REAL-release-line-not-zero" as *u8, t12, ctr) 190 191 var t13: i64 = 0 192 if bcm == 0 { t13 = 1 } 193 gv_check("T13 POSITIVE-CONTROL-COMPILES-under-ownership (move-then-silence, release-then-silence, re-init after both, untracked pointers -- AND the whole imported stdlib. A deny-everything checker dies HERE, and so does a broken fixture path or a noexec runnable dir)" as *u8, t13, ctr) 194 var t14: i64 = 0 195 if cm_run == 0 { t14 = 1 } 196 gv_check("T14 POSITIVE-CONTROL-RUNS-exit0-under-ownership (the accepted program is still the right program)" as *u8, t14, ctr) 197 198 // The mode emits NO IR: the positive control's asm under the flag is byte-identical to its asm 199 // under the default. This is what makes the default build byte-identical BY CONSTRUCTION rather 200 // than by hope, and cb_files_equal is the estate's one byte-comparer -- no second copy here. 201 // NON-EMPTY IS PART OF THE CLAIM. cb_files_equal returns 1 for two UNREADABLE files, so when both 202 // compiles fail this tooth compared nothing and reported equality -- MEASURED on the live builder: 203 // control_asm_default_bytes=0, control_asm_mode_bytes=0, T15 PASS. Two failures agreeing read as an 204 // equivalence -- the exact defect nx_ccbuild_lib's own header warns about -- sitting inside the one 205 // tooth that certifies byte-neutrality. A tooth that passes on the empty set is not a tooth. 206 let cds_n: i64 = cb_fsize(p_cds) 207 let cms_n: i64 = cb_fsize(p_cms) 208 var t15: i64 = 0 209 if cds_n > 0 { if cms_n > 0 { t15 = cb_files_equal(p_cds, p_cms) } } 210 ccg_val("control_asm_default_bytes" as *u8, cds_n) 211 ccg_val("control_asm_mode_bytes" as *u8, cms_n) 212 gv_check("T15 mode-emits-NO-IR (positive-control asm byte-identical default vs --ownership: this rung is compile-time only, so a default build cannot change)" as *u8, t15, ctr) 213 214 var t16: i64 = 0 215 if bam != 0 { if bad == 0 { t16 = 1 } } 216 gv_check("T16 alias-sweep-REFUSES-a-use-through-a-SECOND-name-THE-DEFAULT-ACCEPTED (a checker that marked only the name inside the sys_munmap would report nothing at all here)" as *u8, t16, ctr) 217 let t17: i64 = ccg_file_has(p_ame, "use of 'q'\x00" as *u8) 218 gv_check("T17 alias-refusal-names-the-ALIAS-q-at-the-use-not-the-released-name-p" as *u8, t17, ctr) 219 220 var t18: i64 = 0 221 if bfm != 0 { if bfd == 0 { t18 = 1 } } 222 gv_check("T18 double-free-REFUSED-WHERE-THE-DEFAULT-ACCEPTED-IT (CWE-415 falls out of the SAME use-after-release rule -- the second release must read the name to pass it)" as *u8, t18, ctr) 223 let t19: i64 = ccg_file_has(p_fme, "capability=own-check-uaf\x00" as *u8) 224 gv_check("T19 double-free-refusal-stamps-own-check-uaf-and-NOT-a-separate-rule" as *u8, t19, ctr) 225 226 let rc: i64 = gv_verdict("OWNERSHIP-LN4-LN5" as *u8, ctr, "ownership declared mode" as *u8) 227 sys_exit(rc) 228 return rc 229}