# DF-2621 — hammer2_xop_helper_create() unconditional re-kmalloc leaks thread
# arrays + orphan kernel threads (verified)

Claim under test (from findings/DF-2621-hammer2-xop-helper-create-double-alloc-uaf.md):
`hammer2_xop_helper_create()` (sys/vfs/hammer2/hammer2_admin.c:425-448) assigns
`pmp->xop_groups = kmalloc(...)` unconditionally. When called twice on the same
pmp — which happens whenever a second same-`pfs_clid` device merges into a pmp
that already has xop threads (hammer2_pfsalloc, vfsops.c:588-589, then
hammer2_mount_helper, vfsops.c:1708) — the old array pointer is lost: the
~18 KB array, its 36 kernel threads and their 36 x 128 KB scratch buffers are
leaked, and the threads are orphaned (all teardown paths walk only the CURRENT
`pmp->xop_groups`). The finding further claims the orphans use-after-free the
pmp after `hammer2_pfsfree()` kfrees it (30 s poll loop touches
`pmp->xop_spin`, admin.c:1244/1079).

## Verified results (full story: VERDICT.md)

1. **Double-create on the same pmp — PROVEN** (stock console: both mounts bind
   the same pmp; instrumented kernel: `old_groups` non-NULL, overwritten).
2. **Deterministic leak + orphan threads — PROVEN on stock**: thread census
   0 -> 36 -> 108 per single clone-merge; with 4 devices 0 -> 36 -> 108 ->
   216 -> 360 (creates of 36/72/108/144 threads; 216 orphaned), HAMMER2-mount
   malloc zone 13.0M -> 130M (+117 MB) in eight mount commands, no unmount
   involved, guest otherwise idle. Orphan threads survive every teardown path.
3. **UAF-write component — code-proven, runtime-blocked**: orphan threads
   unconditionally execute `hammer2_spin_ex(&pmp->xop_spin)` every 30 s poll
   (admin.c:1218 falls through into 1074-1079); nothing ever signals them
   STOP. `hammer2_pfsfree()` would kfree the pmp while they poll. On this
   guest the pmp never reaches kfree: every 2-chain teardown wedges earlier
   in `hammer2_pfsfree_scan()`'s freeze phase (a separate, pre-existing
   multi-chain teardown defect, already documented with DF-2620), so the
   freed-pmp write could not be observed live. See VERDICT.md.
4. **Fix validated by rebuild**: guarding the kmalloc with
   `if (pmp->xop_groups == NULL)` removes the double-create (T2 108 -> 72),
   leaves zero orphan threads, and stops the leak growth. fix.diff.

## Reproduce

Images need NO forged bytes — a dd clone shares the pfs_clid by construction.

```sh
# in-guest as root (stock INVARIANTS kernel #0):
sh trigger2.sh     # mount vn0@testvol; mount vn1@testvol (dd clone) -> EBUSY;
                   # census shows 36+72 threads; umount -f wedges (separate bug)
sh trigger3.sh     # amplification: 4 devices, census to 360 threads,
                   # vmstat -m HAMMER2-mount 13M -> 130M
```

Success criteria:
* `MOUNT2_RC=1` with console `hammer2_mount: ... pmp=<same addr>` +
  `PFS already mounted!`
* thread census `T2=108` (36 orphans + 72) vs fixed kernel `T2=72`
* `vmstat -m | grep HAMMER2-mount` grows monotonically per clone-merge

## Instrumented proof build

`instrument.diff` adds kprintf breadcrumbs (helper_create old/new pointers,
cleanup progress, pfsfree_scan phases, pfsfree kfree) and a freed-pmp ring
checked in `hammer2_xop_next()` that would report an orphan spinning on a
freed pmp. Applied to the guest /usr/src only, never to the audit tree.
Decisive output in `instrumented_console.log`:

```
DF2621: helper_create pmp=0xfffff80118c80000 old_groups=0 nchains=1
DF2621: helper_create pmp=0xfffff80118c80000 new_groups=0xfffff80118b4e000
DF2621: helper_create pmp=0xfffff80118c80000 old_groups=0xfffff80118b4e000 nchains=2
DF2621: helper_create pmp=0xfffff80118c80000 new_groups=0xfffff80119a70000 (OLD LEAKED)
```
