# DF-3218 VERDICT

**status: reproduced / impact: panic (vkernel-wide DoS) / confidence: certain**

## Environment

- QEMU guest: DragonFly 6.5-DEVELOPMENT #0 (native X86_64_GENERIC,
  2026-07-02), 6 vCPUs, cc 8.3. dirfs is vkernel64-only, so the PoC runs a
  **vkernel built inside the guest** (`nativekernel KERNCONF=VKDIRFS`,
  VKERNEL64 + `options DIRFS`), booted on a 160MB FFS image with a minimal
  userland, `vm.vkernel_enable=1`.
- Build prerequisites (bit-rot fixes, guest tree only — the audit repo sys/
  is untouched): missing `<sys/malloc.h>` in dirfs_vnops.c and dirfs_subr.c,
  missing `<sys/uio.h>` in dirfs_vnops.c, and `__build_id_*` undefined at
  the vkernel link (script-less SYSTEM_LD) — without these dirfs does not
  compile/link at all on the current tree.

## Chain (what was run)

1. Guest: `mkdir /dirfs-host` (a real directory).
2. vkernel boots to `/bin/sh` (via /sbin/vkinit wrapper) on a fifo console.
3. Inside vkernel (root): `/bin/vkmount /dirfs-host /hostroot`
   → `mount("dirfs", "/hostroot", 0, "/dirfs-host")` → dirfs_mount:
   copyinstr OK, `stat("/dirfs-host")` S_ISDIR OK, mount succeeds.
   `dmp->dm_root` still NULL (no VFS_ROOT yet).
4. Guest: `mv /dirfs-host /dirfs-host.real && ln -s /nonexistent-xyz /dirfs-host`
   → the mount's host path is now a dangling symlink.
5. Inside vkernel: `ls /hostroot` → namei crosses the mountpoint →
   VFS_ROOT → dirfs_root → dm_root==NULL →
   `lstat("/dirfs-host")` SUCCEEDS (the symlink itself; AT_SYMLINK_NOFOLLOW)
   → `dmp->dm_root = dnp` (:250), DIRFS_ROOT set →
   `open("/dirfs-host", O_DIRECTORY)` FOLLOWS the symlink → ENOENT → fd==-1
   → `dirfs_free_vp(dmp, dnp)` (dirfs_vfsops.c:261) →
   `vp = dnp->dn_vnode` == NULL (dirfs_subr.c:300) → `vp->v_data = NULL`
   (dirfs_subr.c:305) → **kernel-mode write to 0x128**.

## Observed (baseline, run.baseline.log)

```
# ls /hostroot
Fatal trap 12: page fault while in kernel mode
fault virtual address      = 0x128
instruction pointer        = 0x2b:0x72080b
current process            = 3 (ls)
Stopped at 0x72080b:       movq $0,0x128(%r13)
```

Symbolization on the vkernel binary:
- `nm -n`: `dirfs_free_vp` at 0x7207d0.
- `objdump -d`: `7207fd: movq $0x0,0x48(%rbx)` = `dnp->dn_vnode = NULL`;
  **`72080b: movq $0x0,0x128(%r13)` = `vp->v_data = NULL` (vp==NULL)** —
  byte-identical to the faulting instruction; the next instruction is
  `jmpq 7204d0 <dirfs_node_drop>` (the path that would drive the
  double-free if the store did not fault).

0x128 is `offsetof(struct vnode, v_data)` in this build. The vkernel OS is
dead at this point (ddb); every process in the vkernel dies with it.

## Fix validation (run.fixed.log)

`fix.diff` applied to the guest tree, vkernel relinked, **identical PoC
re-run**:
- `/bin/vkmount /dirfs-host /hostroot` → mounted
- symlink swap → `ls /hostroot` → **`ls: /hostroot: No such file or
  directory`** (VFS_ROOT returns the host ENOENT cleanly), `echo TRAVERSED`
  runs, vkernel stays alive.

fix_status: fixed (baseline reproduced → patched behavior correct).

## Why not uid0

The bug is a NULL-pointer *write of the constant 0* at a fixed offset —
deterministic fatal trap, not a controllable corruption primitive; there is
no attacker-influenced data in the store. dirfs runs only inside a vkernel
(a userland process on the host), so even a controllable corruption would
execute inside the vkernel process, whose "kernel memory" is the process's
own memory owned by the same host uid. Realistic ceiling: whole-vkernel
panic from a mount-traversal by any vkernel user — hence Medium, not higher.

## Residual notes

- The dm_root dangling (defect 3) and the double-free (defect 2) are latent
  behind the NULL-write in this exact build; the fix removes all three.
- The dm_root lazy-init race (filed separately as DF-3219) is a different
  defect on the same lines.

## Fix status (batch validation)
Applied with the 36 sibling fix.diffs of this batch in findings/poc/_batch41/all_fixes.patch; single in-guest `make -j6 nativekernel KERNCONF=X86_64_GENERIC` completed RC=0 with zero errors under -Werror (full log: findings/poc/_batch41/fix_build8.log). Behavior A/B intentionally skipped for this Info-tier item.
