# DF-0763 — Missing on-disk radix validation in hammer2 chain allocation

**Verdict: REPRODUCED (panic on default GENERIC).**
**Impact: panic / reliable local DoS from a crafted hammer2 filesystem image.**
On a non-INVARIANTS kernel the same primitive becomes a silent heap OOB
read/write (characterized, not escalated on the default target).

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

## What the bug is

`hammer2_chain_alloc` (`sys/vfs/hammer2/hammer2_chain.c:189-190`) computes
`chain->bytes = 1U << radix` from the low 6 bits of the on-disk
`bref->data_off` without validating `radix <= HAMMER2_RADIX_MAX` (16).  A
malicious image setting radix 17..63 makes `chain->bytes` exceed
`HAMMER2_PBUFSIZE` (64KB), and the subsequent `hammer2_io_bread` drives
`hammer2_io_alloc` (`hammer2_io.c:126`) into a KKASSERT panic on INVARIANTS-ON
kernels (the default).

## How to reproduce

```sh
# On the host (has python3):
./build.sh                    # builds vnconfig (guest), creates+crafts the image
ssh -F dfbsd-qemu/config dfbsd 'cp /tmp/h2_craft_radix17.img /tmp/ && sh /root/run_remote.sh'
# or, after build.sh has pushed the image to the guest:
./run.sh                      # mounts the crafted image -> panic on #0, EINVAL on #1
```

### Expected

- **Unpatched kernel (`#0`):** immediate kernel panic at
  `hammer2_io.c:126` (`KKASSERT: pbase != 0 && ((lbase + lsize - 1) & pmask) == pbase`),
  guest enters DDB, ssh dies.  Serial log excerpt in `panic.txt`.
- **Patched kernel (`#1`):** mount returns `EINVAL`, dmesg shows
  `hammer2_mount: error Bad Blockref Error reading super-root`, guest stays up.
  Clean image still mounts and works (no regression).

## Files

| file                  | what                                                       |
|-----------------------|------------------------------------------------------------|
| `craft_radix_img.py`  | host-side image patcher (radix 10→17 + CRC32C recompute)   |
| `build.sh`            | builds vnconfig, creates clean image, crafts bad image     |
| `run.sh`              | mounts the crafted image (the trigger)                     |
| `h2_clean.img`        | clean 64MB hammer2 image (regression check)               |
| `h2_craft_radix17.img`| crafted image (sroot_blockset[0] radix=17)                |
| `fix.diff`            | validated fix (chain_alloc radix check + load_data guard)  |
| `run.log`             | baseline panic transcript (from serial log)                |
| `panic.txt`           | panic signature excerpt                                    |
| `fix_run.log`         | patched-kernel test transcript                             |
| `fix_build.log`       | single-fix kernel build log                                |
| `env.txt`             | guest environment (uname, cc, mount, hammer2 tools)        |
| `VERDICT.md`          | full narrative + impact analysis                           |
| `manifest.json`       | machine-readable artifact catalog                          |

## Preconditions (realistic)

- Attacker can supply a hammer2 filesystem image that a victim mounts
  (USB media, downloaded image, `vfs.usermount=1` with a user-owned device,
  auto-mount).  No special privilege beyond image delivery is required.
- The vulnerable kernel is the **default** `X86_64_GENERIC`
  (hammer2 compiled in; root fs on this guest is hammer2).

## The fix

`fix.diff` adds:
1. A radix bounds check in `hammer2_chain_alloc` that flags the chain with
   `HAMMER2_ERROR_BADBREF` when `radix > HAMMER2_RADIX_MAX`.
2. An early-return guard in `hammer2_chain_load_data` that rejects any chain
   already flagged `HAMMER2_ERROR_BADBREF`, preventing the bad radix from
   reaching `hammer2_io_bread`/`hammer2_io_alloc`.

Validated on a single-fix kernel (`#1`): the panic is gone, the crafted image
is cleanly rejected with `EINVAL`/`Bad Blockref Error`, and normal hammer2
operation (mount/read/write of a clean image) is unaffected.
