# DF-0804 — VERDICT

**Verdict:** REPRODUCED on default GENERIC `#0`; **FIX-VALIDATED** on single-fix `#1`.
**Impact:** panic / DoS (local, via crafted HAMMER2 image). No escalation path
(NULL deref / panic at a fixed offset — pure availability impact).

## Mechanism (trigger → primitive → effect)

1. **Trigger.** A crafted HAMMER2 image is mounted and a file is read. The
   image's DATA blockref for that file carries `bref.methods` with low nibble
   `4` (undefined compression). The blockref passes integrity verification
   because the attacker (who controls the whole image) recomputes the covering
   XXH64/CRC32 cascade — see `corrupt_image.py`.

2. **Primitive.** The read reaches `hammer2_strategy_read_completion()`
   (`sys/vfs/hammer2/hammer2_strategy.c:441`). The DATA branch
   (`:458` → `:474`) does `switch (HAMMER2_DEC_COMP(focus->bref.methods))`.
   The value `4` matches none of NONE/LZ4/ZLIB and falls through to the
   `default:` at `:495`:

   ```c
   default:
       panic("hammer2_strategy_read_completion: "
             "unknown compression type");
   ```

3. **Effect.** `panic()` → `Debugger("panic")` → all CPUs stopped, DDB
   prompt, system halted. Serial console:

   ```
   panic: hammer2_strategy_read_completion: unknown compression type
   hammer2_xop_strategy_read() at hammer2_xop_strategy_read+0x606
   Stopped at Debugger+0x7c: movb $0,0xbdaf09(%rip)
   db>
   ```

The same function has a second unconditional `panic("unknown bref type")` at
`:500` for any `bref.type` other than INODE(1)/DATA(3), and a latent NULL
deref in the INODE branch (`:452`) when `bref.data_off == 0` (which makes
`hammer2_chain_load_data` return early at `hammer2_chain.c:938` leaving
`chain->data == NULL`, `chain->error == 0`).

## Why no escalation

This is a `panic()` / NULL-deref at a **fixed code offset** with no
attacker-controlled write primitive. The crash terminates the kernel; there is
no corrupted object to groom, no function pointer to redirect. It is a
pure denial-of-service. (The guest has SMAP/SMEP/KASLR off, but those only
matter for a write/control primitive — a panic is game-over-by-definition
with no shell to land.)

## Reachability confirmation (line-by-line)

* `hammer2_xop_strategy_read()` (`:308`) calls `hammer2_chain_lookup()`
  (`:344`) with `HAMMER2_LOOKUP_ALWAYS` — forces data resolution.
* `hammer2_chain_lookup()` (`:2387`) scans the parent's blockref array via
  `hammer2_combined_find()` (`:2599`) which returns the on-disk `bref`
  **without** validating `type`/`methods`/`data_off` ranges.
* `hammer2_chain_get()` builds a chain copying `bsave = *bref` verbatim
  (`:2599`); `hammer2_chain_load_data()` (`:920`) honours `data_off` and,
  if `(data_off & ~HAMMER2_OFF_MASK_RADIX) == 0`, returns at `:938` with
  `chain->data` still NULL and `chain->error` still 0.
* Frontend (`:401-410`): `error == 0` → `data = hammer2_xop_gdata()->buf`
  → `hammer2_strategy_read_completion(focus, data, bio)`.
* `hammer2_strategy_read_completion()` `:474` switch → `:495` default → panic.

## Fix (fix.diff)

Three changes, all in `hammer2_strategy_read_completion`:

1. **NULL-data guard** in the INODE branch (`:452`): if `data == NULL`, set
   `B_ERROR`/`EIO` and return (closes the `data_off==0` NULL-deref).
2. **`HAMMER2_COMP_AUTOZERO` case**: zero-fill the buffer (comp=1 is a valid
   "logically zero, not stored" method that previously fell through to panic).
3. **Replace both `panic()` calls** (`:496`, `:500`) with `kprintf` + `B_ERROR`/
   `EIO` + `bp->b_resid = bp->b_bcount`. A corrupted/crafted image now yields a
   per-read `EIO` (and a diagnostic dmesg line) instead of halting the kernel.

## Before / after

| kernel | trigger | result |
|---|---|---|
| `#0` unpatched baseline | mount crafted image, `cat target.bin` | **panic** `unknown compression type`, system halted, DDB `db>` |
| `#1` single-fix | same image, same command | **EIO** (`cat` exit 1), `dmesg: unknown compression type 4`, guest stays up |

Reproduced deterministically (×2 on the patched kernel). The build compiled
cleanly (`make -j6 nativekernel` rc=0) and the `#1` kernel boots and operates
normally.
