# DF-5784 — hammer2 chain comparator key-range overflow (pass 3)

## Finding (short)

`hammer2_chain_cmp()` (sys/vfs/hammer2/hammer2_chain.c:97-118) computes a
chain's key range as `end = key + ((hammer2_key_t)1 << keybits) - 1` with
**no validation that the range wraps the 64-bit keyspace**. A crafted
filesystem image can carry a blockref whose `key + (1 << keybits) > 2^64`
(e.g. `key=0xC000000000000001, keybits=63` — both fully defined shifts, no
UB). The in-memory chain for that bref then compares as "fully to the left"
(`end < other.key`) of *every* other chain — including chains to its own
left — so:

1. the chain is mis-placed in the parent's in-memory rbtree, and
   `hammer2_chain_find()`'s `RB_SCAN` (tree.h: `comp < 0` descends RIGHT
   only) prunes it, so every lookup re-creates a **duplicate chain** from
   the same media bref (`hammer2_chain_lookup` → `hammer2_chain_get` →
   `hammer2_chain_alloc`, hammer2_chain.c:2610-2614), and
2. iteration keys regress: `key_next = key + (1 << keybits)` wraps *below*
   `key_beg` (hammer2_chain.c:2748-2749, 2594-2595), so directory iteration
   can never advance past the element — the readdir xop backend/frontend
   (`hammer2_xop_readdir`, `hammer2_xop_collect`'s `collect_key` advance,
   hammer2_admin.c:1000-1019) live-lock.

Observed on the guest (stock INVARIANTS kernel): mounting the crafted image
and issuing **one** directory read leaves the reading process stuck in
`getdents` **forever**, **unkillable** (`kill -9` → `Operation not
permitted`), `umount` hangs, a CPU pegged with kernel interrupt-livelock
messages (`intr 16 on cpu1 at 40001/40000 hz, livelock limit engaged!`),
and the live chain counter `vfs.hammer2.chain_allocs` creeps upward
(monotone leak; every lookup allocates a new duplicate chain that is
dropped to 0 refs but remains in the rbtree until unmount — which can no
longer happen). Threat model identical to DF-2616/DF-2617/DF-2618:
crafted hammer2 image, mounted by root (or unprivileged user with
`vfs.usermount=1` and an owned vnode device).

Distinct from the known findings: DF-2618 is about *overlapping* ranges
(cmp()==0 collisions/panics); DF-2606 is about *UB shifts* (keybits ≥ 64).
This is a fully-defined-shift **integer wrap** of the range *end* that
breaks the strict-weak ordering silently — no assert fires, no panic, the
system just wedges and leaks.

## Files

* `forge_df5784.py` — host-side forger (builds craftA/craftB from base.img)
* `craftC` variant: same patched key with `keybits=0` (no wrap) — causality
  control, built with an inline python snippet (see build.sh)
* `leakloop.c` — drains a directory via readdir(3), prints live
  `vfs.hammer2.chain_allocs`
* `run_df5784.sh` — guest runner (control + crafted)
* `run_wedge_transcript.txt` — captured wedged-state evidence
* `run_control_craftC.log` — causality control output (200000 entries,
  CRAFTC_DELTA=0, umount OK)
* `fix.diff` — validated fix (skip insane media brefs in
  `hammer2_base_find()`'s forward scan + `hammer2_bref_sane()` helper)

## Build

1. guest (root): `sh mkbase.sh`-equivalent (see build.sh) → `base.img`
2. host: `python3 forge_df5784.py base.img craftA.img craftB.img`
3. host: inline python for `craftC.img` (keybits=0 variant) — see build.sh
4. push to guest `/root/poc/df5784/`

## Run (guest, root)

```
sh /root/poc/df5784/run_df5784.sh
```

### Expected

* control (base.img): leakloop completes 1,000,000 entries quickly,
  chain_allocs delta ≈ 0, umount OK.
* crafted (craftB.img): leakloop **never completes**; the ssh session must
  be abandoned; from a second shell: `ps` shows the loop `R2`, `kill -9`
  → `Operation not permitted`, `umount` hangs, `chain_allocs` creeps up.
* causality control (craftC.img, same key, keybits=0): 200,000 entries
  complete, CRAFTC_DELTA=0, umount rc=0 — proves the *wrap* (not merely
  the odd key) is the trigger.

## Fix validation

`fix.diff` makes `hammer2_base_find()` skip media elements that fail
`hammer2_bref_sane()` (unknown type or wrapped key range), exactly as it
skips EMPTY slots — lookup/scan/readdir then never select the poisoned
element. Validated by rebuilding the kernel in the guest and re-running:
crafted image mounts, `ls` returns (entry invisible), leakloop completes
with delta 0, umount OK, no wedge. See VERDICT.md.
