nx_toolreg_reap_gate.nx source
↩ module page · 135 lines · 6666 B
1// nx_toolreg_reap_gate.nx -- proof that the reaper removes what it should and REFUSES what it must not.
2//
3// The teeth that matter are the NEGATIVE ones. A reaper that removes everything asked of it is a
4// registry-deletion oracle; the whole safety claim is that it discriminates by TARGET SHAPE, so T2
5// (a live .elf row survives an explicit reap request) and T4 (unrelated rows byte-preserved) are what
6// make T1 mean anything. T5 checks the no-op path writes NOTHING -- rewriting a shared conf to change
7// nothing is how a concurrent reader gets a truncated read for no reason.
8// license_tier: ORIGINAL expect_exit: 0
9import "nx_syscalls.nx"
10
11func rg_w(s: *u8) -> i64 { var n: i64=0; while s[n]!=(0 as u8){n=n+1} sys_write(1,s,n); return 0 }
12func rg_slen(s: *u8) -> i64 { var n: i64=0; while s[n]!=(0 as u8){n=n+1} return n }
13func rg_wn(v: i64) -> i64 {
14 let b: *u8=sys_mmap(32); var x: i64=v; var i: i64=31
15 if x==0 { b[i]=48 as u8; i=i-1 }
16 while x>0 { b[i]=(48+x%10) as u8; x=x/10; i=i-1 }
17 sys_write(1, ((b as i64)+i+1) as *u8, 31-i); return 0
18}
19func rg_wfile(path: *u8, content: *u8) -> i64 {
20 let fd: i64=sys_openat_wr(path, 0x1a4)
21 if fd<0 { return 0-1 }
22 sys_write(fd, content, rg_slen(content))
23 sys_close(fd)
24 return 0
25}
26func rg_read(path: *u8, buf: *u8, cap: i64) -> i64 {
27 let fd: i64=sys_openat_rd(path)
28 if fd<0 { return 0-1 }
29 var n: i64=0
30 var go: i64=1
31 while go==1 {
32 let r: i64=sys_read(fd, ((buf as i64)+n) as *u8, cap-n)
33 if r<=0 { go=0 } else { n=n+r }
34 if n>=cap { go=0 }
35 }
36 sys_close(fd)
37 return n
38}
39func rg_has(b: *u8, n: i64, s: *u8) -> i64 {
40 let sl: i64=rg_slen(s)
41 if sl==0 { return 1 }
42 var i: i64=0
43 while i+sl<=n {
44 var m: i64=1; var j: i64=0
45 while j<sl { if b[i+j]!=s[j] { m=0; j=sl } else { j=j+1 } }
46 if m==1 { return 1 }
47 i=i+1
48 }
49 return 0
50}
51// run the reaper from `dir` with up to two names
52func rg_run(elf: *u8, dir: *u8, a1: *u8, a2: *u8) -> i64 {
53 let pid: i64=sys_fork()
54 if pid<0 { return 201 }
55 if pid==0 {
56 sys_chdir(dir)
57 let av: *i64=sys_mmap(64) as *i64
58 av[0]=elf as i64
59 av[1]=a1 as i64
60 if (a2 as i64)==0 { av[2]=0 } else { av[2]=a2 as i64; av[3]=0 }
61 sys_execve(elf, av, 0 as *i64)
62 sys_exit(127)
63 return 127
64 }
65 let stp: *i64=sys_mmap(16) as *i64
66 sys_wait4(pid, stp, 0)
67 return (stp[0]>>8)&255
68}
69
70func main(argc: i64, argv: *i64) -> i64 {
71 rg_w("=== nx_toolreg_reap_gate: reaps scaffolds, REFUSES live tools ===\n" as *u8)
72 var reaper: *u8 = "/volume1/homes/elderwesto/nishihost/nx_toolreg_reap.elf\x00" as *u8
73 if argc>=2 { reaper = argv[1] as *u8 }
74 rg_w("reaper-under-test: " as *u8); rg_w(reaper); rg_w("\n" as *u8)
75 var fails: i64=0
76
77 sys_mkdir("/tmp/trx\x00" as *u8, 0x1ed)
78 // A fixture registry: one scaffold (staged .new), one LIVE tool, one staged row we never name,
79 // and a row whose name is a strict PREFIX of the scaffold's -- the exact-match trap.
80 let conf: *u8 = "nx_keepme\t/volume1/x/nx_keepme.elf\tGREEN\nnx_scaffold_r11\t/volume1/x/nx_thing.sov.elf.new\tGREEN\nnx_scaffold_r1\t/volume1/x/nx_other.sov.elf.new\tGREEN\nnx_livetool\t/volume1/x/nx_livetool.elf\tGREEN\nnx_pinned\t/volume1/x/nx_pinned.elf\tGREEN\tsub arg\n\x00" as *u8
81 rg_wfile("/tmp/trx/tool_allowlist.conf\x00" as *u8, conf)
82
83 // T1 the scaffold goes
84 let rc1: i64 = rg_run(reaper, "/tmp/trx\x00" as *u8, "nx_scaffold_r11\x00" as *u8, 0 as *u8)
85 let b: *u8 = sys_mmap(65536)
86 var n: i64 = rg_read("/tmp/trx/tool_allowlist.conf\x00" as *u8, b, 65530)
87 var t1: i64=0
88 if rc1==0 { if n>0 { if rg_has(b, n, "nx_scaffold_r11\t\x00" as *u8)==0 { t1=1 } } }
89 if t1==1 { rg_w(" [PASS] " as *u8) } else { fails=fails+1; rg_w(" [FAIL] " as *u8) }
90 rg_w("T1 a staged .new scaffold row is REMOVED (rc=" as *u8); rg_wn(rc1); rg_w(")\n" as *u8)
91
92 // T2 THE TOOTH: the prefix-named sibling must SURVIVE -- exact field-0 match, never a prefix match
93 var t2: i64=0
94 if rg_has(b, n, "nx_scaffold_r1\t\x00" as *u8)==1 { t2=1 }
95 if t2==1 { rg_w(" [PASS] " as *u8) } else { fails=fails+1; rg_w(" [FAIL] " as *u8) }
96 rg_w("T2 TOOTH nx_scaffold_r1 SURVIVES reaping nx_scaffold_r11 (exact match, not prefix)\n" as *u8)
97
98 // T3 THE SAFETY TOOTH: an explicit request to reap a LIVE .elf row is REFUSED and the row stays
99 let rc3: i64 = rg_run(reaper, "/tmp/trx\x00" as *u8, "nx_livetool\x00" as *u8, 0 as *u8)
100 n = rg_read("/tmp/trx/tool_allowlist.conf\x00" as *u8, b, 65530)
101 var t3: i64=0
102 if rc3==0 { if rg_has(b, n, "nx_livetool\t\x00" as *u8)==1 { t3=1 } }
103 if t3==1 { rg_w(" [PASS] " as *u8) } else { fails=fails+1; rg_w(" [FAIL] " as *u8) }
104 rg_w("T3 TOOTH a promoted .elf row is REFUSED even when named explicitly -- cannot unregister a live tool\n" as *u8)
105
106 // T4 every unrelated row survives, INCLUDING the 4-field pinned-args row
107 var t4: i64=0
108 if rg_has(b, n, "nx_keepme\t\x00" as *u8)==1 {
109 if rg_has(b, n, "nx_pinned\t/volume1/x/nx_pinned.elf\tGREEN\tsub arg\x00" as *u8)==1 { t4=1 } }
110 if t4==1 { rg_w(" [PASS] " as *u8) } else { fails=fails+1; rg_w(" [FAIL] " as *u8) }
111 rg_w("T4 unrelated rows survive byte-for-byte, including a 4-field pinned-args row\n" as *u8)
112
113 // T5 an unknown name is a NO-OP that writes nothing (rewriting a shared conf to change nothing
114 // is how a concurrent reader gets a torn read for free)
115 let rc5: i64 = rg_run(reaper, "/tmp/trx\x00" as *u8, "nx_does_not_exist\x00" as *u8, 0 as *u8)
116 let b2: *u8 = sys_mmap(65536)
117 let n2: i64 = rg_read("/tmp/trx/tool_allowlist.conf\x00" as *u8, b2, 65530)
118 var t5: i64=0
119 if rc5==0 { if n2==n { t5=1 } }
120 if t5==1 { rg_w(" [PASS] " as *u8) } else { fails=fails+1; rg_w(" [FAIL] " as *u8) }
121 rg_w("T5 an unknown name is a NO-OP, byte count unchanged (" as *u8); rg_wn(n2); rg_w(")\n" as *u8)
122
123 // T6 fail-closed: no allowlist at all = exit 1, never a silent empty write
124 sys_mkdir("/tmp/trx_bare\x00" as *u8, 0x1ed)
125 let rc6: i64 = rg_run(reaper, "/tmp/trx_bare\x00" as *u8, "nx_anything\x00" as *u8, 0 as *u8)
126 var t6: i64=0
127 if rc6==1 { t6=1 }
128 if t6==1 { rg_w(" [PASS] " as *u8) } else { fails=fails+1; rg_w(" [FAIL] " as *u8) }
129 rg_w("T6 no allowlist = RED exit 1, never a silent empty write (rc=" as *u8); rg_wn(rc6); rg_w(")\n" as *u8)
130
131 if fails==0 { rg_w("TOOLREG-REAP GREEN 6/6 -- removes scaffolds, refuses live tools, no-ops safely\n" as *u8); sys_exit(0); return 0 }
132 rg_w("TOOLREG-REAP RED fails=" as *u8); rg_wn(fails); rg_w("\n" as *u8)
133 sys_exit(1)
134 return 1
135}