# DF-2632 — hammer2 flusher panics on dense dirent packing

`insert base %p overlapping elements` in `hammer2_base_insert()`
(sys/vfs/hammer2/hammer2_chain.c:5308-5311), reached from the flusher's
indirect-block maintenance: `hammer2_flush_core` →
`hammer2_chain_indirect_maintenance` → `hammer2_chain_rename_obref` →
`hammer2_base_insert`.  Unprivileged local DoS: any user with write access
to one hammer2 directory can panic the box with ~100 specially-crafted
filenames.

## Trigger

`hammer2_dirhash()` (hammer2_subr.c:178-229) maps a delimiter-free name
to `key = 0x8.. | (crc32c << 32) | ((crc32c^(crc32c<<16)) & 0xFFFF0000) | 0x8000`.
All names sharing one CRC32C hash to the SAME key; `hammer2_dirent_create()`
packs them into consecutive slots X8000, X8001, … of one 64K collision
window (HAMMER2_DIRHASH_LOMASK = 0x7FFF).

`gen_names.c` (from findings/poc/DF-2628) emits arbitrary numbers of
8-char names sharing a chosen CRC32C (meet-in-the-middle, ~5.5 s for
40000 names on the guest).

`fill2632.c` creates the files (one empty file per name) in one directory
with periodic `sync()`, printing progress so the panic-time count is
recoverable from captured stdout.

## Reproduce

    # on the stock guest (root):
    cc -O2 -o gen_names gen_names.c
    cc -O2 -o fill2632 fill2632.c
    truncate -s 512M /tmp/h2632.img
    vnconfig -c vn1 /tmp/h2632.img
    newfs_hammer2 -L DATA /dev/vn1
    mkdir -p /mnt/h2632 && mount_hammer2 /dev/vn1@DATA /mnt/h2632
    ./gen_names deadbeef 40000 /tmp/names.txt
    mkdir /mnt/h2632/dense
    ./fill2632 /mnt/h2632/dense /tmp/names.txt 128
    # -> panic within the first batch (see serial_panic_stock_standalone.log)
    # with fine-grained batches (denseprobe, batch=16, sync+200ms):
    #    panics between 96 and 112 dense dirents (density_probe.log)

Expected (stock): `panic: td_critcount is/would-go negative!` with trace
crit_panic ← spin_unlock ← hammer2_base_insert ← hammer2_chain_rename_obref
← hammer2_chain_indirect_maintenance  (INVARIANTS kernel; the panic path's
hammer2_spin_unex at chain.c:5309 runs while the caller holds no spin,
driving td_critcount negative).  A no-INVARIANTS build panics with the
structural message instead (`serial_panic_noinv.log`, from DF-2628).

## Fix validation (kernel #2, X86_64_GENERIC + fix.diff)

`validate_fix.sh`: 19818 dense same-CRC names created with ZERO errors,
no panic (stock panics at 96-112), clean unmount, all 19818 files present
after remount.  Natural-name directories (22000 names, max 1 per dirhash
window) unaffected.  Stock-image write/sync/umount/remount md5 roundtrip
unaffected.  Fill throughput degrades as the window gets deep (deferred
collapses retry, rate-limited 1/s console notice) — no panic, no data loss.

## Files

    fill2632.c / denseprobe.c / naturalprobe.c   triggers/probes
    run_df2632.sh / run_probe.sh / run_natural.sh / validate_fix.sh
    serial_panic_stock_standalone.log   fresh standalone stock panic (this run)
    serial_panic_density96.log          panic at 96<N<=112 (batch=16)
    density_probe.log / natural_probe.log / stock_run.log
    fix_validation.log / fix_validation_part2.log
    serial_panic_invariants.log / serial_panic_noinv.log   (DF-2628 transcripts)
    fix.diff (shared DF-2632+DF-2633), VERDICT.md, verdict.json, manifest.json
