code wiki / _hdl_build / _k_r2_001b2c_gate.nx
_k_r2_001b2c_gate.nx source
↩ module page · 347 lines · 17811 B
1// _k_r2_001b2c_gate.nx -- the K-R2-001b2c gate (virtio-MMIO STATUS-BYTE WRITEBACK layer; the
2// fourth slice of the K-R2-001b virtqueue epic, on top of b2b's avail-consume + used-ring
3// writeback). Drives the full author->handshake->queue-config->descriptor-DMA->avail/used->
4// status-byte chain with NO mocks: runs the REAL nx_virtio_hs_emit (the team AUTHORS the rv64
5// image + the TABLE-COMPUTED golden transcript from the status spec), then RUNS the image on
6// the SOVEREIGN rv64 emulator (rv64im_min_sim + the rv64im_min_virtio device now, on the b2b
7// QueueNotify kick, after the used-ring writeback, DMA-WRITING the virtio-blk request status
8// byte VIRTIO_BLK_S_OK=0 into the status descriptor's guest buffer + latching it into the
9// QueueStatPeek RO register -- the PRIMARY, gating lane: Nishi owns the runtime) and asserts
10// the captured serial transcript CONTAINS the emitter's golden ("VIO ACK DRV FEAT OK VQ DESC
11// USED STAT\n" -- after the used-ring writeback the device wrote the OK status byte, the
12// driver READ QueueStatPeek BACK and verified it == stat_expected: the status-byte writeback
13// binding proof) AND the 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 + the SAME avail ring into qemu guest RAM, and kicks
20// the real QueueNotify -- the real legacy transport genuinely walks the ring. The sovereign-
21// only QueueDescPeek / QueueUsedIdxPeek / QueueStatPeek result registers are model instruments
22// the real device does not expose (they read 0), so on qemu the descriptor-DMA + used-ring +
23// status-byte verifies gracefully drop " DESC" / " USED" / " STAT"; the gate asserts the qemu
24// transcript contains the b1 prefix golden ("VIO ACK DRV FEAT OK VQ") -- proving the SAME
25// driver lays the avail ring + kicks the real device -> lanes AGREE on every register the real
26// transport can observe.
27//
28// Finally a TAMPER test: corrupt the QueueStatPeek-EXPECTED constant in the sovereign image
29// (the driver loads its own QueueStatPeek read-back into t3 via lwu and compares against this
30// li-loaded expected in t4; bumping the expected's immediate makes t3 != t4) -> the read-back
31// verify branches PAST stage 8 straight to the finisher -> the transcript loses ONLY its
32// " STAT" canary (the "VIO ACK DRV FEAT OK VQ DESC USED" tail survives) -> the gate MUST go
33// RED. Evidence -> knowledge/status/virtio_blk.log (STATGATE row; the queue row's ||MARK=
34// reads it). Sovereign orchestration (fork/dup3/execve/wait4). license_tier: ORIGINAL
35import "nx_syscalls.nx"
36
37// the unique rv64 encoding of `lwu t3, 0x68(t2)` -- the QueueStatPeek read-back load (f3=6,
38// zero-extend; the status byte is a small non-negative int). The StatPeek-expected constant
39// the driver compares against is the li that follows it (lui at +4, addi at +8); corrupting
40// the addi's high immediate byte (+11) is the status-byte tamper.
41const G_QSTATPEEK_LOAD_WORD: i64 = 0x0683ee03 // lwu t3, 0x068(t2) (QueueStatPeek read-back)
42
43func 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 }
44func 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 }
45func 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 }
46
47// run nx_virtio_hs_emit <spec>; return child wait status (0 = ok)
48func g_run_emit(spec: *u8) -> i64 {
49 let pid: i64 = sys_fork()
50 if pid == 0 {
51 let dn: i64 = sys_openat_wr("/dev/null" as *u8, 0x1a4)
52 if dn >= 0 { sys_dup3(dn, 1, 0) }
53 let argv: *i64 = sys_mmap(32) as *i64
54 argv[0] = "_offc/nx_virtio_hs_emit.elf" as *u8 as i64
55 argv[1] = spec as i64
56 argv[2] = 0
57 let envp: *i64 = sys_mmap(16) as *i64
58 envp[0] = "PATH=/usr/bin:/bin" as *u8 as i64
59 envp[1] = 0
60 sys_execve("_offc/nx_virtio_hs_emit.elf" as *u8, argv, envp)
61 sys_exit(127)
62 }
63 let st: *i64 = sys_mmap(16) as *i64
64 sys_wait4(pid, st, 0)
65 return st[0]
66}
67
68// run the SOVEREIGN rv64 emulator on binpath; serial -> outpath; return wait status
69func g_run_sov(binpath: *u8, outpath: *u8) -> i64 {
70 let pid: i64 = sys_fork()
71 if pid == 0 {
72 let ofd: i64 = sys_openat_wr(outpath, 0x1a4)
73 if ofd >= 0 { sys_dup3(ofd, 1, 0); sys_dup3(ofd, 2, 0) }
74 let argv: *i64 = sys_mmap(32) as *i64
75 argv[0] = "_offc/nx_boot_run_sov.elf" as *u8 as i64
76 argv[1] = binpath as i64
77 argv[2] = 0
78 let envp: *i64 = sys_mmap(16) as *i64
79 envp[0] = "PATH=/usr/bin:/bin" as *u8 as i64
80 envp[1] = 0
81 sys_execve("_offc/nx_boot_run_sov.elf" as *u8, argv, envp)
82 sys_exit(127)
83 }
84 let st: *i64 = sys_mmap(16) as *i64
85 sys_wait4(pid, st, 0)
86 return st[0]
87}
88
89// run qemu-system-riscv64 virt + legacy virtio-blk-device on binpath; serial -> outpath.
90func g_run_qemu(binpath: *u8, outpath: *u8, backing: *u8) -> i64 {
91 let pid: i64 = sys_fork()
92 if pid == 0 {
93 let ofd: i64 = sys_openat_wr(outpath, 0x1a4)
94 if ofd >= 0 { sys_dup3(ofd, 1, 0); sys_dup3(ofd, 2, 0) }
95 let argv: *i64 = sys_mmap(128) as *i64
96 argv[0] = "/usr/bin/qemu-system-riscv64" as *u8 as i64
97 argv[1] = "-machine" as *u8 as i64
98 argv[2] = "virt" as *u8 as i64
99 argv[3] = "-global" as *u8 as i64
100 argv[4] = "virtio-mmio.force-legacy=true" as *u8 as i64
101 argv[5] = "-nographic" as *u8 as i64
102 argv[6] = "-bios" as *u8 as i64
103 argv[7] = binpath as i64
104 argv[8] = "-drive" as *u8 as i64
105 argv[9] = "file=/tmp/_statgate_backing.img,if=none,format=raw,id=hd0" as *u8 as i64
106 argv[10] = "-device" as *u8 as i64
107 argv[11] = "virtio-blk-device,drive=hd0" as *u8 as i64
108 argv[12] = 0
109 let envp: *i64 = sys_mmap(16) as *i64
110 envp[0] = "PATH=/usr/bin:/bin" as *u8 as i64
111 envp[1] = 0
112 sys_execve("/usr/bin/qemu-system-riscv64" as *u8, argv, envp)
113 sys_exit(127)
114 }
115 let st: *i64 = sys_mmap(16) as *i64
116 sys_wait4(pid, st, 0)
117 return st[0]
118}
119
120// create a small raw backing file for the qemu virtio-blk device (4 sectors of zeros).
121func g_make_backing(path: *u8) -> i64 {
122 let fd: i64 = sys_openat_wr(path, 0x1a4)
123 if fd < 0 { return 0 - 1 }
124 let z: *u8 = sys_mmap(2048)
125 var i: i64 = 0
126 while i < 2048 { z[i] = 0 as u8; i = i + 1 }
127 sys_write(fd, z, 2048)
128 sys_close(fd)
129 return 0
130}
131
132// read whole file into buf (cap-1 max); return byte count (0 if absent)
133func g_read(path: *u8, buf: *u8, cap: i64) -> i64 {
134 let fd: i64 = sys_openat_rd(path)
135 if fd < 0 { return 0 }
136 var n: i64 = 0
137 var go: i64 = 1
138 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 } }
139 sys_close(fd)
140 return n
141}
142
143// does buf[0,n) contain pat (length pl)? 1/0
144func g_buf_has(buf: *u8, n: i64, pat: *u8, pl: i64) -> i64 {
145 if pl <= 0 { return 0 }
146 var i: i64 = 0
147 while i + pl <= n {
148 var k: i64 = 0
149 var hit: i64 = 1
150 while k < pl { if buf[i+k] != pat[k] { hit = 0; k = pl } else { k = k + 1 } }
151 if hit == 1 { return 1 }
152 i = i + 1
153 }
154 return 0
155}
156
157// read a little-endian 32-bit word from buf at byte offset o.
158func g_w32(buf: *u8, o: i64) -> i64 {
159 let b0: i64 = buf[o] as i64
160 let b1: i64 = buf[o+1] as i64
161 let b2: i64 = buf[o+2] as i64
162 let b3: i64 = buf[o+3] as i64
163 return b0 | (b1 << 8) | (b2 << 16) | (b3 << 24)
164}
165
166// find the byte offset of the (unique) QueueStatPeek read-back load word in the image; -1
167// if absent. The StatPeek-expected li the driver compares against is at off+4 (lui) /
168// off+8 (addi); the addi's high immediate byte is at off+11.
169func g_find_statpeek_load(buf: *u8, n: i64) -> i64 {
170 var o: i64 = 0
171 while o + 4 <= n {
172 if g_w32(buf, o) == G_QSTATPEEK_LOAD_WORD { return o }
173 o = o + 4
174 }
175 return 0 - 1
176}
177
178func g_strlen(s: *u8) -> i64 { var n: i64 = 0; while s[n] != (0 as u8) { n = n + 1 } return n }
179
180// does line [ls,le) of buf begin with key? 1/0
181func g_line_is(buf: *u8, ls: i64, le: i64, key: *u8) -> i64 {
182 var k: i64 = 0
183 while key[k] != (0 as u8) {
184 if ls + k >= le { return 0 }
185 if buf[ls + k] != key[k] { return 0 }
186 k = k + 1
187 }
188 return 1
189}
190
191// author the qemu-alignment spec: copy the main spec, but replace the `base ` line value
192// with the qemu blk slot and the `out ` line with the qemu image path. (The ring page +
193// data buffer + status buffer addresses are guest-RAM addresses valid on both lanes -- left
194// as is.)
195func g_write_qemu_spec(mainspec: *u8, derived: *u8, qemu_out: *u8) -> i64 {
196 let buf: *u8 = sys_mmap(16384)
197 let n: i64 = g_read(mainspec, buf, 16384)
198 if n <= 0 { return 0 - 1 }
199 let ofd: i64 = sys_openat_wr(derived, 0x1a4)
200 if ofd < 0 { return 0 - 1 }
201 var ls: i64 = 0
202 while ls < n {
203 var le: i64 = ls
204 var scan: i64 = 1
205 while scan == 1 { if le >= n { scan = 0 } else { if buf[le] == (10 as u8) { scan = 0 } else { le = le + 1 } } }
206 if g_line_is(buf, ls, le, "base " as *u8) == 1 {
207 g_fp(ofd, "base 0x10008000\n" as *u8)
208 } else {
209 if g_line_is(buf, ls, le, "out " as *u8) == 1 {
210 g_fp(ofd, "out " as *u8); g_fp(ofd, qemu_out); g_fp(ofd, "\n" as *u8)
211 } else {
212 sys_write(ofd, (buf as i64 + ls) as *u8, le - ls)
213 g_fp(ofd, "\n" as *u8)
214 }
215 }
216 ls = le + 1
217 }
218 sys_close(ofd)
219 return 0
220}
221
222func main() -> i64 {
223 let spec: *u8 = "knowledge/specs/virtio_blk_status_virt.spec" as *u8
224 let binpath: *u8 = "runtime/_hdl_build/_virtio_status_virt.bin" as *u8
225 let goldpath: *u8 = "runtime/_hdl_build/_virtio_status_virt.bin.gold" as *u8
226 let qemu_spec: *u8 = "/tmp/_statgate_qemu.spec" as *u8
227 let qemu_bin: *u8 = "/tmp/_statgate_qemu.bin" as *u8
228 let backing: *u8 = "/tmp/_statgate_backing.img" as *u8
229 let tamper_bin: *u8 = "/tmp/_statgate_tamper.bin" as *u8
230 let sov_serial: *u8 = "/tmp/_statgate_sov.txt" as *u8
231 let qemu_serial: *u8 = "/tmp/_statgate_qemu.txt" as *u8
232 let sov_tamper: *u8 = "/tmp/_statgate_sov_tamper.txt" as *u8
233 // the b1 prefix golden the qemu alignment lane asserts (the real device has none of
234 // QueueDescPeek / QueueUsedIdxPeek / QueueStatPeek, so on qemu the descriptor-DMA + used-
235 // ring + status-byte verifies drop " DESC" / " USED" / " STAT", leaving the b1 prefix the
236 // real transport can observe).
237 let prefix_gold: *u8 = "VIO ACK DRV FEAT OK VQ" as *u8
238 // the b2b "USED" tail the tamper must KEEP (only " STAT" drops under the status-byte tamper).
239 let usedonly_gold: *u8 = "VIO ACK DRV FEAT OK VQ DESC USED" as *u8
240 g_p("=== virtio-MMIO status-byte gate (K-R2-001b2c: SOVEREIGN rv64 device status-byte writeback + QueueStatPeek read-back + qemu virtio-blk alignment) ===\n" as *u8)
241
242 let lfd: i64 = sys_openat_append("knowledge/status/virtio_blk.log" as *u8, 0x1a4)
243
244 // STEP 1: author the SOVEREIGN image (base 0x10001000) + the golden transcript.
245 let est: i64 = g_run_emit(spec)
246 if est != 0 {
247 g_p("STATGATE verdict=RED reason=emit-failed\n" as *u8)
248 if lfd >= 0 { g_fp(lfd, "STATGATE verdict=RED reason=emit-failed\n" as *u8); sys_close(lfd) }
249 sys_exit(1); return 1
250 }
251 let gold: *u8 = sys_mmap(512)
252 let gn: i64 = g_read(goldpath, gold, 512)
253 gold[gn] = 0 as u8
254 if gn <= 0 {
255 g_p("STATGATE verdict=RED reason=golden-missing\n" as *u8)
256 if lfd >= 0 { g_fp(lfd, "STATGATE verdict=RED reason=golden-missing\n" as *u8); sys_close(lfd) }
257 sys_exit(1); return 1
258 }
259
260 // STEP 2 (PRIMARY): the Nishi sovereign rv64 emulator RUNS the image -- handshake + queue
261 // config + descriptor lay (b2a) + avail-ring publish + used-ring writeback (b2b) + status-
262 // byte writeback + QueueStatPeek read-back verify -> transcript must CONTAIN the full golden
263 // ("...USED STAT\n").
264 let sst: i64 = g_run_sov(binpath, sov_serial)
265 let sbuf: *u8 = sys_mmap(65536)
266 let sbn: i64 = g_read(sov_serial, sbuf, 65536)
267 let trans_ok: i64 = g_buf_has(sbuf, sbn, gold, gn)
268 let halt_ok: i64 = g_buf_has(sbuf, sbn, "BOOTSOV verdict=GREEN" as *u8, 21)
269 var sov_ok: i64 = 0
270 if sst == 0 { if trans_ok == 1 { if halt_ok == 1 { sov_ok = 1 } } }
271
272 // STEP 3 (ALIGNMENT): author the qemu-base image (slot 0x10008000) + run qemu with a REAL
273 // legacy virtio-blk-device; the SAME driver lays the SAME descriptor + the SAME avail ring
274 // into qemu guest RAM and kicks the real QueueNotify (the real transport walks the ring).
275 // The sovereign-only QueueDescPeek/QueueUsedIdxPeek/QueueStatPeek registers read 0 on the
276 // real device, so the descriptor-DMA + used-ring + status-byte verifies drop " DESC"/
277 // " USED"/" STAT" there; assert the b1 prefix golden ("VIO ACK DRV FEAT OK VQ") appears,
278 // proving the driver runs identically against the real transport up to the avail publish +
279 // kick.
280 g_make_backing(backing)
281 g_write_qemu_spec(spec, qemu_spec, qemu_bin)
282 let qest: i64 = g_run_emit(qemu_spec)
283 var align: i64 = 0
284 if qest == 0 {
285 let qst: i64 = g_run_qemu(qemu_bin, qemu_serial, backing)
286 let qbuf: *u8 = sys_mmap(65536)
287 let qbn: i64 = g_read(qemu_serial, qbuf, 65536)
288 let qtrans: i64 = g_buf_has(qbuf, qbn, prefix_gold, g_strlen(prefix_gold))
289 if qst == 0 { if qtrans == 1 { align = 1 } }
290 }
291
292 // STEP 4 (TAMPER): corrupt the QueueStatPeek-EXPECTED constant in the sovereign image.
293 // Scan for the unique QueueStatPeek read-back load word; the expected li the driver
294 // compares against is the addi at load+8. Bump that addi's high immediate byte (load+11)
295 // so the expected != the value the device latched -> the read-back verify branches to the
296 // finisher -> ONLY the " STAT" canary drops (the "...USED" tail survives) -> gate MUST RED.
297 let ibuf: *u8 = sys_mmap(8192)
298 let ibn: i64 = g_read(binpath, ibuf, 8192)
299 let lpos: i64 = g_find_statpeek_load(ibuf, ibn)
300 var tamper_built: i64 = 0
301 if lpos >= 0 {
302 let ebyte: i64 = lpos + 11 // high immediate byte of the expected addi
303 ibuf[ebyte] = (ibuf[ebyte] + 1) as u8
304 let tfd: i64 = sys_openat_wr(tamper_bin, 0x1a4)
305 if tfd >= 0 { sys_write(tfd, ibuf, ibn); sys_close(tfd); tamper_built = 1 }
306 }
307 var tamper_bites: i64 = 0
308 var tamper_keeps_used: i64 = 0
309 if tamper_built == 1 {
310 let tst: i64 = g_run_sov(tamper_bin, sov_tamper)
311 let tbuf: *u8 = sys_mmap(65536)
312 let tbn: i64 = g_read(sov_tamper, tbuf, 65536)
313 let tamper_trans: i64 = g_buf_has(tbuf, tbn, gold, gn) // full golden should now MISS
314 let tamper_used: i64 = g_buf_has(tbuf, tbn, usedonly_gold, g_strlen(usedonly_gold)) // USED tail survives
315 if tamper_trans == 0 { tamper_bites = 1 }
316 if tamper_used == 1 { tamper_keeps_used = 1 }
317 }
318
319 g_p(" sovereign_emu=" as *u8)
320 if sov_ok == 1 { g_p("GREEN(transcript==golden+clean-halt)" as *u8) } else { g_p("RED" as *u8) }
321 g_p(" qemu_align=" as *u8)
322 if align == 1 { g_p("yes(real-virtio-blk @0x10008000, same descriptor+avail laid + real kick, VQ prefix)" as *u8) } else { g_p("no" as *u8) }
323 g_p(" tamper_bites=" as *u8)
324 if tamper_bites == 1 { g_p("yes(StatPeek-expected corrupt -> STAT drops, USED survives)\n" as *u8) } else { g_p("no\n" as *u8) }
325
326 var pass: i64 = 0
327 if sov_ok == 1 { if align == 1 { if tamper_bites == 1 { if tamper_keeps_used == 1 { pass = 1 } } } }
328
329 if pass == 1 {
330 g_p("STATGATE verdict=GREEN (sovereign rv64 emu drove the status-byte layer; on the b2b kick the device wrote the used ring + bumped used.idx, then DMA-WROTE the virtio-blk status byte VIRTIO_BLK_S_OK into the status descriptor's guest buffer + latched it into QueueStatPeek; the driver read QueueStatPeek back + verified == stat_expected; transcript==golden; clean halt; qemu virtio-blk-device cross-check lays the SAME descriptor+avail + kicks the real transport AGREEING on the VQ prefix; StatPeek tamper REJECTED -- STAT drops, USED survives)\n" as *u8)
331 if lfd >= 0 {
332 g_fp(lfd, "STATGATE verdict=GREEN runtime=sovereign-emu transport=legacy-virtio-mmio layer=status-byte status=blk_s_ok writeback=device-dma-to-statbuf statpeek-readback=verified transcript==golden align_qemu=yes(virtio-blk-device,VQ-prefix) tamper=rejected(STAT-drops,USED-survives) golden=" as *u8)
333 g_fp(lfd, gold)
334 g_fp(lfd, " epoch=" as *u8); g_fn(lfd, sys_now_realtime_sec()); g_fp(lfd, "\n" as *u8); sys_close(lfd)
335 }
336 sys_exit(0); return 0
337 }
338 g_p("STATGATE verdict=RED (sov_ok/align/tamper not all green)\n" as *u8)
339 if lfd >= 0 {
340 g_fp(lfd, "STATGATE verdict=RED sov_ok=" as *u8); g_fn(lfd, sov_ok)
341 g_fp(lfd, " align=" as *u8); g_fn(lfd, align)
342 g_fp(lfd, " tamper_bites=" as *u8); g_fn(lfd, tamper_bites)
343 g_fp(lfd, " tamper_keeps_used=" as *u8); g_fn(lfd, tamper_keeps_used); g_fp(lfd, "\n" as *u8); sys_close(lfd)
344 }
345 sys_exit(1)
346 return 1
347}