# DF-0800 — Missing NULL check on `hammer2_inode_chain` in truncate reset path

**Finding:** `sys/vfs/hammer2/hammer2_xops.c:1603-1607` —
`hammer2_xop_inode_chain_sync()` re-fetches the inode chain after the
truncate-down delete loop but does NOT check the return value for NULL
before dereferencing it in `kprintf(..., parent->data->ipdata.filename)`.
Every other xop in this file (and the first fetch in this very function at
:1547) tests for NULL; this is the sole exception.

**Severity:** Low (CVSS 3.1 `AV:L/AC:H/PR:L/UI:N/S:U/C:N/I:N/A:L`, CWE-476).

## Files

| file | purpose |
|------|---------|
| `df0800_trunc_deref.c` | PoC: exercises the truncate path from unprivileged userspace |
| `build.sh` | builds `df0800_trunc_deref` |
| `run.sh` | runs the PoC on the HAMMER2 root fs |
| `fix.diff` | the one-hunk fix (NULL check + bounded `%.*s`) |
| `VERDICT.md` | full narrative: dead-code analysis, fix validation |
| `build.log` | full compiler output |
| `run.log` | baseline (#0) run — no crash, no "TRUNCATE RESET" in dmesg |
| `fix_build.log` | full nativekernel build output with fix applied |
| `fix_run.log` | fixed-kernel run — same behavior, no regression |
| `env.txt` | guest environment |
| `manifest.json` | machine-readable artifact catalog |

## Reproduce

The PoC runs as the unprivileged user on the HAMMER2 root filesystem:

```sh
./build.sh
./run.sh
```

## Expected output

**Bug present (unpatched `#0`):**
```
DF-0800: completed 8 grow/shrink/fsync cycles
DF-0800: done.
RUN_EXIT=0
# dmesg: 0 (no "TRUNCATE RESET" — reset block is dead code, see VERDICT.md)
```

**Fixed kernel:**
```
DF-0800: completed 8 grow/shrink/fsync cycles
DF-0800: done.
RUN_EXIT=0
# dmesg: 0 (identical — no behavioral change since the path was unreachable)
```

The vulnerable reset block at `hammer2_xops.c:1600-1608` is **dead code** in
the current codebase: the `hammer2_chain_lookup`/`hammer2_chain_next` iteration
always returns `parent` to the inode (type `HAMMER2_BREF_TYPE_INODE`) because
the upward-recursion loop in `hammer2_chain_lookup` (:2429-2439) walks parent
back to the inode whenever `key_end = HAMMER2_KEY_MAX` exceeds any indirect
block's range. The NULL check is therefore a **defense-in-depth** fix that
makes the code safe if the iteration logic ever changes. See `VERDICT.md` for
the full chain-iteration trace.
