code wiki / _hdl_build / _k_r2_001b2a_gate.nx

_k_r2_001b2a_gate.nx source

↩ module page · 356 lines · 18954 B

1// _k_r2_001b2a_gate.nx -- the K-R2-001b2a gate (virtio-MMIO DESCRIPTOR-DMA layer; the 2// second slice of the K-R2-001b virtqueue epic, on top of b1's queue-config). Drives the 3// full author->handshake->queue-config->descriptor-DMA chain with NO mocks: runs the REAL 4// nx_virtio_hs_emit (the team AUTHORS the rv64 image + the TABLE-COMPUTED golden transcript 5// from the descriptor-DMA spec), then RUNS the image on the SOVEREIGN rv64 emulator 6// (rv64im_min_sim + the rv64im_min_virtio device now DMA-reading the descriptor on the 7// QueueNotify kick and exposing the read field at the QueueDescPeek RO register -- the 8// PRIMARY, gating lane: Nishi owns the runtime) and asserts the captured serial transcript 9// CONTAINS the emitter's golden ("VIO ACK DRV FEAT OK VQ DESC\n" -- after the queue config 10// the driver laid ONE legacy virtio descriptor into the QueuePFN-bound guest ring page, 11// kicked QueueNotify so the device DMA-read it, then READ QueueDescPeek BACK and verified it 12// == the descriptor addr-low word it wrote: the descriptor-DMA binding proof) AND the 13// sovereign emu reports a clean SiFive-finisher halt. 14// 15// Then the ALIGNMENT lane: qemu-system-riscv64 -machine virt -global 16// virtio-mmio.force-legacy=true with a REAL virtio-blk-device backing. The gate authors a 17// SECOND image from a base-rewritten spec (the blk device lands at slot 0x10008000) and runs 18// the SAME driver against the REAL legacy virtio-blk transport: it completes the handshake + 19// queue config, lays the SAME descriptor into qemu guest RAM at the ring page, and kicks the 20// real QueueNotify -- the real legacy transport genuinely walks the ring. The QueueDescPeek 21// result register is a sovereign-model instrument the real device does not expose (it reads 22// 0), so on qemu the descriptor-DMA read-back verify gracefully drops ONLY " DESC"; the gate 23// asserts the qemu transcript contains the b1 prefix golden ("VIO ACK DRV FEAT OK VQ") -- 24// proving the SAME driver lays the descriptor + kicks the real device -> lanes AGREE on every 25// register the real transport can observe. 26// 27// Finally a TAMPER test: corrupt the QueueDescPeek-EXPECTED constant in the sovereign image 28// (the driver loads its own QueueDescPeek read-back into t3 via lwu and compares against this 29// li-loaded expected in t4; bumping the expected's immediate makes t3 != t4) -> the read-back 30// verify branches PAST stage 6 straight to the finisher -> the transcript loses ONLY its 31// " DESC" canary (the "VIO ACK DRV FEAT OK VQ" tail survives) -> the gate MUST go RED. 32// Evidence -> knowledge/status/virtio_blk.log (DESCGATE row; the queue row's ||MARK= reads 33// it). Sovereign orchestration (fork/dup3/execve/wait4). license_tier: ORIGINAL 34import "nx_syscalls.nx" 35import "nx_gate_verdict.nx" 36 37// bytes qemu actually produced. ZERO means qemu-system-riscv64 never ran (absent on this host) -- 38// an EXTERNAL PRECONDITION, not a failure of the driver or the device model under test. 39static g_qemu_bytes: i64 40 41// the unique rv64 encoding of `lwu t3, 0x60(t2)` -- the QueueDescPeek read-back load (f3=6, 42// zero-extend; an address is unsigned). The DescPeek-expected constant the driver compares 43// against is the li that follows it (lui at +4, addi at +8); corrupting the addi's high 44// immediate byte (+11) is the descriptor-DMA tamper. 45const G_QDESCPEEK_LOAD_WORD: i64 = 0x0603ee03 // lwu t3, 0x060(t2) (QueueDescPeek read-back) 46 47func g_p(s: *u8) -> i64 { var n: i64=0; while s[n]!=(0 as u8){n=n+1} sys_write(1,s,n); return 0 } 48func g_fp(fd: i64, s: *u8) -> i64 { var n: i64=0; while s[n]!=(0 as u8){n=n+1} sys_write(fd,s,n); return 0 } 49func g_fn(fd: i64, v: i64) -> i64 { let bb: *u8=sys_mmap(28); var m: i64=v; if m<0{m=0-m;sys_write(fd,"-" as *u8,1)}; let t: *u8=sys_mmap(28); var k: i64=0; if m==0{t[0]=48;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(fd,bb,k); return 0 } 50 51// run nx_virtio_hs_emit <spec>; return child wait status (0 = ok) 52func g_run_emit(spec: *u8) -> i64 { 53 let pid: i64 = sys_fork() 54 if pid == 0 { 55 let dn: i64 = sys_openat_wr("/dev/null" as *u8, 0x1a4) 56 if dn >= 0 { sys_dup3(dn, 1, 0) } 57 let argv: *i64 = sys_mmap(32) as *i64 58 argv[0] = "_offc/nx_virtio_hs_emit.elf" as *u8 as i64 59 argv[1] = spec as i64 60 argv[2] = 0 61 let envp: *i64 = sys_mmap(16) as *i64 62 envp[0] = "PATH=/usr/bin:/bin" as *u8 as i64 63 envp[1] = 0 64 sys_execve("_offc/nx_virtio_hs_emit.elf" as *u8, argv, envp) 65 sys_exit(127) 66 } 67 let st: *i64 = sys_mmap(16) as *i64 68 sys_wait4(pid, st, 0) 69 return st[0] 70} 71 72// run the SOVEREIGN rv64 emulator on binpath; serial -> outpath; return wait status 73func g_run_sov(binpath: *u8, outpath: *u8) -> i64 { 74 let pid: i64 = sys_fork() 75 if pid == 0 { 76 let ofd: i64 = sys_openat_wr(outpath, 0x1a4) 77 if ofd >= 0 { sys_dup3(ofd, 1, 0); sys_dup3(ofd, 2, 0) } 78 let argv: *i64 = sys_mmap(32) as *i64 79 argv[0] = "_offc/nx_boot_run_sov.elf" as *u8 as i64 80 argv[1] = binpath as i64 81 argv[2] = 0 82 let envp: *i64 = sys_mmap(16) as *i64 83 envp[0] = "PATH=/usr/bin:/bin" as *u8 as i64 84 envp[1] = 0 85 sys_execve("_offc/nx_boot_run_sov.elf" as *u8, argv, envp) 86 sys_exit(127) 87 } 88 let st: *i64 = sys_mmap(16) as *i64 89 sys_wait4(pid, st, 0) 90 return st[0] 91} 92 93// run qemu-system-riscv64 virt + legacy virtio-blk-device on binpath; serial -> outpath. 94func g_run_qemu(binpath: *u8, outpath: *u8, backing: *u8) -> i64 { 95 let pid: i64 = sys_fork() 96 if pid == 0 { 97 let ofd: i64 = sys_openat_wr(outpath, 0x1a4) 98 if ofd >= 0 { sys_dup3(ofd, 1, 0); sys_dup3(ofd, 2, 0) } 99 let argv: *i64 = sys_mmap(128) as *i64 100 argv[0] = "/usr/bin/qemu-system-riscv64" as *u8 as i64 101 argv[1] = "-machine" as *u8 as i64 102 argv[2] = "virt" as *u8 as i64 103 argv[3] = "-global" as *u8 as i64 104 argv[4] = "virtio-mmio.force-legacy=true" as *u8 as i64 105 argv[5] = "-nographic" as *u8 as i64 106 argv[6] = "-bios" as *u8 as i64 107 argv[7] = binpath as i64 108 argv[8] = "-drive" as *u8 as i64 109 argv[9] = "file=/tmp/_descgate_backing.img,if=none,format=raw,id=hd0" as *u8 as i64 110 argv[10] = "-device" as *u8 as i64 111 argv[11] = "virtio-blk-device,drive=hd0" as *u8 as i64 112 argv[12] = 0 113 let envp: *i64 = sys_mmap(16) as *i64 114 envp[0] = "PATH=/usr/bin:/bin" as *u8 as i64 115 envp[1] = 0 116 sys_execve("/usr/bin/qemu-system-riscv64" as *u8, argv, envp) 117 sys_exit(127) 118 } 119 let st: *i64 = sys_mmap(16) as *i64 120 sys_wait4(pid, st, 0) 121 return st[0] 122} 123 124// create a small raw backing file for the qemu virtio-blk device (4 sectors of zeros). 125func g_make_backing(path: *u8) -> i64 { 126 let fd: i64 = sys_openat_wr(path, 0x1a4) 127 if fd < 0 { return 0 - 1 } 128 let z: *u8 = sys_mmap(2048) 129 var i: i64 = 0 130 while i < 2048 { z[i] = 0 as u8; i = i + 1 } 131 sys_write(fd, z, 2048) 132 sys_close(fd) 133 return 0 134} 135 136// read whole file into buf (cap-1 max); return byte count (0 if absent) 137func g_read(path: *u8, buf: *u8, cap: i64) -> i64 { 138 let fd: i64 = sys_openat_rd(path) 139 if fd < 0 { return 0 } 140 var n: i64 = 0 141 var go: i64 = 1 142 while go == 1 { let r: i64 = sys_read(fd, (buf as i64 + n) as *u8, cap - 1 - n); if r <= 0 { go = 0 } else { n = n + r } if n >= cap - 1 { go = 0 } } 143 sys_close(fd) 144 return n 145} 146 147// does buf[0,n) contain pat (length pl)? 1/0 148func g_buf_has(buf: *u8, n: i64, pat: *u8, pl: i64) -> i64 { 149 if pl <= 0 { return 0 } 150 var i: i64 = 0 151 while i + pl <= n { 152 var k: i64 = 0 153 var hit: i64 = 1 154 while k < pl { if buf[i+k] != pat[k] { hit = 0; k = pl } else { k = k + 1 } } 155 if hit == 1 { return 1 } 156 i = i + 1 157 } 158 return 0 159} 160 161// read a little-endian 32-bit word from buf at byte offset o. 162func g_w32(buf: *u8, o: i64) -> i64 { 163 let b0: i64 = buf[o] as i64 164 let b1: i64 = buf[o+1] as i64 165 let b2: i64 = buf[o+2] as i64 166 let b3: i64 = buf[o+3] as i64 167 return b0 | (b1 << 8) | (b2 << 16) | (b3 << 24) 168} 169 170// find the byte offset of the (unique) QueueDescPeek read-back load word in the image; -1 if 171// absent. The DescPeek-expected li the driver compares against is at off+4 (lui) / off+8 172// (addi); the addi's high immediate byte is at off+11. 173func g_find_descpeek_load(buf: *u8, n: i64) -> i64 { 174 var o: i64 = 0 175 while o + 4 <= n { 176 if g_w32(buf, o) == G_QDESCPEEK_LOAD_WORD { return o } 177 o = o + 4 178 } 179 return 0 - 1 180} 181 182func g_strlen(s: *u8) -> i64 { var n: i64 = 0; while s[n] != (0 as u8) { n = n + 1 } return n } 183 184// does line [ls,le) of buf begin with key? 1/0 185func g_line_is(buf: *u8, ls: i64, le: i64, key: *u8) -> i64 { 186 var k: i64 = 0 187 while key[k] != (0 as u8) { 188 if ls + k >= le { return 0 } 189 if buf[ls + k] != key[k] { return 0 } 190 k = k + 1 191 } 192 return 1 193} 194 195// author the qemu-alignment spec: copy the main spec, but replace the `base ` line value 196// with the qemu blk slot and the `out ` line with the qemu image path. (The descriptor ring 197// page + data buffer addresses are guest-RAM addresses valid on both lanes -- left as is.) 198func g_write_qemu_spec(mainspec: *u8, derived: *u8, qemu_out: *u8) -> i64 { 199 let buf: *u8 = sys_mmap(8192) 200 let n: i64 = g_read(mainspec, buf, 8192) 201 if n <= 0 { return 0 - 1 } 202 let ofd: i64 = sys_openat_wr(derived, 0x1a4) 203 if ofd < 0 { return 0 - 1 } 204 var ls: i64 = 0 205 while ls < n { 206 var le: i64 = ls 207 var scan: i64 = 1 208 while scan == 1 { if le >= n { scan = 0 } else { if buf[le] == (10 as u8) { scan = 0 } else { le = le + 1 } } } 209 if g_line_is(buf, ls, le, "base " as *u8) == 1 { 210 g_fp(ofd, "base 0x10008000\n" as *u8) 211 } else { 212 if g_line_is(buf, ls, le, "out " as *u8) == 1 { 213 g_fp(ofd, "out " as *u8); g_fp(ofd, qemu_out); g_fp(ofd, "\n" as *u8) 214 } else { 215 sys_write(ofd, (buf as i64 + ls) as *u8, le - ls) 216 g_fp(ofd, "\n" as *u8) 217 } 218 } 219 ls = le + 1 220 } 221 sys_close(ofd) 222 return 0 223} 224 225func main() -> i64 { 226 let spec: *u8 = "knowledge/specs/virtio_blk_descdma_virt.spec" as *u8 227 let binpath: *u8 = "runtime/_hdl_build/_virtio_descdma_virt.bin" as *u8 228 let goldpath: *u8 = "runtime/_hdl_build/_virtio_descdma_virt.bin.gold" as *u8 229 let qemu_spec: *u8 = "/tmp/_descgate_qemu.spec" as *u8 230 let qemu_bin: *u8 = "/tmp/_descgate_qemu.bin" as *u8 231 let backing: *u8 = "/tmp/_descgate_backing.img" as *u8 232 let tamper_bin: *u8 = "/tmp/_descgate_tamper.bin" as *u8 233 let sov_serial: *u8 = "/tmp/_descgate_sov.txt" as *u8 234 let qemu_serial: *u8 = "/tmp/_descgate_qemu.txt" as *u8 235 let sov_tamper: *u8 = "/tmp/_descgate_sov_tamper.txt" as *u8 236 // the b1 prefix golden the qemu alignment lane asserts (the real device has no 237 // QueueDescPeek register, so on qemu the descriptor-DMA verify drops ONLY " DESC"). 238 let prefix_gold: *u8 = "VIO ACK DRV FEAT OK VQ" as *u8 239 g_p("=== virtio-MMIO descriptor-DMA gate (K-R2-001b2a: SOVEREIGN rv64 descriptor lay + device DMA-read + QueueDescPeek read-back + qemu virtio-blk alignment) ===\n" as *u8) 240 241 let lfd: i64 = sys_openat_append("knowledge/status/virtio_blk.log" as *u8, 0x1a4) 242 243 // STEP 1: author the SOVEREIGN image (base 0x10001000) + the golden transcript. 244 let est: i64 = g_run_emit(spec) 245 if est != 0 { 246 g_p("DESCGATE verdict=RED reason=emit-failed\n" as *u8) 247 if lfd >= 0 { g_fp(lfd, "DESCGATE verdict=RED reason=emit-failed\n" as *u8); sys_close(lfd) } 248 sys_exit(1); return 1 249 } 250 let gold: *u8 = sys_mmap(512) 251 let gn: i64 = g_read(goldpath, gold, 512) 252 gold[gn] = 0 as u8 253 if gn <= 0 { 254 g_p("DESCGATE verdict=RED reason=golden-missing\n" as *u8) 255 if lfd >= 0 { g_fp(lfd, "DESCGATE verdict=RED reason=golden-missing\n" as *u8); sys_close(lfd) } 256 sys_exit(1); return 1 257 } 258 259 // STEP 2 (PRIMARY): the Nishi sovereign rv64 emulator RUNS the image -- handshake + 260 // queue config + descriptor lay + QueueNotify kick + device DMA-read + QueueDescPeek 261 // read-back verify -> transcript must CONTAIN the full golden ("...VQ DESC\n"). 262 let sst: i64 = g_run_sov(binpath, sov_serial) 263 let sbuf: *u8 = sys_mmap(65536) 264 let sbn: i64 = g_read(sov_serial, sbuf, 65536) 265 let trans_ok: i64 = g_buf_has(sbuf, sbn, gold, gn) 266 let halt_ok: i64 = g_buf_has(sbuf, sbn, "BOOTSOV verdict=GREEN" as *u8, 21) 267 var sov_ok: i64 = 0 268 if sst == 0 { if trans_ok == 1 { if halt_ok == 1 { sov_ok = 1 } } } 269 270 // STEP 3 (ALIGNMENT): author the qemu-base image (slot 0x10008000) + run qemu with a 271 // REAL legacy virtio-blk-device; the SAME driver lays the SAME descriptor into qemu guest 272 // RAM and kicks the real QueueNotify (the real transport walks the ring). The sovereign- 273 // only QueueDescPeek register reads 0 on the real device, so the descriptor-DMA verify 274 // drops " DESC" there; assert the b1 prefix golden ("VIO ACK DRV FEAT OK VQ") appears, 275 // proving the driver runs identically against the real transport up to the kick. 276 g_make_backing(backing) 277 g_write_qemu_spec(spec, qemu_spec, qemu_bin) 278 let qest: i64 = g_run_emit(qemu_spec) 279 var align: i64 = 0 280 if qest == 0 { 281 let qst: i64 = g_run_qemu(qemu_bin, qemu_serial, backing) 282 let qbuf: *u8 = sys_mmap(65536) 283 let qbn: i64 = g_read(qemu_serial, qbuf, 65536) 284 g_qemu_bytes = qbn 285 let qtrans: i64 = g_buf_has(qbuf, qbn, prefix_gold, g_strlen(prefix_gold)) 286 if qst == 0 { if qtrans == 1 { align = 1 } } 287 } 288 289 // STEP 4 (TAMPER): corrupt the QueueDescPeek-EXPECTED constant in the sovereign image. 290 // Scan for the unique QueueDescPeek read-back load word; the expected li the driver 291 // compares against is the addi at load+8. Bump that addi's high immediate byte (load+11) 292 // so the expected != the value the device DMA-read+exposed -> the read-back verify 293 // branches to the finisher -> ONLY the " DESC" canary drops -> gate MUST go RED. 294 let ibuf: *u8 = sys_mmap(8192) 295 let ibn: i64 = g_read(binpath, ibuf, 8192) 296 let lpos: i64 = g_find_descpeek_load(ibuf, ibn) 297 var tamper_built: i64 = 0 298 if lpos >= 0 { 299 let ebyte: i64 = lpos + 11 // high immediate byte of the expected addi 300 ibuf[ebyte] = (ibuf[ebyte] + 1) as u8 301 let tfd: i64 = sys_openat_wr(tamper_bin, 0x1a4) 302 if tfd >= 0 { sys_write(tfd, ibuf, ibn); sys_close(tfd); tamper_built = 1 } 303 } 304 var tamper_bites: i64 = 0 305 var tamper_keeps_vq: i64 = 0 306 if tamper_built == 1 { 307 let tst: i64 = g_run_sov(tamper_bin, sov_tamper) 308 let tbuf: *u8 = sys_mmap(65536) 309 let tbn: i64 = g_read(sov_tamper, tbuf, 65536) 310 let tamper_trans: i64 = g_buf_has(tbuf, tbn, gold, gn) // full golden should now MISS 311 let tamper_prefix: i64 = g_buf_has(tbuf, tbn, prefix_gold, g_strlen(prefix_gold)) // VQ tail survives 312 if tamper_trans == 0 { tamper_bites = 1 } 313 if tamper_prefix == 1 { tamper_keeps_vq = 1 } 314 } 315 316 // ---- MIGRATED ONTO THE BASE CLASS (D001), BY HAND ---------------------------------------------- 317 // This gate was BUILT-UNPROMOTED: compiled, never promoted, so NOTHING COULD RUN IT. That is why a 318 // regression in the layer it covers went unnoticed -- ★★★★★★A GATE NOBODY CAN RUN DOES NOT DEGRADE 319 // TO NEUTRAL, IT DEGRADES TO A FALSE SENSE OF COVERAGE, BECAUSE ITS EXISTENCE IS COUNTED AND ITS 320 // VERDICT IS NOT. /api/promote refuses a D001 gate, so being unreadable is exactly what kept it 321 // unpromoted, and being unpromoted is what kept the regression invisible. 322 let ctr: *i64 = gv_ctr() 323 gv_check("sovereign_emu: descriptor laid at the QueuePFN-bound ring page, QueueNotify kicked, device DMA-read the desc.addr low word, QueueDescPeek read-back == the driver-written value, transcript == golden, clean halt", sov_ok, ctr) 324 // qemu is an EXTERNAL BINARY, not part of the system under test. Zero captured bytes means it never 325 // ran (absent on this host), and scoring that as a FAILED alignment check indicts OUR driver for the 326 // absence of SOMEONE ELSE'S binary. Same third state as _k_r2_001b3_gate; SKIP is NOT a pass, it 327 // still blocks any claim that the two lanes agree. 328 var qemu_ran: i64 = 0 329 if g_qemu_bytes > 0 { qemu_ran = 1 } 330 if gv_need("qemu-system-riscv64 produced a transcript (the external cross-check binary must exist to be compared against)", qemu_ran, ctr) == 1 { 331 gv_check("qemu_align: the SAME driver lays the SAME descriptor against a REAL legacy virtio-blk-device and both lanes agree on the VQ prefix", align, ctr) 332 } 333 // ★gv_bite IS the shape this tooth already wanted, and `tamper_keeps_vq` was ALREADY its 334 // silent-on-good control -- it was just being AND-ed into a flat conjunction where its meaning was 335 // invisible. A whole-transcript collapse would satisfy "DESC dropped" while proving nothing, so the 336 // surviving VQ prefix is what makes the tamper DISCRIMINATING rather than merely destructive. 337 var tamper_overfired: i64 = 1 338 if tamper_keeps_vq == 1 { tamper_overfired = 0 } 339 gv_bite("tamper-bite-descpeek: corrupting the DescPeek-expected constant DROPS the DESC canary while the VQ prefix SURVIVES", tamper_bites, tamper_overfired, ctr) 340 341 // ONE evidence row, written on EVERY path, carrying the SAME fields either way. The old shape had a 342 // GREEN branch that logged the golden and a RED branch that logged the conjuncts, so whichever field 343 // the reader needed was in the branch that did not run. 344 if lfd >= 0 { 345 g_fp(lfd, "DESCGATE runtime=sovereign-emu transport=legacy-virtio-mmio layer=descriptor-dma desc=addr|len|flags|next ring=QueuePFN<<12 kick=QueueNotify dma=device-read sov_ok=" as *u8); g_fn(lfd, sov_ok) 346 g_fp(lfd, " align_qemu=" as *u8); g_fn(lfd, align) 347 g_fp(lfd, " tamper_bites=" as *u8); g_fn(lfd, tamper_bites) 348 g_fp(lfd, " tamper_keeps_vq=" as *u8); g_fn(lfd, tamper_keeps_vq) 349 g_fp(lfd, " golden=" as *u8); g_fp(lfd, gold) 350 g_fp(lfd, " epoch=" as *u8); g_fn(lfd, sys_now_realtime_sec()) 351 g_fp(lfd, "\n" as *u8); sys_close(lfd) 352 } 353 let rc: i64 = gv_verdict("DESCGATE", ctr, "the sovereign rv64 emu drove the descriptor-DMA layer end to end -- descriptor laid at the QueuePFN-bound ring page, QueueNotify kicked, device DMA-read the descriptor addr-low field, QueueDescPeek read-back verified == the driver-written value, transcript == golden, clean halt; a qemu virtio-blk-device cross-check lays the SAME descriptor against the real transport and agrees on the VQ prefix; and the DescPeek tamper is REJECTED with DESC dropping while VQ survives") 354 sys_exit(rc) 355 return rc 356}