nx_signup_origin_gate.nx source
↩ module page · 128 lines · 6063 B
1// nx_signup_origin_gate.nx -- proves POST /register is gated on LAN-AND-INVITED, not on a global flag.
2//
3// WHY (debt 1785445694): public registration was open to the internet because /register consulted ONE
4// global boolean. nx_lan_signup.ls_signup_allowed (LAN AND invited) existed and was gate-proven, but was
5// NOT WIRED to the route. This gate proves the wiring, which is the half that was missing.
6//
7// EVERY TOOTH IS A DENIAL. No tooth registers an account: the ALLOW path is deliberately untested here
8// because exercising it would create a real credential. The ALLOW semantics are already proven 17/17 by
9// nx_lan_signup_gate; what this file adds is that the ROUTE actually consults that decision.
10//
11// T4 IS THE NON-VACUITY TOOTH and the reason this gate is worth anything: with the flag CLOSED the route
12// must answer "registration closed". That proves T1-T3's 403s come from the NEW origin/invite checks and
13// are not just the old flag firing -- without it, a permanently-shut flag would make T1-T3 pass for the
14// wrong reason, which is exactly the vacuous-green class.
15//
16// license_tier: ORIGINAL No hw writes (Rule 26). expect_exit: 0
17import "nx_opaque_login_routes.nx"
18import "nx_opaque_login.nx"
19import "nx_syscalls.nx"
20
21const SG_RESP: i64 = 262144
22const SG_MODE: i64 = 0x1a4
23
24func g_puts(s: *u8) -> i64 { var n: i64 = 0; while s[n] != (0 as u8) { n = n + 1 } sys_write(1, s, n); return 0 }
25func rfind(hay: *u8, n: i64, needle: *u8) -> i64 {
26 var nl: i64 = 0; while needle[nl] != (0 as u8) { nl = nl + 1 }
27 if nl == 0 { return 0 }
28 var i: i64 = 0
29 while i + nl <= n {
30 var j: i64 = 0; var ok: i64 = 1
31 while j < nl { if hay[i + j] != needle[j] { ok = 0; j = nl } else { j = j + 1 } }
32 if ok == 1 { return i }
33 i = i + 1
34 }
35 return 0 - 1
36}
37func chk(name: *u8, got: i64, want: i64, st: *i64) -> i64 {
38 st[1] = st[1] + 1
39 g_puts(" "); g_puts(name)
40 if got == want { st[0] = st[0] + 1; g_puts(": PASS\n") } else { g_puts(": FAIL\n") }
41 return 0
42}
43func sg_writeflag(path: *u8, ch: i64) -> i64 {
44 let fd: i64 = sys_openat_wr(path, SG_MODE)
45 if fd < 0 { return 0 - 1 }
46 let b: *u8 = sys_mmap(8)
47 b[0] = ch as u8
48 sys_write(fd, b, 1)
49 sys_close(fd)
50 sys_munmap(b, 8)
51 return 0
52}
53func sg_ip(a: i64, b: i64, c: i64, d: i64) -> *u8 {
54 let p: *u8 = sys_mmap(8)
55 p[0] = a as u8; p[1] = b as u8; p[2] = c as u8; p[3] = d as u8
56 return p
57}
58
59func main() -> i64 {
60 let st: *i64 = sys_mmap(16) as *i64
61 st[0] = 0; st[1] = 0
62 g_puts("nx_signup_origin_gate -- POST /register must require LAN origin AND an invite\n\n")
63
64 let keys: *u8 = "/tmp/nx_sgorigin_keys.log\x00" as *u8
65 let store: *u8 = "/tmp/nx_sgorigin_store.log\x00" as *u8
66 let fd1: i64 = sys_openat_wr(keys, SG_MODE); if fd1 >= 0 { sys_close(fd1) }
67 let fd2: i64 = sys_openat_wr(store, SG_MODE); if fd2 >= 0 { sys_close(fd2) }
68 let ctx: *NxAuthContext = sys_mmap(256) as *NxAuthContext
69 if olg_ctx_setup(ctx, keys, store, "nishi_site_admin" as *u8, 16, "Nishi site admin" as *u8, 16, 8192, 1, 1) != 0 {
70 g_puts("ctx-setup FAIL\n"); return 1
71 }
72
73 let fopen: *u8 = "/tmp/nx_sgorigin_open.flag\x00" as *u8
74 let fclosed: *u8 = "/tmp/nx_sgorigin_closed.flag\x00" as *u8
75 sg_writeflag(fopen, 49) // ASCII 1 = open
76 sg_writeflag(fclosed, 48) // ASCII 0 = closed
77
78 // an HR store that does not exist -> hra_is_invited returns 0 -> nobody is invited
79 let hrs: *u8 = "/tmp/nx_sgorigin_hr_absent.log\x00" as *u8
80
81 let req: *u8 = "POST /register HTTP/1.1\r\nHost: nishifamily\r\nContent-Length: 29\r\n\r\nhandle=stranger&pw=abcdefghij\x00" as *u8
82 var rn: i64 = 0; while req[rn] != (0 as u8) { rn = rn + 1 }
83 let now: i64 = sys_now_realtime_sec()
84 let resp: *u8 = sys_mmap(SG_RESP)
85
86 let wan: *u8 = sg_ip(8, 8, 8, 8)
87 let lan: *u8 = sg_ip(192, 168, 8, 5)
88
89 // T1 -- UNKNOWN ORIGIN (ip4=0, what the rule-19 wrapper passes) must be treated as WAN and denied.
90 let n1: i64 = olg_route_ip(ctx, req, rn, now, resp, SG_RESP, 0 as *u8, hrs, "fam" as *u8, 3, fopen)
91 var t1: i64 = 0
92 if rfind(resp, n1, "403\x00" as *u8) >= 0 { if rfind(resp, n1, "requires LAN origin\x00" as *u8) >= 0 { t1 = 1 } }
93 chk("T1 unknown origin (ip4=0) -> 403, never dereferenced", t1, 1, st)
94
95 // T2 -- a real WAN address is denied even with the flag OPEN.
96 let n2: i64 = olg_route_ip(ctx, req, rn, now, resp, SG_RESP, wan, hrs, "fam" as *u8, 3, fopen)
97 var t2: i64 = 0
98 if rfind(resp, n2, "403\x00" as *u8) >= 0 { t2 = 1 }
99 chk("T2 WAN 8.8.8.8 + flag OPEN -> 403 (flag cannot open the internet)", t2, 1, st)
100
101 // T3 -- LAN but NOT invited is denied (only-the-family).
102 let n3: i64 = olg_route_ip(ctx, req, rn, now, resp, SG_RESP, lan, hrs, "fam" as *u8, 3, fopen)
103 var t3: i64 = 0
104 if rfind(resp, n3, "403\x00" as *u8) >= 0 { if rfind(resp, n3, "invite\x00" as *u8) >= 0 { t3 = 1 } }
105 chk("T3 LAN 192.168.8.5 + uninvited -> 403 (only-the-family)", t3, 1, st)
106
107 // T4 -- NON-VACUITY. Flag CLOSED must produce the FLAG message, not the origin message. This proves
108 // T1-T3 above are the new checks firing and not the old global flag.
109 let n4: i64 = olg_route_ip(ctx, req, rn, now, resp, SG_RESP, lan, hrs, "fam" as *u8, 3, fclosed)
110 var t4: i64 = 0
111 if rfind(resp, n4, "registration closed\x00" as *u8) >= 0 { t4 = 1 }
112 chk("T4 NEG-CONTROL flag CLOSED -> registration closed (T1-T3 were NOT the flag)", t4, 1, st)
113
114 // T5 -- the rule-19 wrapper keeps the 6-arg contract and denies signup through the WAN door.
115 let n5: i64 = olg_route(ctx, req, rn, now, resp, SG_RESP)
116 var t5: i64 = 0
117 if rfind(resp, n5, "403\x00" as *u8) >= 0 { t5 = 1 }
118 chk("T5 olg_route wrapper (production flag) -> 403, signup shut by construction", t5, 1, st)
119
120 g_puts("\nNX-SIGNUP-ORIGIN passed ")
121 if st[0] == st[1] {
122 g_puts("ALL verdict=GREEN (register requires LAN origin AND an invite)\n")
123 sys_exit(0); return 0
124 }
125 g_puts("verdict=RED\n")
126 sys_exit(1)
127 return 1
128}