# DF-0821 — PoC

**OOB heap read/write via crafted signed `bmap->linear` from a hammer2
filesystem image.**

## Summary

The linear-iterator guard in `hammer2_bmap_alloc`
(`sys/vfs/hammer2/hammer2_freemap.c:616-619`) compares the on-disk
`int32_t bmap->linear` against `HAMMER2_SEGSIZE` (`1<<22`) with a **signed**
`<`, so a negative `linear` (e.g. `0x80001000` = -2147479552) passes the guard.
On default GENERIC (INVARIANTS ON) the downstream `KKASSERT(bmap->linear >= 0
&& ...)` at `:631-633` then panics the kernel — reliable DoS from a crafted
hammer2 image.  On an INVARIANTS-OFF kernel the KKASSERT is compiled out and
the negative `linear` drives `i = linear/(SEGSIZE/8) = -4095`, making
`bmap->bitmapq[i]` an OOB read (`:727/:749/:785`) and OOB write (`:803`) 32760
bytes before `bitmapq[0]` in kernel heap.

## Files

| File               | Purpose                                                  |
|--------------------|----------------------------------------------------------|
| `craft_img.py`     | Poisons `bmap->linear` in a hammer2 image, recomputes CRCs |
| `harness.c`        | Deterministic userspace transcription of guard + OOB logic |
| `build.sh`         | Builds the harness                                       |
| `run.sh`           | Reproduces (harness + live kernel trigger)               |
| `fix.diff`         | Validated one-line guard fix                            |
| `VERDICT.md`       | Full narrative: mechanism, evidence, impact ceiling      |

## Reproduce

### Phase A — deterministic harness (safe, no panic, any user)

```
ssh dfbsd-maxx   # or any DragonFlyBSD user with cc
cd findings/poc/DF-0821 && ./build.sh && ./harness
```

Expected: the harness shows that `linear=0x80001000` passes all 3 guards,
would panic at KKASSERT `:631-633` on GENERIC, and drives `bitmapq[-4095]`
(32760-byte OOB) on a noinv kernel.

### Phase B — live kernel trigger (root; PANICS the unpatched guest)

```
ssh dfbsd        # root
cd findings/poc/DF-0821 && ./run.sh live
```

On **unpatched `#0` (INVARIANTS ON)**: `echo trigger > /mnt/h2821/poison_file`
panics the guest at `hammer2_freemap.c:633`.  Guest enters DDB; ssh dies;
panic is captured in `dfbsd-qemu/boot.log`.

On **fixed `#1`**: mount + write succeed; file is written correctly; guest
stays up.  No panic.

### Image crafting (what `run.sh live` does, step by step)

1. `truncate -s 64M clean.img && newfs_hammer2 -L testvol clean.img`
2. Mount + write 5 small files + unmount (populates freemap leaves on disk).
3. `python3 craft_img.py clean.img crafted.img 0x80001000`:
   - Parses volume header at offset 0; reads `freemap_blockset[0]` →
     `FREEMAP_LEAF` at disk offset `0x10000` (32KB, 256 `bmap_data` entries).
   - Poisons up to 10 entries with `avail > 0` by setting `linear =
     0x80001000` (negative int32 that passes all 3 guards).
   - Recomputes `HAMMER2_CHECK_FREEMAP` CRC (`iscsi_crc32` over 32KB leaf)
     and stores it in `freemap_blockset[0].check.freemap.icrc32`.
   - Recomputes `icrc_volheader` over the 64KB volume header.
4. `vnconfig -c vn1 crafted.img && mount -t hammer2 /dev/vn1@testvol /mnt`
5. `echo trigger > /mnt/poison_file`  →  freemap allocation  →  panic.

## Preconditions

- Root (or `vfs.usermount=1` + user-owned device) to mount the crafted image.
- Default `X86_64_GENERIC` kernel (HAMMER2 compiled in; it is the root FS).
- The crafted image's freemap leaf CRC and volume-header CRC are recomputed
  so the kernel trusts the poisoned leaf.

## Fix

`fix.diff`: add `bmap->linear >= 0 &&` as the first sub-condition of the guard
at `hammer2_freemap.c:616-619`.  When the guard fails (negative or oversized
`linear`), the allocator falls through to the bitmap-scan path, which does
not use `linear` for array indexing.  Validated on a single-fix `#1` kernel
(see `VERDICT.md`).
