code wiki / _hdl_build / _trap_syscall_gate.nx
_trap_syscall_gate.nx source
↩ module page · 216 lines · 10145 B
1// _trap_syscall_gate.nx -- the K-R1 gate (kernel-up ladder rung 1). Drives the full
2// author->trap->syscall chain with NO mocks: runs the REAL nx_trap_syscall_emit (the
3// team AUTHORS the rv64 trap+syscall image + the TABLE-COMPUTED golden transcript from
4// the spec), then RUNS the image on the SOVEREIGN rv64 emulator (rv64im_min_sim -- the
5// PRIMARY, gating lane: Nishi owns the runtime) and asserts the captured serial
6// transcript CONTAINS the emitter's golden (banner + each syscall's emit byte, in order)
7// AND the sovereign emu reports a clean SiFive-finisher halt (BOOTSOV verdict=GREEN).
8// Then it runs the SAME image on qemu-system-riscv64 -machine virt (the declared
9// hardware-alignment lane, NOT the runtime -- the diff-lane signal) and asserts the
10// lanes AGREE on the transcript. Finally a TAMPER test: corrupt one syscall's a7 in the
11// image -> that handler never fires -> the transcript loses a byte -> the gate must go
12// RED (proves the gate bites, not a rubber stamp). Evidence -> knowledge/status/
13// trap_syscall.log (TRAPGATE row; the queue row's ||MARK= reads it). Sovereign
14// orchestration (fork/dup3/execve/wait4). license_tier: ORIGINAL
15import "nx_syscalls.nx"
16
17func 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 }
18func 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 }
19func 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 }
20
21// run nx_trap_syscall_emit <spec>; return child wait status (0 = ok)
22func g_run_emit(spec: *u8) -> i64 {
23 let pid: i64 = sys_fork()
24 if pid == 0 {
25 let dn: i64 = sys_openat_wr("/dev/null" as *u8, 0x1a4)
26 if dn >= 0 { sys_dup3(dn, 1, 0) }
27 let argv: *i64 = sys_mmap(32) as *i64
28 argv[0] = "_offc/nx_trap_syscall_emit.elf" as *u8 as i64
29 argv[1] = spec as i64
30 argv[2] = 0
31 let envp: *i64 = sys_mmap(16) as *i64
32 envp[0] = "PATH=/usr/bin:/bin" as *u8 as i64
33 envp[1] = 0
34 sys_execve("_offc/nx_trap_syscall_emit.elf" as *u8, argv, envp)
35 sys_exit(127)
36 }
37 let st: *i64 = sys_mmap(16) as *i64
38 sys_wait4(pid, st, 0)
39 return st[0]
40}
41
42// run qemu-system-riscv64 on binpath; serial -> outpath; return child wait status
43func g_run_qemu(binpath: *u8, outpath: *u8) -> i64 {
44 let pid: i64 = sys_fork()
45 if pid == 0 {
46 let ofd: i64 = sys_openat_wr(outpath, 0x1a4)
47 if ofd >= 0 { sys_dup3(ofd, 1, 0); sys_dup3(ofd, 2, 0) }
48 let argv: *i64 = sys_mmap(64) as *i64
49 argv[0] = "/usr/bin/qemu-system-riscv64" as *u8 as i64
50 argv[1] = "-machine" as *u8 as i64
51 argv[2] = "virt" as *u8 as i64
52 argv[3] = "-nographic" as *u8 as i64
53 argv[4] = "-bios" as *u8 as i64
54 argv[5] = binpath as i64
55 argv[6] = 0
56 let envp: *i64 = sys_mmap(16) as *i64
57 envp[0] = "PATH=/usr/bin:/bin" as *u8 as i64
58 envp[1] = 0
59 sys_execve("/usr/bin/qemu-system-riscv64" as *u8, argv, envp)
60 sys_exit(127)
61 }
62 let st: *i64 = sys_mmap(16) as *i64
63 sys_wait4(pid, st, 0)
64 return st[0]
65}
66
67// run the SOVEREIGN rv64 emulator on binpath; serial -> outpath; return wait status
68func g_run_sov(binpath: *u8, outpath: *u8) -> i64 {
69 let pid: i64 = sys_fork()
70 if pid == 0 {
71 let ofd: i64 = sys_openat_wr(outpath, 0x1a4)
72 if ofd >= 0 { sys_dup3(ofd, 1, 0); sys_dup3(ofd, 2, 0) }
73 let argv: *i64 = sys_mmap(32) as *i64
74 argv[0] = "_offc/nx_boot_run_sov.elf" as *u8 as i64
75 argv[1] = binpath as i64
76 argv[2] = 0
77 let envp: *i64 = sys_mmap(16) as *i64
78 envp[0] = "PATH=/usr/bin:/bin" as *u8 as i64
79 envp[1] = 0
80 sys_execve("_offc/nx_boot_run_sov.elf" as *u8, argv, envp)
81 sys_exit(127)
82 }
83 let st: *i64 = sys_mmap(16) as *i64
84 sys_wait4(pid, st, 0)
85 return st[0]
86}
87
88// read whole file into buf (cap-1 max); return byte count (0 if absent)
89func g_read(path: *u8, buf: *u8, cap: i64) -> i64 {
90 let fd: i64 = sys_openat_rd(path)
91 if fd < 0 { return 0 }
92 var n: i64 = 0
93 var go: i64 = 1
94 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 } }
95 sys_close(fd)
96 return n
97}
98
99// does the file at path contain pat (length pl)? 1/0
100func g_buf_has(buf: *u8, n: i64, pat: *u8, pl: i64) -> i64 {
101 if pl <= 0 { return 0 }
102 var i: i64 = 0
103 while i + pl <= n {
104 var k: i64 = 0
105 var hit: i64 = 1
106 while k < pl { if buf[i+k] != pat[k] { hit = 0; k = pl } else { k = k + 1 } }
107 if hit == 1 { return 1 }
108 i = i + 1
109 }
110 return 0
111}
112
113func g_strlen(s: *u8) -> i64 { var n: i64 = 0; while s[n] != (0 as u8) { n = n + 1 } return n }
114
115// file path -> contains pat? reads from disk. 1/0
116func g_file_has(path: *u8, pat: *u8) -> i64 {
117 let buf: *u8 = sys_mmap(65536)
118 let n: i64 = g_read(path, buf, 65536)
119 return g_buf_has(buf, n, pat, g_strlen(pat))
120}
121
122func main() -> i64 {
123 let spec: *u8 = "knowledge/specs/trap_syscall_virt.spec" as *u8
124 let binpath: *u8 = "runtime/_hdl_build/_trap_syscall_virt.bin" as *u8
125 let goldpath: *u8 = "runtime/_hdl_build/_trap_syscall_virt.bin.gold" as *u8
126 let tamper_bin: *u8 = "/tmp/_trapgate_tamper.bin" as *u8
127 let sov_serial: *u8 = "/tmp/_trapgate_sov.txt" as *u8
128 let qemu_serial: *u8 = "/tmp/_trapgate_qemu.txt" as *u8
129 let sov_tamper: *u8 = "/tmp/_trapgate_sov_tamper.txt" as *u8
130 g_p("=== trap+syscall gate (K-R1: SOVEREIGN rv64 runtime + qemu alignment diff-lane) ===\n" as *u8)
131
132 let est: i64 = g_run_emit(spec)
133 let lfd: i64 = sys_openat_append("knowledge/status/trap_syscall.log" as *u8, 0x1a4)
134 if est != 0 {
135 g_p("TRAPGATE verdict=RED reason=emit-failed\n" as *u8)
136 if lfd >= 0 { g_fp(lfd, "TRAPGATE verdict=RED reason=emit-failed\n" as *u8); sys_close(lfd) }
137 sys_exit(1); return 1
138 }
139
140 // load the TABLE-COMPUTED golden transcript the emitter authored
141 let gold: *u8 = sys_mmap(512)
142 let gn: i64 = g_read(goldpath, gold, 512)
143 gold[gn] = 0 as u8
144 if gn <= 0 {
145 g_p("TRAPGATE verdict=RED reason=golden-missing\n" as *u8)
146 if lfd >= 0 { g_fp(lfd, "TRAPGATE verdict=RED reason=golden-missing\n" as *u8); sys_close(lfd) }
147 sys_exit(1); return 1
148 }
149
150 // PRIMARY: the Nishi sovereign rv64 emulator RUNS the image (Nishi owns the runtime)
151 let sst: i64 = g_run_sov(binpath, sov_serial)
152 let sbuf: *u8 = sys_mmap(65536)
153 let sbn: i64 = g_read(sov_serial, sbuf, 65536)
154 let trans_ok: i64 = g_buf_has(sbuf, sbn, gold, gn) // serial CONTAINS golden
155 let halt_ok: i64 = g_buf_has(sbuf, sbn, "BOOTSOV verdict=GREEN" as *u8, 21)
156 var sov_ok: i64 = 0
157 if sst == 0 { if trans_ok == 1 { if halt_ok == 1 { sov_ok = 1 } } }
158
159 // ALIGNMENT: qemu cross-check only (NOT the runtime -- the diff-lane signal).
160 // qemu's raw serial == the golden transcript exactly (no runner prefix).
161 let qst: i64 = g_run_qemu(binpath, qemu_serial)
162 let qbuf: *u8 = sys_mmap(65536)
163 let qbn: i64 = g_read(qemu_serial, qbuf, 65536)
164 let qtrans: i64 = g_buf_has(qbuf, qbn, gold, gn)
165 var align: i64 = 0
166 if qst == 0 { if qtrans == 1 { align = 1 } }
167
168 // TAMPER: corrupt one syscall's a7 -> its handler never fires -> transcript loses a
169 // byte -> the gate MUST go RED on the sovereign lane. We flip the a7 immediate of
170 // the first driver `addi a7,x0,64` (write). Image layout (table-known): boot is
171 // auipc+addi+csrrw+lui t0 (4 words=16B) then banner "K1\n" = 3*2 words (24B); the
172 // first driver addi is at byte 40. Corrupt its imm field (top byte of the word).
173 let ibuf: *u8 = sys_mmap(8192)
174 let ibn: i64 = g_read(binpath, ibuf, 8192)
175 var ti: i64 = 0
176 while ti < ibn { ibuf[ti] = ibuf[ti]; ti = ti + 1 }
177 // word at byte 40 = addi a7,x0,64 ; its imm[11:0] sits in bits [31:20] = bytes 42-43.
178 // Bump byte 43 to derange the syscall number to one with no handler row.
179 ibuf[40 + 3] = (ibuf[40 + 3] + 1) as u8
180 let tfd: i64 = sys_openat_wr(tamper_bin, 0x1a4)
181 if tfd >= 0 { sys_write(tfd, ibuf, ibn); sys_close(tfd) }
182 let tst: i64 = g_run_sov(tamper_bin, sov_tamper)
183 let tbuf: *u8 = sys_mmap(65536)
184 let tbn: i64 = g_read(sov_tamper, tbuf, 65536)
185 let tamper_trans: i64 = g_buf_has(tbuf, tbn, gold, gn) // should now MISS
186 var tamper_bites: i64 = 0
187 if tamper_trans == 0 { tamper_bites = 1 } // golden no longer present = gate bites
188
189 g_p(" sovereign_emu=" as *u8)
190 if sov_ok == 1 { g_p("GREEN(transcript==golden+clean-halt)" as *u8) } else { g_p("RED" as *u8) }
191 g_p(" qemu_align=" as *u8)
192 if align == 1 { g_p("yes" as *u8) } else { g_p("no" as *u8) }
193 g_p(" tamper_bites=" as *u8)
194 if tamper_bites == 1 { g_p("yes\n" as *u8) } else { g_p("no\n" as *u8) }
195
196 var pass: i64 = 0
197 if sov_ok == 1 { if align == 1 { if tamper_bites == 1 { pass = 1 } } }
198
199 if pass == 1 {
200 g_p("TRAPGATE verdict=GREEN (sovereign rv64 emu ran trap+8-syscall surface; transcript==golden; qemu cross-check AGREES; tamper REJECTED)\n" as *u8)
201 if lfd >= 0 {
202 g_fp(lfd, "TRAPGATE verdict=GREEN runtime=sovereign-emu transcript==golden align_qemu=yes tamper=rejected nsys=8 golden=" as *u8)
203 g_fp(lfd, gold)
204 g_fp(lfd, " epoch=" as *u8); g_fn(lfd, sys_now_realtime_sec()); g_fp(lfd, "\n" as *u8); sys_close(lfd)
205 }
206 sys_exit(0); return 0
207 }
208 g_p("TRAPGATE verdict=RED (sov_ok/align/tamper not all green)\n" as *u8)
209 if lfd >= 0 {
210 g_fp(lfd, "TRAPGATE verdict=RED sov_ok=" as *u8); g_fn(lfd, sov_ok)
211 g_fp(lfd, " align=" as *u8); g_fn(lfd, align)
212 g_fp(lfd, " tamper_bites=" as *u8); g_fn(lfd, tamper_bites); g_fp(lfd, "\n" as *u8); sys_close(lfd)
213 }
214 sys_exit(1)
215 return 1
216}