code wiki / _hdl_build / nx_cms_snapshot_gate.nx

nx_cms_snapshot_gate.nx source

↩ module page · 279 lines · 13285 B

1// nx_cms_snapshot_gate.nx -- CMS W4 GATE (re-runnable, evidence-driven): SNAPSHOT/RESTORE (the 2// UpdraftPlus class). Proves at the wire: take a snapshot of the published baseline, change the live 3// content, then RESTORE -> the live page returns to the BYTE-EXACT baseline (not the change); the 4// snapshot is downloadable + carries the archive magic; restore is session+CSRF guarded. Appends 5// "CMSGATE row=nx_cms_snapshot ... verdict=PASS|FAIL". Exit 0 iff all rows pass. license_tier: ORIGINAL 6import "nx_cms_store.nx" 7import "nx_connect.nx" // bounded connect: a raw sys_connect hangs ~127s on a black-holed host 8import "nx_sha256.nx" 9import "nx_syscalls.nx" 10import "_hdl_build/nx_kill_portable.nx" 11 12const KS_PORT: i64 = 8091 13const KS_SITE: *u8 = "/tmp/_cms_snap_site" 14 15func ks_w(s: *u8) -> i64 { var n: i64=0; while s[n]!=(0 as u8){n=n+1} sys_write(1,s,n); return 0 } 16func ks_len(s: *u8) -> i64 { var n: i64=0; while s[n]!=(0 as u8){n=n+1} return n } 17func ks_cat(dst: *u8, off: i64, s: *u8) -> i64 { var o: i64=off; var k: i64=0; while s[k]!=(0 as u8){dst[o]=s[k];o=o+1;k=k+1} return o } 18func ks_catn(dst: *u8, off: i64, s: *u8, n: i64) -> i64 { var o: i64=off; var k: i64=0; while k<n{dst[o]=s[k];o=o+1;k=k+1} return o } 19func ks_index(hay: *u8, n: i64, needle: *u8) -> i64 { 20 let nl: i64 = ks_len(needle) 21 var i: i64 = 0 22 while i + nl <= n { 23 var q: i64 = 0 24 var ok: i64 = 1 25 while q < nl { if (hay[i+q] as i64) != (needle[q] as i64) { ok = 0; q = nl } q = q + 1 } 26 if ok == 1 { return i } 27 i = i + 1 28 } 29 return 0 - 1 30} 31func ks_has(hay: *u8, n: i64, needle: *u8) -> i64 { if ks_index(hay, n, needle) >= 0 { return 1 } return 0 } 32func ks_http(req: *u8, rl: i64, resp: *u8, cap: i64) -> i64 { 33 let fd: i64 = sys_socket(2, 1, 0) 34 if fd < 0 { return 0 - 1 } 35 let addr: *u8 = sys_mmap(16) 36 addr[0] = 2 as u8; addr[1] = 0 as u8 37 addr[2] = ((KS_PORT >> 8) & 0xff) as u8 38 addr[3] = (KS_PORT & 0xff) as u8 39 addr[4] = 127 as u8; addr[5] = 0 as u8; addr[6] = 0 as u8; addr[7] = 1 as u8 40 var zi: i64 = 8 41 while zi < 16 { addr[zi] = 0 as u8; zi = zi + 1 } 42 if nx_connect_bounded(fd, addr, 16, NX_CONN_DEFAULT_MS) < 0 { sys_close(fd); return 0 - 1 } 43 var off: i64 = 0 44 var go: i64 = 1 45 while go == 1 { go = 0; if off < rl { let w: i64 = sys_write(fd, (req + off) as *u8, rl - off); if w > 0 { off = off + w; go = 1 } } } 46 sys_set_socket_timeout(fd, 10) 47 var total: i64 = 0 48 go = 1 49 while go == 1 { go = 0; if total < cap { let r: i64 = sys_read(fd, (resp + total) as *u8, cap - total); if r > 0 { total = total + r; go = 1 } } } 50 sys_close(fd) 51 return total 52} 53func ks_post(req: *u8, path: *u8, cookie: *u8, body: *u8) -> i64 { 54 var o: i64 = ks_cat(req, 0, "POST " as *u8) 55 o = ks_cat(req, o, path) 56 o = ks_cat(req, o, " HTTP/1.1\r\nHost: gate\r\nContent-Type: application/x-www-form-urlencoded\r\n" as *u8) 57 if (cookie[0] as i64) != 0 { o = ks_cat(req, o, "Cookie: nsess=" as *u8); o = ks_catn(req, o, cookie, 32); o = ks_cat(req, o, "\r\n" as *u8) } 58 o = ks_cat(req, o, "Content-Length: " as *u8) 59 let bl: i64 = ks_len(body) 60 let t: *u8 = sys_mmap(24) 61 var m: i64 = bl 62 var k: i64 = 0 63 if m == 0 { t[0] = 48 as u8; k = 1 } 64 while m > 0 { t[k] = (48 + (m % 10)) as u8; m = m / 10; k = k + 1 } 65 var i: i64 = 0 66 while i < k { req[o] = t[k-1-i]; o = o + 1; i = i + 1 } 67 o = ks_cat(req, o, "\r\n\r\n" as *u8) 68 o = ks_cat(req, o, body) 69 return o 70} 71func ks_get(req: *u8, path: *u8, cookie: *u8) -> i64 { 72 var o: i64 = ks_cat(req, 0, "GET " as *u8) 73 o = ks_cat(req, o, path) 74 o = ks_cat(req, o, " HTTP/1.1\r\nHost: gate\r\n" as *u8) 75 if (cookie[0] as i64) != 0 { o = ks_cat(req, o, "Cookie: nsess=" as *u8); o = ks_catn(req, o, cookie, 32); o = ks_cat(req, o, "\r\n" as *u8) } 76 o = ks_cat(req, o, "\r\n" as *u8) 77 return o 78} 79func ks_row(id: i64, ok: i64, what: *u8) -> i64 { 80 ks_w("SNAPROW " as *u8) 81 let d: *u8 = sys_mmap(8); d[0] = (48 + id/10) as u8; d[1] = (48 + id%10) as u8; d[2] = 32 as u8; d[3] = 0 as u8 82 ks_w(d) 83 if ok == 1 { ks_w("PASS " as *u8) } 84 if ok == 0 { ks_w("FAIL " as *u8) } 85 ks_w(what); ks_w("\n" as *u8) 86 return ok 87} 88func ks_mkdir(path: *u8, mode: i64) -> i64 { let nb: *i64 = sys_mmap(8) as *i64; nb[0] = 258; return __syscall(nb[0], 0 - 100, path, mode, 0, 0, 0) } 89func ks_writefile(path: *u8, buf: *u8, n: i64) -> i64 { let fd: i64 = sys_openat_wr(path, 0x1a4); if fd < 0 { return 0 } sys_write(fd, buf, n); sys_close(fd); return 1 } 90 91func main() -> i64 { 92 ks_mkdir(KS_SITE, 0x1ed) 93 cst_copy("knowledge/cms/andelinwest/template.html" as *u8, "/tmp/_cms_snap_site/template.html" as *u8) 94 cst_copy("knowledge/cms/andelinwest/content.txt" as *u8, "/tmp/_cms_snap_site/content.txt" as *u8) 95 sys_renameat("/tmp/_cms_snap_site/draft.txt" as *u8, "/tmp/_cms_snap_site/draft.stale" as *u8) 96 sys_renameat("/tmp/_cms_snap_site/snapshot.nxsnap" as *u8, "/tmp/_cms_snap_site/snapshot.stale" as *u8) 97 let dig: *u8 = sys_mmap(32) 98 sha256_digest("snap-gate-pw-1" as *u8, 14, dig) 99 let hexs: *u8 = sys_mmap(80) 100 let hxc: *u8 = "0123456789abcdef" as *u8 101 var i: i64 = 0 102 while i < 32 { hexs[i*2] = hxc[((dig[i] as i64) >> 4) & 15]; hexs[i*2+1] = hxc[(dig[i] as i64) & 15]; i = i + 1 } 103 ks_writefile("/tmp/_cms_snap_site/admin_pw.sha256" as *u8, hexs, 64) 104 105 let bpid: i64 = sys_fork() 106 if bpid == 0 { 107 let dn: i64 = sys_openat_wr("/dev/null" as *u8, 0x1a4) 108 if dn >= 0 { sys_dup3(dn, 1, 0); sys_dup3(dn, 2, 0) } 109 let bargv: *i64 = sys_mmap(32) as *i64 110 bargv[0] = "_offc/nx_sov_build_run.elf" as *u8 as i64 111 bargv[1] = "nx_cms_admin" as *u8 as i64 112 bargv[2] = 0 113 let benvp: *i64 = sys_mmap(16) as *i64; benvp[0] = 0 114 sys_execve("_offc/nx_sov_build_run.elf" as *u8, bargv, benvp); sys_exit(127) 115 } 116 let bst: *i64 = sys_mmap(16) as *i64 117 sys_wait4(bpid, bst, 0) 118 // CALLER-GAP FIX 2026-08-01 (seq1267 class): the runner emits buildroot/_build/<t>.sov.elf under 119 // the no-/tmp doctrine while this probed /tmp. Proven by control: _build copy 208962B, /tmp absent. 120 var bchk: i64 = sys_openat_rd("buildroot/_build/nx_cms_admin.sov.elf" as *u8) 121 var badm: *u8 = "buildroot/_build/nx_cms_admin.sov.elf" as *u8 122 if bchk < 0 { bchk = sys_openat_rd("/tmp/nx_cms_admin.sov.elf" as *u8); badm = "/tmp/nx_cms_admin.sov.elf" as *u8 } 123 if bchk < 0 { ks_w("CMSGATE row=nx_cms_snapshot ADMIN-BUILD-MISSING (probed buildroot/_build AND /tmp)\n" as *u8); sys_exit(1) } 124 sys_close(bchk) 125 let pid: i64 = sys_fork() 126 if pid == 0 { 127 let lg: i64 = sys_openat_wr("/tmp/_cms_admin_snap_gate.log" as *u8, 0x1a4) 128 if lg >= 0 { sys_dup3(lg, 1, 0); sys_dup3(lg, 2, 0) } 129 let argvv: *i64 = sys_mmap(8*6) as *i64 130 argvv[0] = badm as i64 131 argvv[1] = "8091" as *u8 as i64 132 argvv[2] = KS_SITE as i64 133 argvv[3] = "99" as *u8 as i64 134 argvv[4] = 0 135 let envp: *i64 = sys_mmap(16) as *i64; envp[0] = 0 136 sys_execve(badm, argvv, envp) 137 sys_exit(127) 138 } 139 140 let req: *u8 = sys_mmap(262144) 141 let resp: *u8 = sys_mmap(524288) 142 let nocookie: *u8 = sys_mmap(8) 143 nocookie[0] = 0 as u8 144 let sess: *u8 = sys_mmap(64) 145 sess[0] = 0 as u8 146 let csrf: *u8 = sys_mmap(64) 147 csrf[0] = 0 as u8 148 149 // K0: boot + login + csrf 150 var rn: i64 = 0 - 1 151 var tries: i64 = 0 152 while tries < 50 { 153 let rl0: i64 = ks_get(req, "/" as *u8, nocookie) 154 rn = ks_http(req, rl0, resp, 524287) 155 if rn > 0 { tries = 50 } 156 if rn <= 0 { sys_sleep_ms(100); tries = tries + 1 } 157 } 158 var pass: i64 = 0 159 var rows: i64 = 0 160 var ok: i64 = 0 161 var rl: i64 = ks_post(req, "/admin/login" as *u8, nocookie, "pw=snap-gate-pw-1" as *u8) 162 rn = ks_http(req, rl, resp, 524287) 163 if rn > 0 { if ks_has(resp, rn, "303" as *u8) == 1 { 164 let sp: i64 = ks_index(resp, rn, "nsess=" as *u8) 165 if sp >= 0 { 166 var m2: i64 = 0 167 while m2 < 32 { sess[m2] = resp[sp+6+m2]; m2 = m2 + 1 } 168 sess[32] = 0 as u8 169 rl = ks_get(req, "/admin" as *u8, sess) 170 rn = ks_http(req, rl, resp, 524287) 171 let cp: i64 = ks_index(resp, rn, "name=\"csrf\" value=\"" as *u8) 172 if cp >= 0 { var m3: i64 = 0; while m3 < 32 { csrf[m3] = resp[cp+19+m3]; m3 = m3 + 1 } csrf[32] = 0 as u8; ok = 1 } 173 } 174 } } 175 rows = rows + 1; pass = pass + ks_row(0, ok, "login + csrf + editor shows Backup" as *u8) 176 var hasbackup: i64 = 0 177 if ok == 1 { if ks_has(resp, rn, "Take snapshot" as *u8) == 1 { hasbackup = 1 } } 178 rows = rows + 1; pass = pass + ks_row(1, hasbackup, "editor carries snapshot/restore controls" as *u8) 179 180 // K2: establish a baseline publish, then snapshot it 181 let bodyB: *u8 = sys_mmap(4096) 182 var bb: i64 = ks_cat(bodyB, 0, "csrf=" as *u8) 183 bb = ks_catn(bodyB, bb, csrf, 32) 184 bb = ks_cat(bodyB, bb, "&hero_title=SnapBaselineHeadline" as *u8) 185 bodyB[bb] = 0 as u8 186 rl = ks_post(req, "/admin/save" as *u8, sess, bodyB) 187 ks_http(req, rl, resp, 524287) 188 let bodyS: *u8 = sys_mmap(256) 189 var bs: i64 = ks_cat(bodyS, 0, "csrf=" as *u8) 190 bs = ks_catn(bodyS, bs, csrf, 32) 191 bodyS[bs] = 0 as u8 192 rl = ks_post(req, "/admin/snapshot" as *u8, sess, bodyS) 193 rn = ks_http(req, rl, resp, 524287) 194 ok = 0 195 if rn > 0 { if ks_has(resp, rn, "303" as *u8) == 1 { ok = 1 } } 196 rows = rows + 1; pass = pass + ks_row(2, ok, "snapshot of baseline taken 303" as *u8) 197 198 // K3: snapshot downloadable + carries the archive magic 199 rl = ks_get(req, "/admin/snapshot.nxsnap" as *u8, sess) 200 rn = ks_http(req, rl, resp, 524287) 201 ok = 0 202 if rn > 0 { if ks_has(resp, rn, "application/octet-stream" as *u8) == 1 { if ks_has(resp, rn, "NXSNAP1" as *u8) == 1 { ok = 1 } } } 203 rows = rows + 1; pass = pass + ks_row(3, ok, "snapshot downloadable with archive magic" as *u8) 204 205 // K4: change the live content (so restore has something to undo) 206 let bodyC: *u8 = sys_mmap(4096) 207 var bc: i64 = ks_cat(bodyC, 0, "csrf=" as *u8) 208 bc = ks_catn(bodyC, bc, csrf, 32) 209 bc = ks_cat(bodyC, bc, "&hero_title=ChangedAfterSnapshot" as *u8) 210 bodyC[bc] = 0 as u8 211 rl = ks_post(req, "/admin/save" as *u8, sess, bodyC) 212 ks_http(req, rl, resp, 524287) 213 rl = ks_get(req, "/" as *u8, nocookie) 214 rn = ks_http(req, rl, resp, 524287) 215 ok = 0 216 if rn > 0 { if ks_has(resp, rn, "ChangedAfterSnapshot" as *u8) == 1 { ok = 1 } } 217 rows = rows + 1; pass = pass + ks_row(4, ok, "live content changed after snapshot" as *u8) 218 219 // K5: RESTORE -> live page byte-back to the baseline (and the change is gone) 220 let bodyR: *u8 = sys_mmap(256) 221 var br: i64 = ks_cat(bodyR, 0, "csrf=" as *u8) 222 br = ks_catn(bodyR, br, csrf, 32) 223 bodyR[br] = 0 as u8 224 rl = ks_post(req, "/admin/restore" as *u8, sess, bodyR) 225 rn = ks_http(req, rl, resp, 524287) 226 ok = 0 227 if rn > 0 { if ks_has(resp, rn, "303" as *u8) == 1 { 228 rl = ks_get(req, "/" as *u8, nocookie) 229 rn = ks_http(req, rl, resp, 524287) 230 if rn > 0 { if ks_has(resp, rn, "SnapBaselineHeadline" as *u8) == 1 { if ks_has(resp, rn, "ChangedAfterSnapshot" as *u8) == 0 { ok = 1 } } } 231 } } 232 rows = rows + 1; pass = pass + ks_row(5, ok, "restore brings back baseline byte-exact" as *u8) 233 234 // K6: restore is session+CSRF guarded (unauth refused, change preserved) 235 rl = ks_post(req, "/admin/restore" as *u8, nocookie, "csrf=00000000000000000000000000000000" as *u8) 236 rn = ks_http(req, rl, resp, 524287) 237 ok = 0 238 if rn > 0 { if ks_has(resp, rn, "403" as *u8) == 1 { ok = 1 } } 239 rows = rows + 1; pass = pass + ks_row(6, ok, "restore refused without session 403" as *u8) 240 241 // ---- teardown ---- 242 nxk_kill(pid, 9) 243 let stbuf: *i64 = sys_mmap(16) as *i64 244 sys_wait4(pid, stbuf, 0) 245 246 var verdict: *u8 = "FAIL" as *u8 247 if pass == rows { verdict = "PASS" as *u8 } 248 let line: *u8 = sys_mmap(512) 249 var lo: i64 = ks_cat(line, 0, "CMSGATE row=nx_cms_snapshot rows=" as *u8) 250 let t2: *u8 = sys_mmap(24) 251 var mm: i64 = rows 252 var kk: i64 = 0 253 if mm == 0 { t2[0] = 48 as u8; kk = 1 } 254 while mm > 0 { t2[kk] = (48 + (mm % 10)) as u8; mm = mm / 10; kk = kk + 1 } 255 var ii: i64 = 0 256 while ii < kk { line[lo] = t2[kk-1-ii]; lo = lo + 1; ii = ii + 1 } 257 lo = ks_cat(line, lo, " pass=" as *u8) 258 mm = pass; kk = 0 259 if mm == 0 { t2[0] = 48 as u8; kk = 1 } 260 while mm > 0 { t2[kk] = (48 + (mm % 10)) as u8; mm = mm / 10; kk = kk + 1 } 261 ii = 0 262 while ii < kk { line[lo] = t2[kk-1-ii]; lo = lo + 1; ii = ii + 1 } 263 lo = ks_cat(line, lo, " verdict=" as *u8) 264 lo = ks_cat(line, lo, verdict) 265 lo = ks_cat(line, lo, " epoch=" as *u8) 266 mm = sys_now_realtime_sec(); kk = 0 267 if mm == 0 { t2[0] = 48 as u8; kk = 1 } 268 while mm > 0 { t2[kk] = (48 + (mm % 10)) as u8; mm = mm / 10; kk = kk + 1 } 269 ii = 0 270 while ii < kk { line[lo] = t2[kk-1-ii]; lo = lo + 1; ii = ii + 1 } 271 line[lo] = 10 as u8 272 lo = lo + 1 273 sys_write(1, line, lo) 274 let gf: i64 = sys_openat_append("knowledge/status/cms_gate.log" as *u8, 0x1a4) 275 if gf >= 0 { sys_write(gf, line, lo); sys_close(gf) } 276 if pass == rows { sys_exit(0) } 277 sys_exit(1) 278 return 1 279}