nx_tool_exec_allow_gate.nx source
↩ module page · 125 lines · 8047 B
1// nx_tool_exec_allow_gate.nx -- GATE for R1 (nx_tool_exec_allow), composing R0 (nx_tool_run). Proves the
2// execution allowlist is fail-closed and never-brick: only an allowlisted, gate-GREEN row resolves to an ELF
3// path and runs; a non-GREEN row is present-but-blocked; an unknown name and a traversal name ("../x") both
4// refuse; and a GREEN row actually EXECUTES its ELF and captures real stdout. Target = the canonical no-op
5// nx_tool_ping (a SEPARATE tiny ELF, zero recursion risk). Writes its allowlist to /tmp (never touches a
6// real config). PREREQ: build nx_tool_ping FIRST (produces _offc/nx_tool_ping.elf).
7// license_tier: ORIGINAL expect_exit: 0
8import "nx_tool_exec_allow.nx"
9import "nx_gate_verdict.nx"
10
11const TEA_TESTCONF: *u8 = "/tmp/nx_tea_test.conf" as *u8
12
13func ew(s: *u8) -> i64 { var n: i64=0; while s[n]!=(0 as u8){n=n+1} sys_write(1,s,n); return 0 }
14func en(v: i64) -> i64 { let bb: *u8=sys_mmap(28); var m: i64=v; if m<0{sys_write(1,"-" as *u8,1);m=0-m} let t: *u8=sys_mmap(28); var k: i64=0; if m==0{t[0]=48 as u8;k=1} while m>0{t[k]=(48+(m%10)) as u8;m=m/10;k=k+1} var i: i64=0; while i<k{bb[i]=t[k-1-i];i=i+1} sys_write(1,bb,k); return 0 }
15func e_streq(a: *u8, b: *u8) -> i64 { var i: i64=0; while a[i]!=(0 as u8){ if a[i]!=b[i]{return 0} i=i+1 } if b[i]!=(0 as u8){return 0} return 1 }
16func e_slen(s: *u8) -> i64 { var n: i64=0; while s[n]!=(0 as u8){n=n+1} return n }
17
18// write the test allowlist (O_WRONLY|O_CREAT|O_TRUNC=0x241, mode 0644) via openat(AT_FDCWD=-100)
19func tea_write_testconf(self:*u8) -> i64 {
20 let fd: i64 = __syscall(257, 0 - 100, TEA_TESTCONF, 0x241, 0x1a4, 0, 0)
21 if fd < 0 { return 0 - 1 }
22 let body: *u8 = "# nx execution allowlist (test)\nselftest\t_offc/nx_tool_ping.elf\tGREEN\nblocked\t_offc/nx_tool_ping.elf\tRED\npinned\t_offc/nx_tool_ping.elf\tGREEN\tnftread\nwrongstatus\t_offc/nx_tool_ping.elf\tGREENextra\tnftread\n" as *u8
23 sys_write(fd, body, e_slen(body))
24 let prefix:*u8="pinnedchild\t" as *u8
25 let suffix:*u8="\tGREEN\tnftread\n" as *u8
26 sys_write(fd,prefix,e_slen(prefix))
27 sys_write(fd,self,e_slen(self))
28 sys_write(fd,suffix,e_slen(suffix))
29 sys_close(fd)
30 return 0
31}
32
33func main(argc:i64, argv:*i64) -> i64 {
34 if argc>1 {
35 if argc==2 && e_streq(argv[1] as *u8,"nftread" as *u8)==1 { ew("PINNED_CHILD_OK\n" as *u8); return 0 }
36 return 43
37 }
38 ew("=== nx_tool_exec_allow_gate (R1: execution allowlist, fail-closed + never-brick, composing R0) ===\n" as *u8)
39 if tea_write_testconf(argv[0] as *u8) != 0 { ew("FAIL: could not write test allowlist\n" as *u8); sys_exit(1); return 1 }
40
41 var pass: i64 = 0
42 var tot: i64 = 0
43 let path: *u8 = sys_mmap(4096)
44
45 // T1: GREEN row resolves to its ELF path
46 tot = tot + 1
47 let r1: i64 = tea_resolve_from(TEA_TESTCONF, "selftest" as *u8, 8, path, 4096)
48 var ok1: i64 = 1
49 if r1 != TEA_OK { ok1 = 0 }
50 if e_streq(path, "_offc/nx_tool_ping.elf" as *u8) == 0 { ok1 = 0 }
51 if ok1 == 1 { pass = pass + 1; ew("PASS T1 GREEN row -> TEA_OK, path=_offc/nx_tool_ping.elf\n" as *u8) } else { ew("FAIL T1 rc=" as *u8); en(r1); ew(" path=" as *u8); ew(path); ew("\n" as *u8) }
52
53 // T2 NEG: non-GREEN row -> BLOCKED, empty path (listed but NOT runnable)
54 tot = tot + 1
55 path[0] = 0 as u8
56 let r2: i64 = tea_resolve_from(TEA_TESTCONF, "blocked" as *u8, 7, path, 4096)
57 var ok2: i64 = 1
58 if r2 != TEA_BLOCKED { ok2 = 0 }
59 if path[0] != (0 as u8) { ok2 = 0 }
60 if ok2 == 1 { pass = pass + 1; ew("PASS T2 non-GREEN row -> BLOCKED, empty path (present != runnable)\n" as *u8) } else { ew("FAIL T2 rc=" as *u8); en(r2); ew("\n" as *u8) }
61
62 // T3 NEG: unknown name -> NOTFOUND
63 tot = tot + 1
64 let r3: i64 = tea_resolve_from(TEA_TESTCONF, "ghost" as *u8, 5, path, 4096)
65 if r3 == TEA_NOTFOUND { pass = pass + 1; ew("PASS T3 unknown name -> NOTFOUND\n" as *u8) } else { ew("FAIL T3 rc=" as *u8); en(r3); ew("\n" as *u8) }
66
67 // T4 NEG (never-brick): a traversal name is refused by tea_name_ok BEFORE it can index anything
68 tot = tot + 1
69 let r4: i64 = tea_resolve_from(TEA_TESTCONF, "../etc/passwd" as *u8, 13, path, 4096)
70 var ok4: i64 = 1
71 if r4 != TEA_NOTFOUND { ok4 = 0 }
72 if path[0] != (0 as u8) { ok4 = 0 }
73 if ok4 == 1 { pass = pass + 1; ew("PASS T4 never-brick: traversal name '../etc/passwd' refused (no '/' reaches resolve)\n" as *u8) } else { ew("FAIL T4 rc=" as *u8); en(r4); ew("\n" as *u8) }
74
75 // T5: composed R0+R1 -- resolve the GREEN row AND execute it (no arg), capturing real stdout + exit code
76 tot = tot + 1
77 let out: *u8 = sys_mmap(65536)
78 let lenp: *i64 = sys_mmap(16) as *i64
79 let rcp: *i64 = sys_mmap(16) as *i64
80 let ec: i64 = tea_run_from(TEA_TESTCONF, "selftest" as *u8, 8, 0 as *u8, out, 65536, lenp, rcp)
81 var ok5: i64 = 1
82 if rcp[0] != TEA_OK { ok5 = 0 }
83 if ec != 0 { ok5 = 0 }
84 if tr_contains(out, lenp[0], "NX_TOOL_PING_OK" as *u8) != 1 { ok5 = 0 }
85 if ok5 == 1 { pass = pass + 1; ew("PASS T5 composed exec: allowlisted GREEN tool ran, exit=" as *u8); en(ec); ew(" captured " as *u8); en(lenp[0]); ew(" bytes incl sentinel\n" as *u8) }
86 else { ew("FAIL T5 rc=" as *u8); en(rcp[0]); ew(" exit=" as *u8); en(ec); ew(" len=" as *u8); en(lenp[0]); ew("\n" as *u8) }
87
88 // T6 NEG: tea_run on a BLOCKED tool must NOT execute -- returns the refusal code, zero capture
89 tot = tot + 1
90 lenp[0] = 0
91 let ec6: i64 = tea_run_from(TEA_TESTCONF, "blocked" as *u8, 7, 0 as *u8, out, 65536, lenp, rcp)
92 var ok6: i64 = 1
93 if ec6 != TEA_BLOCKED { ok6 = 0 }
94 if lenp[0] != 0 { ok6 = 0 }
95 if ok6 == 1 { pass = pass + 1; ew("PASS T6 blocked tool NOT executed -> refusal code, zero capture\n" as *u8) } else { ew("FAIL T6 ec=" as *u8); en(ec6); ew(" len=" as *u8); en(lenp[0]); ew("\n" as *u8) }
96
97 let pin:*u8=sys_mmap(64)
98 let hp:*i64=sys_mmap(16) as *i64
99 tot=tot+1
100 let pr:i64=tea_resolve_pinned_from(TEA_TESTCONF,"pinned" as *u8,6,path,4096,pin,64,hp)
101 if pr==TEA_OK && hp[0]==1 && e_streq(pin,"nftread" as *u8)==1 { pass=pass+1; ew("PASS pinned registry arguments preserved\n" as *u8) }
102 tot=tot+1
103 if tea_resolve_pinned_from(TEA_TESTCONF,"pinned" as *u8,6,path,4096,pin,7,hp)==TEA_BLOCKED && path[0]==(0 as u8) && hp[0]==0 { pass=pass+1; ew("PASS pin truncation refused before output\n" as *u8) }
104 tot=tot+1
105 if tea_resolve_pinned_from(TEA_TESTCONF,"pinned" as *u8,6,path,4,pin,64,hp)==TEA_BLOCKED { pass=pass+1; ew("PASS path truncation refused\n" as *u8) }
106 tot=tot+1
107 if tea_resolve_pinned_from(TEA_TESTCONF,"wrongstatus" as *u8,11,path,4096,pin,64,hp)==TEA_BLOCKED { pass=pass+1; ew("PASS status requires exact GREEN\n" as *u8) }
108 let av:*i64=sys_mmap(32) as *i64
109 let scratch:*u8=sys_mmap(16)
110 tot=tot+1
111 if tea_tokenize_checked(" a bb " as *u8,av,1,4,scratch,5)==2 && av[3]==0 && e_streq(av[1] as *u8,"a" as *u8)==1 && e_streq(av[2] as *u8,"bb" as *u8)==1 { pass=pass+1; ew("PASS exact scratch capacity and argv terminator\n" as *u8) }
112 av[1]=123; scratch[0]=77 as u8
113 tot=tot+1
114 if tea_tokenize_checked("a bb" as *u8,av,1,3,scratch,5)<0 && av[1]==123 && scratch[0]==(77 as u8) { pass=pass+1; ew("PASS argv overflow refused without writes\n" as *u8) }
115 tot=tot+1
116 if tea_tokenize_checked("a bb" as *u8,av,1,4,scratch,4)<0 && av[1]==123 && scratch[0]==(77 as u8) { pass=pass+1; ew("PASS scratch overflow refused without writes\n" as *u8) }
117 av[0]=0; av[1]="wrong-operation" as *u8 as i64; av[2]=0
118 tot=tot+1
119 let childrc:i64=tea_run_pinned_from(TEA_TESTCONF,"pinnedchild" as *u8,11,av,out,65536,lenp,rcp)
120 if childrc==0 && rcp[0]==TEA_OK && tr_contains(out,lenp[0],"PINNED_CHILD_OK" as *u8)==1 { pass=pass+1; ew("PASS real child receives pinned operation despite caller override\n" as *u8) }
121 ew("nx_tool_exec_allow_gate pass=" as *u8); en(pass); ew("/" as *u8); en(tot)
122 let ctr:*i64=gv_ctr()
123 ctr[0]=pass; ctr[1]=tot
124 return gv_verdict("TOOL-EXEC-ALLOW-GATE" as *u8,ctr,"registry resolution, argument bounds and pinned child execution" as *u8)
125}