# DF-5785 — VERDICT

**Status: reproduced** (impact: panic — deterministic kernel panic from a
mounted crafted hammer2 image; raw `panic()`, so it fires on production
kernels too, INVARIANTS not required). Fix validated.

## Root cause

`hammer2_chain_alloc()` (sys/vfs/hammer2/hammer2_chain.c:177-237) switches
on `bref->type` and `panic()`s on any unrecognized value
(hammer2_chain.c:207-210):

```c
case HAMMER2_BREF_TYPE_EMPTY:
default:
        panic("hammer2_chain_alloc: unrecognized blockref type: %d",
              bref->type);
```

The type byte of every blockref read from media is attacker-controlled and
unvalidated on the read path: `hammer2_base_find()` selects any non-EMPTY
slot (hammer2_chain.c:4975-4986), `hammer2_combined_find` returns its
bref, and `hammer2_chain_lookup` (hammer2_chain.c:2606-2615) /
`hammer2_chain_create_indirect` (:3942) pass it straight into
`hammer2_chain_get` → `hammer2_chain_alloc`. Valid media types are 1..6;
bytes 7..253 (e.g. 0xAA=170) panic instantly. (Types 254/255 are
pseudo-types that never appear in arrays but are also accepted by the
alloc switch — they take a different bad path in
`hammer2_chain_load_data` :1086 — same missing-validation class.)

## Reproduction (stock INVARIANTS kernel #0 Jul 2 2026)

`craftA.img` = base image with f1's DIRENT bref type byte set to 0xAA
(ancestors methods=CHECK_NONE so the edited blocks load; volhdr CRCs
recomputed). Mount succeeds (label lookup does not touch the dirent); the
**first directory read** panics:

```
panic: hammer2_chain_alloc: unrecognized blockref type: 170
hammer2_chain_alloc() at hammer2_chain_alloc+0x1ca
hammer2_chain_get() at hammer2_chain_get+0xbd
hammer2_chain_lookup() at hammer2_chain_lookup+0x615
hammer2_xop_readdir() at hammer2_xop_readdir+0x151
hammer2_primary_xops_thread() at hammer2_primary_xops_thread+0x280
```

(panic.txt; matches the cited path exactly.) Control base.img mounts and
lists f1 normally.

## Fix validation

Same `fix.diff` as DF-5784 (validated together, kernel #1 Oct 12 15:27):
`hammer2_bref_sane()` rejects non-media types and
`hammer2_base_find()` skips such elements like EMPTY slots. On the patched
kernel craftA mounts, `ls` returns cleanly (`total 0` — poisoned entry
invisible), `stat` returns ENOENT, umount OK — panic gone (`run_fix.log`).

## Impact

Local DoS (panic) under the mounted-crafted-image threat model (root
mount, or unprivileged with vfs.usermount=1 + owned vnode device) — same
class/severity as DF-2618/DF-2632 panics. No memory-corruption primitive:
the panic is a fail-stop before any allocation-sized operation.

## Fix status (batch validation)
Applied with the 36 sibling fix.diffs of this batch in findings/poc/_batch41/all_fixes.patch; single in-guest `make -j6 nativekernel KERNCONF=X86_64_GENERIC` completed RC=0 with zero errors under -Werror (full log: findings/poc/_batch41/fix_build8.log). Behavior A/B intentionally skipped for this Info-tier item.
