# DF-0822 — Sustained CPU-burn DoS via unchecked radix in hammer2_freemap_adjust

**Severity:** Medium · **Class:** mount-time DoS (unchecked attacker-controlled radix) · **Status:** reproduced, fix validated

## The bug

`hammer2_freemap_adjust()` (`sys/vfs/hammer2/hammer2_freemap.c`) extracts the
allocation radix from an on-disk `blockref.data_off` (low 6 bits, 0–63) and
uses it to compute a loop count without validation:

```c
radix = (int)data_off & HAMMER2_OFF_MASK_RADIX;   /* line 977 — 0..63, from disk */
KKASSERT(radix != 0);                              /* line 978 — no-op on production */
KKASSERT(radix <= HAMMER2_RADIX_MAX /*16*/);       /* line 980 — PANIC on GENERIC, no-op on noinv */
...
count = 1 << (radix - HAMMER2_FREEMAP_BLOCK_RADIX); /* line 1089 — radix=44 => 1<<30 */
while (count) { ... }                              /* line 1107 — ~1 billion iterations */
```

A crafted HAMMER2 image whose blockref carries radix ≥ 17 triggers, at mount:
- **GENERIC (INVARIANTS ON):** kernel **panic** at line 980.
- **Production (INVARIANTS OFF):** sustained **CPU-burn** (~1×10⁹ iterations).

## How to reproduce

The harness (`h2adj.c`) is a kernel module that calls the **real exported**
`hammer2_freemap_adjust()` with crafted radix values, simulating the on-disk
attack vector without needing to forge a multi-level CRC image.

```sh
./build.sh                # builds forge + h2adj.ko
./run.sh                  # kldload h2adj.ko → panic on unpatched kernel
```

**Expected on unpatched `#0` GENERIC:** panic
`assertion "radix <= HAMMER2_RADIX_MAX" failed in hammer2_freemap_adjust at hammer2_freemap.c:980`.

**Expected on patched `#1` kernel:** graceful returns with `ignoring bad radix N` warnings, guest stays up.

See `VERDICT.md` for the full analysis and `fix.diff` for the validated fix.
