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}