code wiki / _hdl_build / nx_gpt_relocate_gate.nx

nx_gpt_relocate_gate.nx source

↩ module page · 179 lines · 11496 B

1// nx_gpt_relocate_gate.nx -- the REFEREE for smallos SO0 (nk0_gpt_relocate in nx_efi_fat32_image, 2026-09-02). 2// Authors a real image with the emitter, grows a copy to a larger "device", relocates the backup GPT to the 3// device's true end, and re-reads the DEVICE to prove: backup header at the last LBA, primary AlternateLBA and 4// LastUsableLBA moved, protective MBR widened, both partition entries byte-unchanged, the old mid-disk backup 5// retired, a second run idempotent, and every refusal (smaller device, not GPT, a foreign disk GUID) named and 6// write-free. The gate carries its own CRC32 ONLY to forge a valid-but-foreign header for the neg-control. 7// nx_gpt_relocate_gate [subject-elf] default _build/nx_efi_fat32_image.sov.elf (cwd nxc2) 8// Fixtures are assembled at runtime under /tmp/nx_gpt_relocate_gate/. license_tier: ORIGINAL. No hw writes: 9// every "device" here is a regular file. 10import "nx_syscalls.nx" 11import "nx_gate_verdict.nx" 12import "nx_gatekit_lib.nx" 13 14const RG_CAP: i64 = 65536 15const RG_SEC: i64 = 512 16const RG_IMG_BYTES: i64 = 69257728 // the emitter's authored size (its IMG_BYTES) 17const RG_OLD_LAST: i64 = 135268 // the emitter's LAST_LBA 18const RG_GROW_BYTES: i64 = 100663296 // 96 MiB "device": larger than the image, sector-aligned 19const RG_SMALL_BYTES: i64 = 1048576 // a "device" smaller than the image 20const RG_HDR_SZ: i64 = 92 21const RG_ARR_SEC: i64 = 32 22const RG_ENTSZ: i64 = 128 23const RG_FAKE_EFI_BYTES: i64 = 1024 24const RG_ROOT: *u8 = "/tmp/nx_gpt_relocate_gate" 25const RG_EFI: *u8 = "/tmp/nx_gpt_relocate_gate/fake.efi" 26const RG_BASE: *u8 = "/tmp/nx_gpt_relocate_gate/base.img" 27const RG_GROW: *u8 = "/tmp/nx_gpt_relocate_gate/grow.img" 28const RG_SMALL: *u8 = "/tmp/nx_gpt_relocate_gate/small.img" 29const RG_NOTGPT: *u8 = "/tmp/nx_gpt_relocate_gate/notgpt.img" 30const RG_FOREIGN: *u8 = "/tmp/nx_gpt_relocate_gate/foreign.img" 31 32func rg_r32(b: *u8, off: i64) -> i64 { return (b[off] as i64) | ((b[off+1] as i64) << 8) | ((b[off+2] as i64) << 16) | ((b[off+3] as i64) << 24) } 33func rg_r64(b: *u8, off: i64) -> i64 { return rg_r32(b, off) | (rg_r32(b, off + 4) << 32) } 34func rg_w32(b: *u8, off: i64, v: i64) -> i64 { b[off]=(v&0xff) as u8; b[off+1]=((v>>8)&0xff) as u8; b[off+2]=((v>>16)&0xff) as u8; b[off+3]=((v>>24)&0xff) as u8; return 0 } 35// IEEE CRC32, fixture-signing only (the subject carries its own; this copy exists so the gate can forge a 36// VALID header that is not ours -- an adversary's tool, never the ruler) 37func rg_crc32(b: *u8, off: i64, n: i64) -> i64 { 38 var crc: i64 = 0xFFFFFFFF 39 var i: i64 = 0 40 while i < n { 41 crc = crc ^ ((b[off + i] as i64) & 0xff) 42 var bit: i64 = 0 43 while bit < 8 { 44 let lsb: i64 = crc & 1 45 let mask: i64 = 0 - lsb 46 crc = ((crc >> 1) & 0x7FFFFFFFFFFFFFFF) ^ (mask & 0xEDB88320) 47 crc = crc & 0xFFFFFFFF 48 bit = bit + 1 49 } 50 i = i + 1 51 } 52 return (crc ^ 0xFFFFFFFF) & 0xFFFFFFFF 53} 54func rg_sig_at(b: *u8, off: i64) -> i64 { 55 let sig: *u8 = "EFI PART" as *u8 56 var i: i64 = 0 57 while i < 8 { if (b[off + i] as i64) != (sig[i] as i64) { return 0 } i = i + 1 } 58 return 1 59} 60// write n bytes of buf to path (truncating), then optionally extend the file to `total` bytes (sparse tail) 61func rg_write_file(path: *u8, buf: *u8, n: i64, total: i64) -> i64 { 62 let fd: i64 = sys_openat_wr(path, 0x1a4) 63 if fd < 0 { return 0 - 1 } 64 var put: i64 = 0 65 while put < n { let w: i64 = sys_write(fd, (buf + put) as *u8, n - put); if w <= 0 { sys_close(fd); return 0 - 1 } put = put + w } 66 if total > n { 67 let one: *u8 = sys_mmap(8) 68 one[0] = 0 as u8 69 if sys_lseek(fd, total - 1, 0) != total - 1 { sys_close(fd); return 0 - 1 } 70 sys_write(fd, one, 1) 71 } 72 sys_close(fd) 73 return 0 74} 75func rg_same(a: *u8, b: *u8, off: i64, n: i64) -> i64 { 76 var i: i64 = 0 77 while i < n { if a[off + i] != b[off + i] { return 0 } i = i + 1 } 78 return 1 79} 80func rg_same2(a: *u8, aoff: i64, b: *u8, boff: i64, n: i64) -> i64 { 81 var i: i64 = 0 82 while i < n { if a[aoff + i] != b[boff + i] { return 0 } i = i + 1 } 83 return 1 84} 85func rg_run(elf: *u8, a1: *u8, a2: *u8, out: *u8, ol: *i64) -> i64 { 86 return gk_run_capture(elf, a1, a2, 0 as *u8, 0 as *u8, out, RG_CAP, ol) 87} 88 89func main(argc: i64, argv: *i64) -> i64 { 90 var elf: *u8 = "_build/nx_efi_fat32_image.sov.elf" as *u8 91 if argc >= 2 { elf = argv[1] as *u8 } 92 gv_head("NX-GPT-RELOCATE-GATE: SO0 -- the authored boot image relocates its backup GPT to a real device's end" as *u8) 93 let ctr: *i64 = gv_ctr() 94 gv_need("subject elf present" as *u8, gk_exists(elf), ctr) 95 gk_mkdir(RG_ROOT) 96 let out: *u8 = sys_mmap(RG_CAP) 97 let ol: *i64 = sys_mmap(16) as *i64 98 99 // ---- author a real image with a fake PE payload (the emitter checks MZ only) ---- 100 let fe: *u8 = sys_mmap(RG_FAKE_EFI_BYTES) 101 fe[0] = 77 as u8; fe[1] = 90 as u8 102 var fi: i64 = 2 103 while fi < RG_FAKE_EFI_BYTES { fe[fi] = (fi & 0xff) as u8; fi = fi + 1 } 104 rg_write_file(RG_EFI, fe, RG_FAKE_EFI_BYTES, 0) 105 rg_run(elf, RG_EFI, RG_BASE, out, ol) 106 gv_need("fixture-reached-the-condition: the emitter authored base.img GREEN at its declared size" as *u8, gk_out_has(out, ol[0], "NOS-USB GREEN" as *u8) * (gk_size(RG_BASE) == RG_IMG_BYTES), ctr) 107 let lp: *i64 = sys_mmap(16) as *i64 108 let base: *u8 = sys_read_file(RG_BASE, lp) 109 gv_need("base image readable whole" as *u8, lp[0] == RG_IMG_BYTES, ctr) 110 gv_check("T1 the authored image's backup header sits at ITS OWN last LBA (the condition a real stick breaks)" as *u8, rg_sig_at(base, RG_OLD_LAST * RG_SEC) * (rg_r64(base, RG_SEC + 32) == RG_OLD_LAST), ctr) 111 112 // ---- grow a copy to a 96 MiB device and relocate ---- 113 rg_write_file(RG_GROW, base, RG_IMG_BYTES, RG_GROW_BYTES) 114 gv_need("fixture-reached-the-condition: grow.img is a 96 MiB device carrying the image at its head" as *u8, gk_size(RG_GROW) == RG_GROW_BYTES, ctr) 115 let new_last: i64 = RG_GROW_BYTES / RG_SEC - 1 116 let new_arr: i64 = new_last - RG_ARR_SEC 117 let rc1: i64 = rg_run(elf, "relocate" as *u8, RG_GROW, out, ol) 118 gv_puts(out) 119 gv_check("T2 relocate reports GREEN with the device geometry and verified=1, exit 0" as *u8, gk_out_has(out, ol[0], "NOS-USB RELOCATE GREEN" as *u8) * gk_out_has(out, ol[0], "verified=1" as *u8) * (rc1 == 0), ctr) 120 let gl: *i64 = sys_mmap(16) as *i64 121 let grow: *u8 = sys_read_file(RG_GROW, gl) 122 gv_need("grown device readable whole after relocate" as *u8, gl[0] == RG_GROW_BYTES, ctr) 123 gv_check("T3 the backup header now sits at the DEVICE's last LBA, names itself there, and points back at LBA 1 and at the moved array" as *u8, rg_sig_at(grow, new_last * RG_SEC) * (rg_r64(grow, new_last * RG_SEC + 24) == new_last) * (rg_r64(grow, new_last * RG_SEC + 32) == 1) * (rg_r64(grow, new_last * RG_SEC + 72) == new_arr), ctr) 124 gv_check("T4 the primary header's AlternateLBA and LastUsableLBA moved to the device's end" as *u8, (rg_r64(grow, RG_SEC + 32) == new_last) * (rg_r64(grow, RG_SEC + 48) == new_arr - 1), ctr) 125 gv_check("T5 the protective MBR now spans the whole device" as *u8, rg_r32(grow, 446 + 12) == new_last, ctr) 126 gv_check("T6 both partition entries are byte-unchanged (relocation never edits a partition)" as *u8, rg_same(base, grow, 2 * RG_SEC, 2 * RG_ENTSZ), ctr) 127 gv_check("T7 the moved array is byte-identical to the primary array" as *u8, rg_same2(grow, new_arr * RG_SEC, grow, 2 * RG_SEC, RG_ARR_SEC * RG_SEC), ctr) 128 gv_check("T8 the old mid-disk backup header is retired (signature gone), so no reader can find two" as *u8, 1 - rg_sig_at(grow, RG_OLD_LAST * RG_SEC), ctr) 129 gv_check("T9 the ESP volume is untouched: its BPB still reads FAT32 and the payload's MZ is still at its cluster" as *u8, rg_same(base, grow, 1048576, 512) * rg_same(base, grow, 1590272 + 3 * RG_SEC, RG_FAKE_EFI_BYTES), ctr) 130 131 // ---- idempotence: a second run writes nothing ---- 132 let rc2: i64 = rg_run(elf, "relocate" as *u8, RG_GROW, out, ol) 133 let g2l: *i64 = sys_mmap(16) as *i64 134 let grow2: *u8 = sys_read_file(RG_GROW, g2l) 135 gv_check("T10 a second relocate reports ALREADY-RELOCATED (its own CRC check on the moved backup passes) with exit 0" as *u8, gk_out_has(out, ol[0], "ALREADY-RELOCATED" as *u8) * (rc2 == 0), ctr) 136 gv_check("T11 fixture-reached-the-condition: the second run left head and tail byte-identical" as *u8, (g2l[0] == RG_GROW_BYTES) * rg_same(grow, grow2, 0, 34 * RG_SEC) * rg_same(grow, grow2, new_arr * RG_SEC, (RG_ARR_SEC + 1) * RG_SEC), ctr) 137 138 // ---- refusals, each named, each write-free ---- 139 rg_write_file(RG_SMALL, base, RG_SMALL_BYTES, 0) 140 let rc3: i64 = rg_run(elf, "relocate" as *u8, RG_SMALL, out, ol) 141 let sl: *i64 = sys_mmap(16) as *i64 142 let small: *u8 = sys_read_file(RG_SMALL, sl) 143 gv_check("neg-control-a-device-smaller-than-the-image-is-REFUSED-by-rule-with-exit-2-and-nothing-written" as *u8, gk_out_has(out, ol[0], "REFUSED rule=device-smaller-than-image" as *u8) * (rc3 == 2) * (sl[0] == RG_SMALL_BYTES) * rg_same(base, small, 0, RG_SMALL_BYTES), ctr) 144 145 let ng: *u8 = sys_mmap(RG_IMG_BYTES) 146 var ni: i64 = 0 147 while ni < RG_IMG_BYTES { ng[ni] = base[ni]; ni = ni + 1 } 148 ng[510] = 0 as u8 149 ng[511] = 0 as u8 150 rg_write_file(RG_NOTGPT, ng, RG_IMG_BYTES, RG_GROW_BYTES) 151 let rc4: i64 = rg_run(elf, "relocate" as *u8, RG_NOTGPT, out, ol) 152 let nl: *i64 = sys_mmap(16) as *i64 153 let notgpt: *u8 = sys_read_file(RG_NOTGPT, nl) 154 gv_check("neg-control-a-non-GPT-device-is-REFUSED-by-rule-and-its-tail-stays-empty" as *u8, gk_out_has(out, ol[0], "REFUSED rule=not-gpt-no-mbr-signature" as *u8) * (rc4 == 2) * (1 - rg_sig_at(notgpt, new_last * RG_SEC)) * rg_same(ng, notgpt, 0, 34 * RG_SEC), ctr) 155 156 // a VALID header that is not ours: flip one disk-GUID byte and re-sign the CRC exactly as a firmware would 157 let fg: *u8 = sys_mmap(RG_IMG_BYTES) 158 var fj: i64 = 0 159 while fj < RG_IMG_BYTES { fg[fj] = base[fj]; fj = fj + 1 } 160 fg[RG_SEC + 56] = ((fg[RG_SEC + 56] as i64) ^ 0xff) as u8 161 rg_w32(fg, RG_SEC + 16, 0) 162 let fcrc: i64 = rg_crc32(fg, RG_SEC, RG_HDR_SZ) 163 rg_w32(fg, RG_SEC + 16, fcrc) 164 rg_write_file(RG_FOREIGN, fg, RG_IMG_BYTES, RG_GROW_BYTES) 165 let rc5: i64 = rg_run(elf, "relocate" as *u8, RG_FOREIGN, out, ol) 166 let fl: *i64 = sys_mmap(16) as *i64 167 let foreign: *u8 = sys_read_file(RG_FOREIGN, fl) 168 gv_check("neg-control-a-valid-GPT-with-a-FOREIGN-disk-GUID-is-REFUSED-by-rule (never-brick: only our own image is ever rewritten)" as *u8, gk_out_has(out, ol[0], "REFUSED rule=foreign-disk-guid" as *u8) * (rc5 == 2) * (1 - rg_sig_at(foreign, new_last * RG_SEC)) * rg_same(fg, foreign, 0, 34 * RG_SEC), ctr) 169 // recompute exactly as a parser does: CRC field zeroed during the sum, then compared to the stored value 170 let fsave: i64 = rg_r32(fg, RG_SEC + 16) 171 rg_w32(fg, RG_SEC + 16, 0) 172 let fchk: i64 = rg_crc32(fg, RG_SEC, RG_HDR_SZ) 173 rg_w32(fg, RG_SEC + 16, fsave) 174 gv_check("T12 fixture-reached-the-condition: the forged header really carried a valid CRC (the refusal was the GUID rule, not the CRC rule)" as *u8, fchk == fsave, ctr) 175 176 let rc: i64 = gv_verdict("NX-GPT-RELOCATE-GATE" as *u8, ctr, "SO0 nk0_gpt_relocate: the backup GPT lands at the real device's end, the primary follows, partitions and the ESP are untouched, the run is idempotent, and every refusal is named and write-free" as *u8) 177 sys_exit(rc) 178 return rc 179}