# DF-0918 — NULL-deref panic via dead/replied race in fuse_ipc_wait early-return

**Severity:** Medium (CWE-476 NULL Pointer Dereference)
**Impact:** kernel panic / DoS (root→kernel; not an escalation)
**Reachability:** root-only on default GENERIC (`/dev/fuse` is `root:operator 0660`,
`mount("fuse")` needs `caps_priv_check(SYSCAP_NOMOUNT_FUSE)` → uid 0).

## The bug

`fuse_ipc_wait()` (`sys/vfs/fuse/fuse_ipc.c:158-204`) has early-return paths
at `:169-170` and `:173-174` that test the `replied` flag and `return 0`
without re-checking the mount's `dead` flag and without verifying
`fip->reply.buf` is populated. `fuse_device_clear()` (`fuse_device.c:99-116`)
sets `replied` on pending fips during teardown WITHOUT assigning
`fip->reply.buf` (it stays NULL from `fuse_ipc_get:fuse_ipc.c:104`).

If a tx waiter is between `:163` (dead check) and `:198` (post-tsleep dead
recheck) when `fuse_device_clear` runs, it observes `replied==1` and
returns 0. Back in `fuse_ipc_tx`:
```
fuse_ipc.c:274   ohd = fuse_out(fip);    // == fip->reply.buf == NULL
fuse_ipc.c:275   KKASSERT(ohd);          // PANIC (INVARIANTS ON, default GENERIC)
                       // or :276 ohd->error NULL-deref
```

## Reproduction

The race window is nanoseconds wide (between `tsleep_interlock` at `:172`
and the replied check at `:173`); `fuse_device_clear` runs once per
teardown. A deterministic pthread harness forces the worst-case
interleaving and proves the primitive; a live daemon exercises the code
path at runtime.

### Build & run (deterministic models — primary proof, unprivileged)

```
ssh dfbsd-maxx 'cd poc/DF-0918 && sh build.sh'
# UNFIXED model — confirms the NULL-ohd primitive
./harness
# FIXED model — proves the fix closes the window
./harness_fixed
```

Expected:
- `./harness` → `RESULT: NULL-ohd PRIMITIVE reproduced deterministically.`
- `./harness_fixed` → `RESULT: FIXED model — NO NULL-deref.`

### Live path-exercised run (root only)

```
ssh dfbsd 'kldload fuse && mkdir -p /mnt/df918 && cd /root/poc918 && \
  cc -O2 -pthread -o fused0918 fused0918.c && ./fused0918 3 1'
```

Drives 3.6M+ `stat()` calls (= `fuse_ipc_tx`es) and 3 force-unmount
teardowns. The nanosecond race is not expected to fire in a short run;
the harness is the proof. If the guest stays up, the path was exercised
without winning the race.

### Fix validation (module-only)

```
ssh dfbsd 'cd /usr/src && patch -p1 < /root/fix.diff && \
  cd sys/vfs/fuse && make && cp fuse.ko /boot/kernel/fuse.ko && kldload fuse'
# re-run the daemon on the patched module — should behave identically (no regression)
```

## Files

- `harness.c` — deterministic pthread model (UNFIXED/FIXED)
- `fused0918.c` — live FUSE race daemon
- `fix.diff` — `goto done` + `done:` label (dead-recheck-after-replied)
- `build.sh` / `run.sh` — exact build/run commands
- `VERDICT.md` — full narrative + line-by-line trace
- `manifest.json` — machine-readable catalog
- `*.log` — full untrimmed build/run/fix logs
