code wiki / _hdl_build / nx_evict.nx
nx_evict.nx source
↩ module page · 164 lines · 8449 B
1// nx_evict.nx -- THE SOVEREIGN JANITOR MOVER (debt seq221 second half; retires the "sanctioned
2// janitor ssh mv" residual class). REVERSIBLE eviction: atomic sys_renameat of a nishihost-relative
3// file into retired/ with the path FLATTENED (each '/' -> '__'), bytes preserved (additive-only law
4// rule 13 -- nothing is ever deleted, history is sacred). restore = the exact inverse, refusing to
5// clobber an existing destination (fail-safe).
6// DENY BY CONSTRUCTION (not config): absolute paths, '..' traversal, secret material (secret/key/
7// token/passw/.pem/opaque needles), live binaries (.elf -- promote/rollback owns those), the retired/
8// namespace itself, and the protected registries (tool_allowlist.conf, daemons.reg, clock_jobs.tsv,
9// proxy_routes.conf, sites.conf). Over-deny is the safe failure mode; needles are lowercase-literal
10// (declared envelope).
11// nx_evict evict <relpath> -> retired/<flattened> (atomic, reversible)
12// nx_evict restore <name> <destpath> -> retired/<name> -> <destpath> (refuses if dest exists)
13// nx_evict selftest -> probe evict/restore round-trip + refusal teeth, exit 0/5
14// license_tier: ORIGINAL No hw writes (Rule 26). expect_exit: 0
15import "nx_syscalls.nx"
16
17const EV_STDERR: i64 = 2
18const EV_PATHCAP: i64 = 512
19const EV_SLASH: i64 = 47
20const EV_UND: i64 = 95
21const EV_MODE: i64 = 0x1a4
22const EV_EXIT_USAGE: i64 = 2
23const EV_EXIT_REFUSED: i64 = 3
24const EV_EXIT_IO: i64 = 4
25const EV_EXIT_SELF: i64 = 5
26const EV_V_E: i64 = 101
27const EV_V_R: i64 = 114
28const EV_V_S: i64 = 115
29const EV_ARGC_EVICT: i64 = 3
30const EV_ARGC_RESTORE: i64 = 4
31
32func ev_slen(s: *u8) -> i64 { var n: i64 = 0; while s[n] != (0 as u8) { n = n + 1 } return n }
33func ev_werr(s: *u8) -> i64 { sys_write(EV_STDERR, s, ev_slen(s)); return 0 }
34func ev_cat(d: *u8, off: i64, s: *u8) -> i64 {
35 var o: i64 = off
36 var j: i64 = 0
37 while s[j] != (0 as u8) { d[o] = s[j]; o = o + 1; j = j + 1 }
38 return o
39}
40func ev_eq(a: *u8, b: *u8) -> i64 {
41 var i: i64 = 0
42 while a[i] != (0 as u8) { if a[i] != b[i] { return 0 } i = i + 1 }
43 if b[i] != (0 as u8) { return 0 }
44 return 1
45}
46func ev_has(s: *u8, sub: *u8) -> i64 {
47 let n: i64 = ev_slen(s)
48 let m: i64 = ev_slen(sub)
49 if m == 0 { return 0 }
50 var i: i64 = 0
51 while i + m <= n {
52 var j: i64 = 0
53 var ok: i64 = 1
54 while j < m { if s[i+j] != sub[j] { ok = 0; j = m } else { j = j + 1 } }
55 if ok == 1 { return 1 }
56 i = i + 1
57 }
58 return 0
59}
60// 1 = refused (fail-safe over-deny), 0 = allowed
61func ev_deny(p: *u8) -> i64 {
62 if p[0] == (EV_SLASH as u8) { return 1 }
63 if ev_has(p, ".." as *u8) == 1 { return 1 }
64 if ev_has(p, "secret" as *u8) == 1 { return 1 }
65 if ev_has(p, "key" as *u8) == 1 { return 1 }
66 if ev_has(p, "token" as *u8) == 1 { return 1 }
67 if ev_has(p, "passw" as *u8) == 1 { return 1 }
68 if ev_has(p, ".pem" as *u8) == 1 { return 1 }
69 if ev_has(p, "opaque" as *u8) == 1 { return 1 }
70 if ev_has(p, ".elf" as *u8) == 1 { return 1 }
71 if ev_has(p, "retired/" as *u8) == 1 { return 1 }
72 if ev_eq(p, "tool_allowlist.conf" as *u8) == 1 { return 1 }
73 if ev_eq(p, "daemons.reg" as *u8) == 1 { return 1 }
74 if ev_eq(p, "clock_jobs.tsv" as *u8) == 1 { return 1 }
75 if ev_eq(p, "proxy_routes.conf" as *u8) == 1 { return 1 }
76 if ev_eq(p, "sites.conf" as *u8) == 1 { return 1 }
77 return 0
78}
79// dst = "retired/" + src with each '/' flattened to "__"
80func ev_flat(src: *u8, dst: *u8) -> i64 {
81 var o: i64 = ev_cat(dst, 0, "retired/" as *u8)
82 var i: i64 = 0
83 while src[i] != (0 as u8) {
84 if src[i] == (EV_SLASH as u8) { dst[o] = EV_UND as u8; o = o + 1; dst[o] = EV_UND as u8; o = o + 1 }
85 else { dst[o] = src[i]; o = o + 1 }
86 i = i + 1
87 }
88 dst[o] = 0 as u8
89 return o
90}
91// file exists? (openat_rd probe)
92func ev_exists(p: *u8) -> i64 {
93 let fd: i64 = sys_openat_rd(p)
94 if fd < 0 { return 0 }
95 sys_close(fd)
96 return 1
97}
98
99func main(argc: i64, argv: *i64) -> i64 {
100 if argc < 2 { ev_werr("usage: nx_evict {evict <relpath> | restore <name> <destpath> | selftest}\n" as *u8); sys_exit(EV_EXIT_USAGE); return EV_EXIT_USAGE }
101 let v: *u8 = argv[1] as *u8
102
103 if v[0] == (EV_V_S as u8) {
104 var ok: i64 = 1
105 if ev_deny("tool_allowlist.conf" as *u8) == 0 { ok = 0 }
106 if ev_deny("../up.txt" as *u8) == 0 { ok = 0 }
107 if ev_deny("tools_cap_secret.key" as *u8) == 0 { ok = 0 }
108 if ev_deny("/etc/hosts" as *u8) == 0 { ok = 0 }
109 if ev_deny("nx_hostctl.elf" as *u8) == 0 { ok = 0 }
110 if ev_deny("mctest-seg-0.docs" as *u8) == 1 { ok = 0 }
111 if ok == 0 { ev_werr("EV-SELFTEST FAIL verdict=RED deny-teeth\n" as *u8); sys_exit(EV_EXIT_SELF); return EV_EXIT_SELF }
112 let fd: i64 = sys_openat_wr("evict_probe.tmp" as *u8, EV_MODE)
113 if fd < 0 { ev_werr("EV-SELFTEST FAIL verdict=RED probe-create\n" as *u8); sys_exit(EV_EXIT_SELF); return EV_EXIT_SELF }
114 sys_write(fd, "x" as *u8, 1)
115 sys_close(fd)
116 if sys_renameat("evict_probe.tmp" as *u8, "retired/evict_probe.tmp" as *u8) < 0 { ev_werr("EV-SELFTEST FAIL verdict=RED evict\n" as *u8); sys_exit(EV_EXIT_SELF); return EV_EXIT_SELF }
117 if ev_exists("retired/evict_probe.tmp" as *u8) == 0 { ev_werr("EV-SELFTEST FAIL verdict=RED evict-verify\n" as *u8); sys_exit(EV_EXIT_SELF); return EV_EXIT_SELF }
118 if sys_renameat("retired/evict_probe.tmp" as *u8, "evict_probe.tmp" as *u8) < 0 { ev_werr("EV-SELFTEST FAIL verdict=RED restore\n" as *u8); sys_exit(EV_EXIT_SELF); return EV_EXIT_SELF }
119 if ev_exists("evict_probe.tmp" as *u8) == 0 { ev_werr("EV-SELFTEST FAIL verdict=RED restore-verify\n" as *u8); sys_exit(EV_EXIT_SELF); return EV_EXIT_SELF }
120 if sys_renameat("evict_probe.tmp" as *u8, "retired/evict_probe.tmp" as *u8) < 0 { ev_werr("EV-SELFTEST FAIL verdict=RED re-evict\n" as *u8); sys_exit(EV_EXIT_SELF); return EV_EXIT_SELF }
121 ev_werr("EV-SELFTEST OK verdict=GREEN\n" as *u8)
122 sys_exit(0)
123 return 0
124 }
125
126 if v[0] == (EV_V_E as u8) {
127 if argc < EV_ARGC_EVICT { ev_werr("evict needs <relpath>\n" as *u8); sys_exit(EV_EXIT_USAGE); return EV_EXIT_USAGE }
128 let p: *u8 = argv[2] as *u8
129 if ev_deny(p) == 1 { ev_werr("EV-REFUSED (deny-by-construction: absolute/traversal/secret/elf/protected)\n" as *u8); sys_exit(EV_EXIT_REFUSED); return EV_EXIT_REFUSED }
130 if ev_exists(p) == 0 { ev_werr("EV-FAIL src absent\n" as *u8); sys_exit(EV_EXIT_IO); return EV_EXIT_IO }
131 let dst: *u8 = sys_mmap(EV_PATHCAP)
132 ev_flat(p, dst)
133 if ev_exists(dst) == 1 { ev_werr("EV-REFUSED retired name already exists (no clobber)\n" as *u8); sys_exit(EV_EXIT_REFUSED); return EV_EXIT_REFUSED }
134 if sys_renameat(p, dst) < 0 { ev_werr("EV-FAIL rename\n" as *u8); sys_exit(EV_EXIT_IO); return EV_EXIT_IO }
135 ev_werr("EV-OK evicted -> " as *u8)
136 ev_werr(dst)
137 ev_werr("\n" as *u8)
138 sys_exit(0)
139 return 0
140 }
141
142 if v[0] == (EV_V_R as u8) {
143 if argc < EV_ARGC_RESTORE { ev_werr("restore needs <name> <destpath>\n" as *u8); sys_exit(EV_EXIT_USAGE); return EV_EXIT_USAGE }
144 let nm: *u8 = argv[2] as *u8
145 let dp: *u8 = argv[3] as *u8
146 if ev_has(nm, "/" as *u8) == 1 { ev_werr("EV-REFUSED name must be bare (no /)\n" as *u8); sys_exit(EV_EXIT_REFUSED); return EV_EXIT_REFUSED }
147 if ev_has(nm, ".." as *u8) == 1 { ev_werr("EV-REFUSED traversal\n" as *u8); sys_exit(EV_EXIT_REFUSED); return EV_EXIT_REFUSED }
148 if ev_deny(dp) == 1 { ev_werr("EV-REFUSED dest (deny-by-construction)\n" as *u8); sys_exit(EV_EXIT_REFUSED); return EV_EXIT_REFUSED }
149 if ev_exists(dp) == 1 { ev_werr("EV-REFUSED dest exists (no clobber)\n" as *u8); sys_exit(EV_EXIT_REFUSED); return EV_EXIT_REFUSED }
150 let sp: *u8 = sys_mmap(EV_PATHCAP)
151 var o: i64 = ev_cat(sp, 0, "retired/" as *u8)
152 o = ev_cat(sp, o, nm)
153 sp[o] = 0 as u8
154 if ev_exists(sp) == 0 { ev_werr("EV-FAIL retired name absent\n" as *u8); sys_exit(EV_EXIT_IO); return EV_EXIT_IO }
155 if sys_renameat(sp, dp) < 0 { ev_werr("EV-FAIL rename\n" as *u8); sys_exit(EV_EXIT_IO); return EV_EXIT_IO }
156 ev_werr("EV-OK restored\n" as *u8)
157 sys_exit(0)
158 return 0
159 }
160
161 ev_werr("usage: nx_evict {evict <relpath> | restore <name> <destpath> | selftest}\n" as *u8)
162 sys_exit(EV_EXIT_USAGE)
163 return EV_EXIT_USAGE
164}