# DF-0002 — PoC

`sys_fhopen()` at `sys/kern/vfs_syscalls.c:4933-4938` has a missing
`error = EINVAL` before `goto bad_drop` when a `VREG` vnode has
`v_object == NULL` after `VOP_OPEN`. The function returns `0` (success)
while the reserved fd slot is freed and `sysmsg_result` is never written,
so the caller sees "fd 0" (its own stdin) — no real descriptor allocated.

**Info-severity correctness bug.** `fhopen` is gated by
`SYSCAP_RESTRICTEDROOT` (root-only) and the trigger requires an FS-invariant
violation (a `VREG` vnode without a VM object after `VOP_OPEN`), which no
in-tree filesystem produces. There is no security impact; reported for
correctness/hardening.

## Files

| File | Purpose |
|------|---------|
| `fhopen_spur.c` | User-space PoC: mount tmpfs, open a file, get its handle, ask the KLD to null `v_object`, then `fhopen(2)` the handle. |
| `df0002_trigger.c` | Tiny KLD exposing `debug.df0002.corrupt_fd`. Writing a positive fd saves-and-nulls that vnode's `v_object`; writing `-1` restores. |
| `Makefile` | Builds `df0002_trigger.ko` against `/usr/src/sys`. |
| `build.sh` / `run.sh` | Exact build/run commands. |
| `VERDICT.md` | Full narrative + before/after evidence. |
| `fix.diff` | `git apply`-able one-line fix (matches finding proposal). |
| `manifest.json` | Site-renderable catalog. |

## Build

```
./build.sh    # builds fhopen_spur + df0002_trigger.ko (guest, root, /usr/src present)
```

## Run

```
./run.sh      # kldload df0002_trigger.ko && ./fhopen_spur
```

## Expected output

Bug present (unpatched `#0`):

```
[*] v_object nullified on vnode for fd 3 (/tmp/df0002_tmpfs/file)
fhopen returned 0 (success)
BUG: fd 0 is stdin, no real descriptor was allocated
```

Fixed (single-fix `#1`):

```
[*] v_object nullified on vnode for fd 3 (/tmp/df0002_tmpfs/file)
fhopen failed as expected: Invalid argument (errno=22)
```

Normal usage (no KLD, normal FS) is unaffected on either kernel: `./fhopen_spur`
on a UFS file returns `fhopen returned 3 (success)` — a real descriptor.

## Preconditions (realistic-threat model)

The KLD is root-only (`kldload`) and is just a tool to demonstrate the buggy
code path fires; it is **not** part of any escalation chain. The bug itself is
also gated by `SYSCAP_RESTRICTEDROOT`, so there is no privilege boundary being
crossed. The realistic in-tree trigger is the FS-invariant violation alone
(e.g. a buggy/out-of-tree/fuse filesystem) — without the KLD, the bug requires
that violation to manifest.
