# DF-0792 — NULL deref in HAMMER direct I/O when hammer_get_volume fails

## Verdict
**REPRODUCED — Low-severity local DoS (kernel panic via NULL-pointer fetch).**

The bug is a real, reachable NULL-pointer dereference in three HAMMER
direct-I/O paths. Triggering it requires an admin to mount a maliciously
crafted HAMMER v1 filesystem image and then read any file whose B-Tree
leaf entries reference a volume number that is not present in the mount.
No memory corruption, no info leak, no privilege escalation — pure DoS
(kernel panic). Validated end-to-end: panic on the unpatched `#0` kernel,
clean EIO/ENOENT error return + guest stays up on the single-fix `#1`
kernel.

## How to reproduce

```sh
# As root on the DragonFlyBSD guest (vfs.usermount=0 by default).
scp corrupt_hammer.c run.sh build.sh dfbsd:/root/htest/
ssh dfbsd
sh /root/htest/run.sh
```

`run.sh` does everything end-to-end:
1. Creates a 2 GiB sparse image and `newfs_hammer -V 6` on it (vol_version
   6 so the B-Tree node CRC is plain crc32, which the corruptor can
   recompute locally).
2. Mounts it, writes a 256 KiB `testfile` (forces the zone-10 large-data
   direct-I/O path), syncs, unmounts.
3. Runs `corrupt_hammer`, which scans the offline image for B-Tree leaf
   nodes (validated by recomputing the node CRC), finds every zone-10
   leaf entry whose data_len >= 16 KiB, and flips the `vol_no` byte of
   `data_offset` from `0` to `7` (any value not in the mount). The B-Tree
   node CRC is then recomputed so the kernel's
   `hammer_crc_test_btree()` check (`hammer_ondisk.c:1315`) does not
   reject the node before reaching the buggy call site.
4. Re-mounts the now-malicious image and `cat`s `testfile`.

### Expected

| Kernel                       | Behaviour                                                |
|------------------------------|----------------------------------------------------------|
| Unpatched `#0` (audit base)  | Kernel panic; guest dies in DDB.                         |
|                              | `Stopped at hammer_rel_interlock+0x20: movl (%r12),%ebx` |
| Patched `#1` (this fix.diff) | `cat: ...: No such file or directory` (ENOENT from the   |
|                              | missing volume). Guest stays up. `dmesg` shows repeated  |
|                              | `hammer_io_direct_read: failed @ 2070000022000000`.      |

## Trigger realism

`vfs.usermount=0` on this guest, so mounting a HAMMER image requires
root. The realistic threat is "admin mounts a HAMMER image obtained from
an untrusted source" (USB stick, downloaded snapshot, restore-from-backup
of unknown provenance). HAMMER is no longer the default filesystem
(HAMMER2 is), but HAMMER v1 is still supported, so an admin can be
presented with one. The default `vfs.hammer.verify_zone=0` means the
blockmap lookup does not validate the zone-2 vol_no before it reaches
`hammer_get_volume`, so the malicious value is delivered verbatim.

## Files in this folder

| File                | What                                                     |
|---------------------|----------------------------------------------------------|
| `corrupt_hammer.c`  | offline image corruptor: flips vol_no byte + rebuilds btree node CRC |
| `build.sh`          | `cc -O2 -o corrupt_hammer corrupt_hammer.c`              |
| `run.sh`            | end-to-end trigger (newfs, mount, write, corrupt, remount, cat) |
| `fix.diff`          | git-apply-able unified diff: NULL guards at 3 io.c call sites + defense-in-depth NULL check in `hammer_rel_volume` itself |
| `panic.txt`         | serial-console panic signature from the unpatched run    |
| `fix_build.log`     | full `make nativekernel` output for the patched kernel   |
| `fix_run.log`       | full `run.sh` output on the patched kernel (no panic)    |
| `env.txt`           | guest uname, kern.version, kernel sha256, sysctl state   |
| `VERDICT.md`        | human-readable root-cause narrative                      |
| `manifest.json`     | machine-readable artifact catalog                        |
