code wiki / _hdl_build / _k_r2_001b1_gate.nx

_k_r2_001b1_gate.nx source

↩ module page · 319 lines · 15022 B

1// _k_r2_001b1_gate.nx -- the K-R2-001b1 gate (virtio-MMIO QUEUE-CONFIGURATION REGISTER 2// layer; the smallest first slice of the K-R2-001b virtqueue epic). Drives the full 3// author->handshake->queue-config chain with NO mocks: runs the REAL nx_virtio_hs_emit (the 4// team AUTHORS the rv64 image + the TABLE-COMPUTED golden transcript from the queue-config 5// spec), then RUNS the image on the SOVEREIGN rv64 emulator (rv64im_min_sim with the 6// rv64im_min_virtio device now carrying the legacy virtqueue-config registers QueueSel / 7// QueueNumMax / QueueNum / QueueAlign / QueuePFN / QueueNotify carved into load32/store32 -- 8// the PRIMARY, gating lane: Nishi owns the runtime) and asserts the captured serial 9// transcript CONTAINS the emitter's golden ("VIO ACK DRV FEAT OK VQ\n" -- after the 10// handshake the driver selected virtqueue 0, read QueueNumMax, latched the ring size, bound 11// the guest ring page-frame via QueuePFN, kicked QueueNotify, and READ QueuePFN BACK to 12// confirm the binding) AND the sovereign emu reports a clean SiFive-finisher halt. 13// 14// Then the ALIGNMENT lane: qemu-system-riscv64 -machine virt -global 15// virtio-mmio.force-legacy=true with a REAL virtio-blk-device backing. qemu fills its 16// virtio-mmio slots in REVERSE (the blk device lands at slot 0x10008000), so the gate 17// authors a SECOND image from a base-rewritten spec and runs THAT on qemu; the SAME driver 18// completes the queue config against the REAL legacy virtio-blk transport (QueuePFN 19// read-back is genuinely RW on the real device) and emits the SAME golden -> lanes AGREE. 20// 21// Finally a TAMPER test: corrupt the QueuePFN-EXPECTED constant in the sovereign image (the 22// driver loads its own QueuePFN read-back into t3 and compares against this li-loaded 23// expected in t4; bumping the expected's immediate makes t3 != t4) -> the read-back verify 24// branches PAST stage 5 straight to the finisher -> the transcript loses ONLY its "VQ\n" 25// canary (the handshake tail survives) -> the gate MUST go RED. Evidence -> 26// knowledge/status/virtio_blk.log (VQCFGGATE row; the queue row's ||MARK= reads it). 27// Sovereign orchestration (fork/dup3/execve/wait4). license_tier: ORIGINAL 28import "nx_syscalls.nx" 29 30// the unique rv64 encoding of `lw t3, 0x40(t2)` -- the QueuePFN read-back load. The 31// QueuePFN-expected constant the driver compares against is the li that follows it (lui 32// at +4, addi at +8); corrupting the addi's immediate is the queue-config tamper. 33const G_QPFN_LOAD_WORD: i64 = 0x0403ae03 // lw t3, 0x040(t2) (QueuePFN read-back) 34 35func 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 } 36func 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 } 37func g_fn(fd: i64, v: i64) -> i64 { let bb: *u8=sys_mmap(28); var m: i64=v; if m<0{m=0-m}; 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 } 38 39// run nx_virtio_hs_emit <spec>; return child wait status (0 = ok) 40func g_run_emit(spec: *u8) -> i64 { 41 let pid: i64 = sys_fork() 42 if pid == 0 { 43 let dn: i64 = sys_openat_wr("/dev/null" as *u8, 0x1a4) 44 if dn >= 0 { sys_dup3(dn, 1, 0) } 45 let argv: *i64 = sys_mmap(32) as *i64 46 argv[0] = "_offc/nx_virtio_hs_emit.elf" as *u8 as i64 47 argv[1] = spec as i64 48 argv[2] = 0 49 let envp: *i64 = sys_mmap(16) as *i64 50 envp[0] = "PATH=/usr/bin:/bin" as *u8 as i64 51 envp[1] = 0 52 sys_execve("_offc/nx_virtio_hs_emit.elf" as *u8, argv, envp) 53 sys_exit(127) 54 } 55 let st: *i64 = sys_mmap(16) as *i64 56 sys_wait4(pid, st, 0) 57 return st[0] 58} 59 60// run the SOVEREIGN rv64 emulator on binpath; serial -> outpath; return wait status 61func g_run_sov(binpath: *u8, outpath: *u8) -> i64 { 62 let pid: i64 = sys_fork() 63 if pid == 0 { 64 let ofd: i64 = sys_openat_wr(outpath, 0x1a4) 65 if ofd >= 0 { sys_dup3(ofd, 1, 0); sys_dup3(ofd, 2, 0) } 66 let argv: *i64 = sys_mmap(32) as *i64 67 argv[0] = "_offc/nx_boot_run_sov.elf" as *u8 as i64 68 argv[1] = binpath as i64 69 argv[2] = 0 70 let envp: *i64 = sys_mmap(16) as *i64 71 envp[0] = "PATH=/usr/bin:/bin" as *u8 as i64 72 envp[1] = 0 73 sys_execve("_offc/nx_boot_run_sov.elf" as *u8, argv, envp) 74 sys_exit(127) 75 } 76 let st: *i64 = sys_mmap(16) as *i64 77 sys_wait4(pid, st, 0) 78 return st[0] 79} 80 81// run qemu-system-riscv64 virt + legacy virtio-blk-device on binpath; serial -> outpath. 82func g_run_qemu(binpath: *u8, outpath: *u8, backing: *u8) -> i64 { 83 let pid: i64 = sys_fork() 84 if pid == 0 { 85 let ofd: i64 = sys_openat_wr(outpath, 0x1a4) 86 if ofd >= 0 { sys_dup3(ofd, 1, 0); sys_dup3(ofd, 2, 0) } 87 let argv: *i64 = sys_mmap(128) as *i64 88 argv[0] = "/usr/bin/qemu-system-riscv64" as *u8 as i64 89 argv[1] = "-machine" as *u8 as i64 90 argv[2] = "virt" as *u8 as i64 91 argv[3] = "-global" as *u8 as i64 92 argv[4] = "virtio-mmio.force-legacy=true" as *u8 as i64 93 argv[5] = "-nographic" as *u8 as i64 94 argv[6] = "-bios" as *u8 as i64 95 argv[7] = binpath as i64 96 argv[8] = "-drive" as *u8 as i64 97 argv[9] = "file=/tmp/_vqcfggate_backing.img,if=none,format=raw,id=hd0" as *u8 as i64 98 argv[10] = "-device" as *u8 as i64 99 argv[11] = "virtio-blk-device,drive=hd0" as *u8 as i64 100 argv[12] = 0 101 let envp: *i64 = sys_mmap(16) as *i64 102 envp[0] = "PATH=/usr/bin:/bin" as *u8 as i64 103 envp[1] = 0 104 sys_execve("/usr/bin/qemu-system-riscv64" as *u8, argv, envp) 105 sys_exit(127) 106 } 107 let st: *i64 = sys_mmap(16) as *i64 108 sys_wait4(pid, st, 0) 109 return st[0] 110} 111 112// create a small raw backing file for the qemu virtio-blk device (4 sectors of zeros). 113func g_make_backing(path: *u8) -> i64 { 114 let fd: i64 = sys_openat_wr(path, 0x1a4) 115 if fd < 0 { return 0 - 1 } 116 let z: *u8 = sys_mmap(2048) 117 var i: i64 = 0 118 while i < 2048 { z[i] = 0 as u8; i = i + 1 } 119 sys_write(fd, z, 2048) 120 sys_close(fd) 121 return 0 122} 123 124// read whole file into buf (cap-1 max); return byte count (0 if absent) 125func g_read(path: *u8, buf: *u8, cap: i64) -> i64 { 126 let fd: i64 = sys_openat_rd(path) 127 if fd < 0 { return 0 } 128 var n: i64 = 0 129 var go: i64 = 1 130 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 } } 131 sys_close(fd) 132 return n 133} 134 135// does buf[0,n) contain pat (length pl)? 1/0 136func g_buf_has(buf: *u8, n: i64, pat: *u8, pl: i64) -> i64 { 137 if pl <= 0 { return 0 } 138 var i: i64 = 0 139 while i + pl <= n { 140 var k: i64 = 0 141 var hit: i64 = 1 142 while k < pl { if buf[i+k] != pat[k] { hit = 0; k = pl } else { k = k + 1 } } 143 if hit == 1 { return 1 } 144 i = i + 1 145 } 146 return 0 147} 148 149// read a little-endian 32-bit word from buf at byte offset o. 150func g_w32(buf: *u8, o: i64) -> i64 { 151 let b0: i64 = buf[o] as i64 152 let b1: i64 = buf[o+1] as i64 153 let b2: i64 = buf[o+2] as i64 154 let b3: i64 = buf[o+3] as i64 155 return b0 | (b1 << 8) | (b2 << 16) | (b3 << 24) 156} 157 158// find the byte offset of the (unique) QueuePFN read-back load word in the image; -1 if 159// absent. The QueuePFN-expected li the driver compares against is at off+4 (lui) / off+8 160// (addi); the addi's high immediate byte is at off+11. 161func g_find_qpfn_load(buf: *u8, n: i64) -> i64 { 162 var o: i64 = 0 163 while o + 4 <= n { 164 if g_w32(buf, o) == G_QPFN_LOAD_WORD { return o } 165 o = o + 4 166 } 167 return 0 - 1 168} 169 170func g_strlen(s: *u8) -> i64 { var n: i64 = 0; while s[n] != (0 as u8) { n = n + 1 } return n } 171 172// does line [ls,le) of buf begin with key? 1/0 173func g_line_is(buf: *u8, ls: i64, le: i64, key: *u8) -> i64 { 174 var k: i64 = 0 175 while key[k] != (0 as u8) { 176 if ls + k >= le { return 0 } 177 if buf[ls + k] != key[k] { return 0 } 178 k = k + 1 179 } 180 return 1 181} 182 183// author the qemu-alignment spec: copy the main spec, but replace the `base ` line value 184// with the qemu blk slot and the `out ` line with the qemu image path. 185func g_write_qemu_spec(mainspec: *u8, derived: *u8, qemu_out: *u8) -> i64 { 186 let buf: *u8 = sys_mmap(8192) 187 let n: i64 = g_read(mainspec, buf, 8192) 188 if n <= 0 { return 0 - 1 } 189 let ofd: i64 = sys_openat_wr(derived, 0x1a4) 190 if ofd < 0 { return 0 - 1 } 191 var ls: i64 = 0 192 while ls < n { 193 var le: i64 = ls 194 var scan: i64 = 1 195 while scan == 1 { if le >= n { scan = 0 } else { if buf[le] == (10 as u8) { scan = 0 } else { le = le + 1 } } } 196 if g_line_is(buf, ls, le, "base " as *u8) == 1 { 197 g_fp(ofd, "base 0x10008000\n" as *u8) 198 } else { 199 if g_line_is(buf, ls, le, "out " as *u8) == 1 { 200 g_fp(ofd, "out " as *u8); g_fp(ofd, qemu_out); g_fp(ofd, "\n" as *u8) 201 } else { 202 sys_write(ofd, (buf as i64 + ls) as *u8, le - ls) 203 g_fp(ofd, "\n" as *u8) 204 } 205 } 206 ls = le + 1 207 } 208 sys_close(ofd) 209 return 0 210} 211 212func main() -> i64 { 213 let spec: *u8 = "knowledge/specs/virtio_blk_queuecfg_virt.spec" as *u8 214 let binpath: *u8 = "runtime/_hdl_build/_virtio_vqcfg_virt.bin" as *u8 215 let goldpath: *u8 = "runtime/_hdl_build/_virtio_vqcfg_virt.bin.gold" as *u8 216 let qemu_spec: *u8 = "/tmp/_vqcfggate_qemu.spec" as *u8 217 let qemu_bin: *u8 = "/tmp/_vqcfggate_qemu.bin" as *u8 218 let backing: *u8 = "/tmp/_vqcfggate_backing.img" as *u8 219 let tamper_bin: *u8 = "/tmp/_vqcfggate_tamper.bin" as *u8 220 let sov_serial: *u8 = "/tmp/_vqcfggate_sov.txt" as *u8 221 let qemu_serial: *u8 = "/tmp/_vqcfggate_qemu.txt" as *u8 222 let sov_tamper: *u8 = "/tmp/_vqcfggate_sov_tamper.txt" as *u8 223 g_p("=== virtio-MMIO queue-config gate (K-R2-001b1: SOVEREIGN rv64 virtqueue-config registers + qemu virtio-blk alignment) ===\n" as *u8) 224 225 let lfd: i64 = sys_openat_append("knowledge/status/virtio_blk.log" as *u8, 0x1a4) 226 227 // STEP 1: author the SOVEREIGN image (base 0x10001000) + the golden transcript. 228 let est: i64 = g_run_emit(spec) 229 if est != 0 { 230 g_p("VQCFGGATE verdict=RED reason=emit-failed\n" as *u8) 231 if lfd >= 0 { g_fp(lfd, "VQCFGGATE verdict=RED reason=emit-failed\n" as *u8); sys_close(lfd) } 232 sys_exit(1); return 1 233 } 234 let gold: *u8 = sys_mmap(512) 235 let gn: i64 = g_read(goldpath, gold, 512) 236 gold[gn] = 0 as u8 237 if gn <= 0 { 238 g_p("VQCFGGATE verdict=RED reason=golden-missing\n" as *u8) 239 if lfd >= 0 { g_fp(lfd, "VQCFGGATE verdict=RED reason=golden-missing\n" as *u8); sys_close(lfd) } 240 sys_exit(1); return 1 241 } 242 243 // STEP 2 (PRIMARY): the Nishi sovereign rv64 emulator RUNS the image -- handshake + 244 // virtqueue config + QueuePFN read-back -> transcript must CONTAIN the golden. 245 let sst: i64 = g_run_sov(binpath, sov_serial) 246 let sbuf: *u8 = sys_mmap(65536) 247 let sbn: i64 = g_read(sov_serial, sbuf, 65536) 248 let trans_ok: i64 = g_buf_has(sbuf, sbn, gold, gn) 249 let halt_ok: i64 = g_buf_has(sbuf, sbn, "BOOTSOV verdict=GREEN" as *u8, 21) 250 var sov_ok: i64 = 0 251 if sst == 0 { if trans_ok == 1 { if halt_ok == 1 { sov_ok = 1 } } } 252 253 // STEP 3 (ALIGNMENT): author the qemu-base image (slot 0x10008000) + run qemu with a 254 // REAL legacy virtio-blk-device; assert the SAME golden appears (QueuePFN read-back is 255 // genuinely RW on the real transport, so the driver's verify passes there too). 256 g_make_backing(backing) 257 g_write_qemu_spec(spec, qemu_spec, qemu_bin) 258 let qest: i64 = g_run_emit(qemu_spec) 259 var align: i64 = 0 260 if qest == 0 { 261 let qst: i64 = g_run_qemu(qemu_bin, qemu_serial, backing) 262 let qbuf: *u8 = sys_mmap(65536) 263 let qbn: i64 = g_read(qemu_serial, qbuf, 65536) 264 let qtrans: i64 = g_buf_has(qbuf, qbn, gold, gn) 265 if qst == 0 { if qtrans == 1 { align = 1 } } 266 } 267 268 // STEP 4 (TAMPER): corrupt the QueuePFN-EXPECTED constant in the sovereign image. Scan 269 // for the unique QueuePFN read-back load word; the expected li the driver compares 270 // against is the addi at load+8. Bump that addi's high immediate byte (load+11) so the 271 // expected != the value the driver actually wrote+read-back -> the read-back verify 272 // branches to the finisher -> ONLY the "VQ\n" canary drops -> gate MUST go RED. 273 let ibuf: *u8 = sys_mmap(8192) 274 let ibn: i64 = g_read(binpath, ibuf, 8192) 275 let lpos: i64 = g_find_qpfn_load(ibuf, ibn) 276 var tamper_built: i64 = 0 277 if lpos >= 0 { 278 let ebyte: i64 = lpos + 11 // high immediate byte of the expected addi 279 ibuf[ebyte] = (ibuf[ebyte] + 1) as u8 280 let tfd: i64 = sys_openat_wr(tamper_bin, 0x1a4) 281 if tfd >= 0 { sys_write(tfd, ibuf, ibn); sys_close(tfd); tamper_built = 1 } 282 } 283 var tamper_bites: i64 = 0 284 if tamper_built == 1 { 285 let tst: i64 = g_run_sov(tamper_bin, sov_tamper) 286 let tbuf: *u8 = sys_mmap(65536) 287 let tbn: i64 = g_read(sov_tamper, tbuf, 65536) 288 let tamper_trans: i64 = g_buf_has(tbuf, tbn, gold, gn) // golden should now MISS 289 if tamper_trans == 0 { tamper_bites = 1 } 290 } 291 292 g_p(" sovereign_emu=" as *u8) 293 if sov_ok == 1 { g_p("GREEN(transcript==golden+clean-halt)" as *u8) } else { g_p("RED" as *u8) } 294 g_p(" qemu_align=" as *u8) 295 if align == 1 { g_p("yes(real-virtio-blk @0x10008000, QueuePFN read-back)" as *u8) } else { g_p("no" as *u8) } 296 g_p(" tamper_bites=" as *u8) 297 if tamper_bites == 1 { g_p("yes(QueuePFN-expected corrupt -> VQ drops)\n" as *u8) } else { g_p("no\n" as *u8) } 298 299 var pass: i64 = 0 300 if sov_ok == 1 { if align == 1 { if tamper_bites == 1 { pass = 1 } } } 301 302 if pass == 1 { 303 g_p("VQCFGGATE verdict=GREEN (sovereign rv64 emu drove the virtqueue-config layer; QueueSel/QueueNumMax/QueueNum/QueuePFN/QueueNotify; QueuePFN read-back verified; transcript==golden; clean halt; qemu virtio-blk-device cross-check AGREES; QueuePFN tamper REJECTED)\n" as *u8) 304 if lfd >= 0 { 305 g_fp(lfd, "VQCFGGATE verdict=GREEN runtime=sovereign-emu transport=legacy-virtio-mmio layer=queue-config queues=QueueSel|QueueNumMax|QueueNum|QueueAlign|QueuePFN|QueueNotify queuepfn-readback=verified transcript==golden align_qemu=yes(virtio-blk-device) tamper=rejected golden=" as *u8) 306 g_fp(lfd, gold) 307 g_fp(lfd, " epoch=" as *u8); g_fn(lfd, sys_now_realtime_sec()); g_fp(lfd, "\n" as *u8); sys_close(lfd) 308 } 309 sys_exit(0); return 0 310 } 311 g_p("VQCFGGATE verdict=RED (sov_ok/align/tamper not all green)\n" as *u8) 312 if lfd >= 0 { 313 g_fp(lfd, "VQCFGGATE verdict=RED sov_ok=" as *u8); g_fn(lfd, sov_ok) 314 g_fp(lfd, " align=" as *u8); g_fn(lfd, align) 315 g_fp(lfd, " tamper_bites=" as *u8); g_fn(lfd, tamper_bites); g_fp(lfd, "\n" as *u8); sys_close(lfd) 316 } 317 sys_exit(1) 318 return 1 319}