# DF-3012 — hammer(1) in-memory record leaked when blockmap reservation
fails in `hammer_ip_add_bulk()` (+ same class in `hammer_ip_add_direntry`)

## What (defect, code-certain)

`hammer_ip_add_bulk()` (sys/vfs/hammer/hammer_object.c:969-978) allocates a
memory record (refcount 1) and then tries the direct-write blockmap
reservation.  On failure it releases the record **without setting
`HAMMER_RECF_DELETED_FE`**:

```c
record = hammer_alloc_mem_record(ip, 0);          /* ref = 1        */
record->resv = hammer_blockmap_reserve(...);
if (record->resv == NULL) {
        hdkprintf("reservation failed\n");
        hammer_rel_mem_record(record);            /* ref -> 0, LEAK */
        return (NULL);
}
```

`hammer_rel_mem_record()` (hammer_object.c:374-393) only destroys a record
when `HAMMER_RECF_DELETED_FE|BE` or `COMMITTED` is set.  A never-inserted,
un-flagged record dropped to zero refs is freed by *nobody* — it is on no
RB-tree, no target list, referenced by nothing.  Both the record
(~224 B, "HAMMER-others" malloc zone) and — in the sibling case — its
kmalloc'd data leak permanently (until reboot; not reclaimed by sync, rm,
or unmount logic that walks inodes).

The sibling site is `hammer_ip_add_direntry()` at hammer_object.c:711-716
(namekey-iteration exhaustion → ENOSPC): leaks record +
`HAMMER_ENTRY_SIZE(bytes)` of direntry data.

Every comparable error path in the tree sets the flag before releasing —
e.g. `hammer_io_direct_write()`'s failure path (hammer_io.c:1793-1795)
does `record->flags |= HAMMER_RECF_DELETED_FE; hammer_rel_mem_record(...)` —
these two sites were missed.

## Trigger conditions (what Phase V established)

A record leaks **once per `hammer_blockmap_reserve()` failure** hit from
`hammer_vop_strategy_write()` (hammer_vnops.c:3251 — every 16K-aligned
buffer flush).  The frontend `hammer_checkspace()` gate
(hammer_vnops.c:605, estimate in hammer_blockmap.c:1267-1290) exists
precisely to keep buffered writes from reaching an exhausted blockmap; its
headroom is `vfs.hammer.limit_dirtybufspace` (default 49 MB = half the
global dirty-buffer cap) + undo/slop reserves, and pending records are
accounted into the estimate at strategy time.  We could **not** make the
reservation fail from an unprivileged user on default tuning despite:
plain fills, 40,000-file tiny-file sprays, mmap/msync putpages (which go
through VOP_WRITE and are gated too), and 6-parallel-writer unique-data
races on both tmpfs-fast and virtio-slow backing storage, including with
the gate headroom removed via `vfs.hammer.limit_dirtybufspace=2M`.

Realistic residual triggers (unproven in lab): freemap `hammer_bread`
I/O errors inside `hammer_blockmap_reserve()` (failing/mismatched
hardware), concurrent admin reblock/prune/mirror operations racing the
frontend, multi-volume configurations, or deep drain-lag topologies
(slow data path, fast writers) where the in-flight dirty backlog exceeds
the reserve.

## Reproduce (what the pack runs)

    # in guest as root:  build + self-contained run (fresh 512M fs)
    sh build.sh         # cc -O -o /tmp/op3012 /tmp/op3012.c -Wall
    sh run.sh           # fills with urandom via 6 parallel 'nobody'
                        # writers on a nohistory fs, watches dmesg for
                        # "reservation failed" and the HAMMER-others zone

Expected on a vulnerable kernel when a reservation failure occurs:
dmesg gains one `hammer_ip_add_bulk: reservation failed` line per leaked
record and the `HAMMER-others` zone in `vmstat -m` grows monotonically,
never shrinking after `rm -rf` + `sync`.

Observed on the lab guest (INVARIANTS kernel, default + reduced tuning):
**zero reservation failures across all attempts** — the leak's trigger
precondition could not be organically produced; see VERDICT.md for the
full negative-result analysis and EXPERIMENTS.md for every attempt.
