β¬’ DragonFlyBSD Kernel Audit
← triage Β· dashboard
DF-5171

vcdstrategy() panics on BUF_CMD_FLUSH/BUF_CMD_FREEBLKS β€” mounting a HAMMER filesystem on a vcd-backed volume kills the vkernel inside mount(2)

Summary

The b_cmd switch handles only READ/WRITE - HAMMER delivers BUF_CMD_FLUSH on every mount recovery and flusher cycle, dscheck passes it through, and the vkernel panics 'bad b_cmd 5'. The vdisk sibling got FLUSH handling in the 2016 rework; vcd never did. REPRODUCED in a booted vkernel ('panic: vcd: bad b_cmd 5' at uptime 3s, instrumented fatal bio); FLUSH->fsync + complete-unknown fix validated (clean HAMMER mount, 15s flusher cycles, no panic). Currently gated behind the DF-1031 -c boot bug. BONUS routed: FFS TRIM is dead code tree-wide (ffs_alloc.c:1670 gates on i_devvp->v_mount which is devfs, never the UFS mount) - one-word fix ITOV(ip)->v_mount.

Discussion (0)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-5171 Β· 18 files
FileTypeDescriptionSize
README.md β€” 3.0 KB ↓ raw
VERDICT.md β€” 5.1 KB ↓ raw
vcd_flush_poc.c β€” 2.3 KB view raw
vcd_trim_poc.c β€” 4.2 KB view raw
odcat.c β€” 843 B view raw
guest_prep.sh β€” 1.3 KB view raw
build.sh β€” 173 B view raw
run.sh β€” 160 B view raw
build.log β€” 8.4 KB view raw
fix_build.log β€” 433 B view raw
run.log β€” 2.4 KB view raw
fix_run.log β€” 2.2 KB view raw
panic.txt β€” 25 B view raw
env.txt β€” 462 B view raw
fix.diff β€” 844 B view raw
manifest.json β€” 1.2 KB view raw
verdict.json β€” 5.4 KB view raw
hashes.txt β€” 392 B view raw

DF-5171 β€” vcdstrategy() panics on BUF_CMD_FLUSH / BUF_CMD_FREEBLKS

vkernel virtual CD-ROM driver β€” sys/dev/virtual/vkernel/cdrom/vcd.c

What

vcdstrategy() (vcd.c:136-177) handles only BUF_CMD_READ and BUF_CMD_WRITE; every other buffer command hits:

default:
        panic("vcd: bad b_cmd %d", bp->b_cmd);   /* vcd.c:165 */

The vdisk sibling was reworked in 2016 and handles BUF_CMD_FLUSH (vdisk.c:332-337, fsync() on the backing file); vcd never got that treatment. Two in-tree producers deliver non-R/W bios to any block device:

  • HAMMER β€” hammer_io_flush_sync() (sys/vfs/hammer/hammer_io.c:1985-2007) issues a BUF_CMD_FLUSH bio (b_bcount=0, bio_offset=0) through vn_strategy(volume->devvp) on every mount-time recovery and every flusher cycle (hammer_io_wait_all(..., 1) from hammer_flusher.c:784,910 and hammer_recover.c:1438,1461,1465).
  • FFS with mount -o trim β€” ffs_blkfree() (sys/vfs/ufs/ffs_alloc.c:1668-1705) issues BUF_CMD_FREEBLKS. (Killed during verification: the MNT_TRIM gate tests ip->i_devvp->v_mount β€” the devfs mount β€” so the TRIM branch is dead code in this tree; see VERDICT.md Β§4.)

Impact

A guest root inside the vkernel that mounts a HAMMER filesystem on a vcd-backed volume (or would use FFS TRIM on one) panics the vkernel instantly β€” inside the mount(2) syscall. The vkernel is a userspace process on the host, so this is a crash/DoS of the vkernel instance, not of the host kernel and not a privilege boundary on the host.

Currently gated behind DF-1031: on HEAD, init_disk(cdFile, NULL, …, VKD_CD) passes diskFlags == NULL and dereferences diskFlags[i] (sys/platform/vkernel64/platform/init.c:487-489 + init_disk body), so the -c option kills the vkernel before any vcd device can exist. The moment that crash is fixed (a two-line fix), this bug is live: the PoC boots a vkernel with that enabler applied and panics on the first HAMMER flush.

Verified on the guest (2026-09-30)

  • Baseline: panic: vcd: bad b_cmd 5 at uptime 3s, inside mount(2) β€” run.log:45-46, backtrace included. (That build also carried a temporary per-bio kprintf which recorded the fatal bio verbatim: VCDDBG: bio cmd=5 off=0 bcount=0 dev=vcd0 β€” exactly the hammer_io_flush_sync shape.)
  • Fix validated: with fix.diff (BUF_CMD_FLUSH β†’ fsync(sc->fd), other commands β†’ complete instead of panic) the same PoC prints mount(hammer) returned OK and RESULT: no-panic (STILL ALIVE after 15s) β€” fix_run.log:39-40 β€” and HAMMER reports mounted clean.

Files

  • vcd_flush_poc.c β€” decisive PoC (HAMMER route; static PID1 binary).
  • vcd_trim_poc.c β€” the FFS-TRIM route explored first (kept as the document of why it is dead: ffs_alloc.c:1673).
  • odcat.c, guest_prep.sh, build.sh, run.sh β€” harness.
  • run.log / fix_run.log / panic.txt β€” baseline panic, patched run.
  • build.log / fix_build.log, env.txt β€” build output, guest env.
  • fix.diff β€” the validated fix (git-apply-able against vcd.c).
VERDICT.md
↓ download raw

DF-5171 VERDICT β€” vcd FLUSH/FREEBLKS panic: REPRODUCED, fix validated

1. Reproduced? YES β€” panic: vcd: bad b_cmd 5 inside mount(2)

Guest: DragonFly 6.5-DEVELOPMENT x86_64 (X86_64_GENERIC #0 Thu Jul 2 2026), cc 8.3, vm.vkernel_enable=1. A purpose-built VKERNEL64 (built from the guest's /usr/src, which mirrors the audited tree) was booted as:

./vkernel64 -m 256M -r root.img -c hammer.img -e init_path=/poc
  • root.img β€” 128 MB UFS carrying the static PID1 PoC (vcd_flush_poc.c);
  • hammer.img β€” 2 GB newfs_hammer -f volume, attached as the CD.

/poc runs mount("hammer", "/mnt", 0 /*rw*/, &info) with info.volumes = {"/dev/vcd0"}. Decisive console sequence (run.log:34-46):

DF-5171(B) vcd FLUSH panic PoC (hammer) start
MOUNTING hammer on /dev/vcd0 (rw)...
VCDDBG: bio cmd=1 off=0 bcount=16384 dev=vcd0      <- hammer reads vol header
HAMMER(vktest2) recovery check seqno=000fffff
VCDDBG: bio cmd=2 off=0 bcount=16384 dev=vcd0      <- rw mount writes header
VCDDBG: bio cmd=5 off=0 bcount=0    dev=vcd0      <- hammer_io_flush_sync()
panic: vcd: bad b_cmd 5                             <- vcd.c:165
Uptime: 3s

(The VCDDBG lines came from a temporary one-line kprintf in the guest's vcd.c used during triage; it is not part of the finding or the fix and records the fatal bio's identity: BUF_CMD_FLUSH, offset 0, length 0 β€” exactly hammer_io.c:1995-1999.)

The mount(2) never returns; the vkernel dies at 3s uptime. Backtrace and panic.txt in the pack.

2. The chain, line by line

  1. hammer rw mount on /dev/vcd0 β†’ mount-time recovery walks the undo FIFO and then calls hammer_io_wait_all(hmp, ..., 1) (hammer_recover.c:1438/1461/1465; every flusher cycle does the same, hammer_flusher.c:784/910).
  2. hammer_io_flush_sync() (hammer_io.c:1985-2007): getpbuf, bp->b_cmd = BUF_CMD_FLUSH, bio_offset = 0, b_bcount = 0, vn_strategy(volume->devvp, &bp->b_bio1).
  3. devfs spec strategy passes non-READ/WRITE bios straight through (devfs_spec_strategy, devfs_vnops.c β€” the b_cmd != READ && != WRITE shortcut β†’ dev_dstrategy_chain, kern_device.c:392).
  4. /dev/vcd0 is a disk-core covering device (disk1_ops, subr_disk.c:716-745) β†’ diskstrategy β†’ dscheck() (subr_diskslice.c:91): offset 0 / length 0 is sector-aligned and in bounds β†’ passes through untouched (dscheck never filters by b_cmd).
  5. dev_dstrategy(dp->d_rawdev) β†’ vcdstrategy() β†’ switch (bp->b_cmd) has no FLUSH case β†’ panic("vcd: bad b_cmd 5") (vcd.c:154-167).

3. Fix validated (baseline panic β‡’ patched clean)

fix.diff adds the vdisk-mirror FLUSH case (fsync(sc->fd), cf. vdisk.c:332-337) and completes unknown commands instead of panicking. Guest rebuilt with only that diff applied to vcd.c:

fix_run.log:35  HAMMER(vktest2) mounted clean, no recovery needed
fix_run.log:39  mount(hammer) returned OK
fix_run.log:40  RESULT: no-panic (STILL ALIVE after 15s)
fix_run.log:58  Uptime: 18s

15 seconds of HAMMER flusher cycles (multiple FLUSH bios each completed via fsync) β€” no panic, mount fully functional.

4. Secondary discovery (killed route, real bug, out of this file's scope)

The FFS-TRIM producer was explored first and is dead code in this tree: ffs_blkfree() gates the trim bio on ip->i_devvp->v_mount->mnt_flag & MNT_TRIM (ffs_alloc.c:1670-1677) β€” but i_devvp is the device vnode, whose v_mount is devfs, never the UFS mount that carries MNT_TRIM. Empirically proven inside the vkernel: statfs("/mnt") showed f_flags=01001040 (MNT_TRIM set), file deletions and reallocation writes produced only cmd=1/cmd=2 bios (instrumented run), and the bitmap frees went through ffs_blkfree_cg. β‡’ FFS TRIM never issues trim bios anywhere in this kernel β€” worth a separate finding against ffs_alloc.c (one-word fix: ITOV(ip)->v_mount).

5. Reachability preconditions (honest scoping)

  • Requires a vkernel booted with -c <image> β€” which today crashes in init_disk (DF-1031: diskFlags == NULL deref, init.c:487-489). The PoC applies a two-line guest-local enabler (NULL β†’ static zero flags, plus O_RDWR for the CD backing to allow a rw mount β€” vcd's dev_ops include physwrite, so a writable image is an ordinary configuration). With DF-1031 fixed upstream, the panic is reachable on any vkernel given a CD image.
  • Trigger needs guest-root (mounting a filesystem). Guest-unprivileged users cannot mount; once root mounts HAMMER the panic is immediate.
  • Impact ceiling: DoS of the vkernel process (userspace). No host kernel impact, no host privilege boundary crossed β€” hence Low, not Medium.

6. Verdict

reproduced / impact panic (of the vkernel target process) / confidence certain. Fix: add FLUSH handling mirroring vdisk and stop panicking on unknown commands β€” validated on the guest.

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.

Fix verification

fixed
baseline reproduced→ patch + rebuild →patched clean

Single-fix kernel (only fix.diff applied to vcd.c) reboots the identical PoC to a clean HAMMER mount: 'mounted clean, no recovery needed', 'mount(hammer) returned OK', 15s of flusher cycles with no panic (fix_run.log:35-58, Uptime 18s vs 3s baseline panic). Baseline reproduced the panic immediately before and after (same images, same boot line).

fix_run.log:35-40,58 (patched run); run.log:45-46 (baseline panic); fix.diff (unified diff, applied cleanly, byte-compared); fix_build.log (MAKE_RC=0).
↓ fix.diffper-fix-DF-5171

Confirmed kernel references

Detail

Exploit chain

vkernel booted with -c (requires DF-1031 fixed/enabled) -> guest root: mount("hammer", "/mnt", rw, volumes={"/dev/vcd0"}) -> hammer mount recovery calls hammer_io_wait_all(...,1) -> hammer_io_flush_sync() builds BUF_CMD_FLUSH bio (b_bcount=0, bio_offset=0) -> vn_strategy(devvp) -> devfs_spec_strategy passthrough (b_cmd!=READ/WRITE shortcut) -> diskstrategy -> dscheck passes (0/0 is aligned+in-bounds; dscheck never filters by b_cmd) -> dev_dstrategy(vcd0 rawdev) -> vcdstrategy() -> default: panic("vcd: bad b_cmd 5"). DoS of the vkernel instance.

Evidence (decisive lines)

run.log:34-46 (MOUNTING -> VCDDBG cmd=5 off=0 bcount=0 -> panic: vcd: bad b_cmd 5, Uptime 3s, backtrace); panic.txt (one-line signature); fix_run.log:35-40 (patched: 'mounted clean', 'mount(hammer) returned OK', 'RESULT: no-panic (STILL ALIVE after 15s)'); fix.diff (validated); VERDICT.md sec.4 (FFS-TRIM route proven dead: ffs_alloc.c:1673 tests the devfs mount's flags).

PoC changes

Seed sketch replaced entirely: wrote a static PID1 binary (vcd_flush_poc.c) that mounts HAMMER rw on /dev/vcd0 via mount(2) with struct hammer_mount_info (verbatim layout from hammer_mount.h); logging goes to /dev/console (init's stdio is dead in the vkernel) plus /marker. Harness mirrors DF-5010's: config VKERNEL64 + build-id link workaround, root.img UFS with the PoC, images built guest-side (newfs_hammer -f -L for a 2GB volume; dd+sync ordering matters: flush the zero-fill BEFORE formatting or late zero pages clobber the volume header). Two guest-local test-enablers in the guest's /usr/src only: (1) DF-1031 NULL-flags guard so -c can create a vcd at all, (2) CD backing opened O_RDWR so a rw mount is possible (vcd carries physwrite; vkd -r opens O_RDWR the same way). FFS-TRIM route (vcd_trim_poc.c) explored first and killed: the MNT_TRIM gate reads the devfs mount's flags (ffs_alloc.c:1670-1677) so no FREEBLKS bio is ever issued - documented in VERDICT.md sec.4 as a separate bug for ffs_alloc.c.

Verified recommended fix

Handle BUF_CMD_FLUSH via fsync(sc->fd) mirroring vkd_doio (vdisk.c:332-337) and complete unknown buffer commands instead of panic("vcd: bad b_cmd") - see validated fix.diff.

Verdict

vcdstrategy() panics on any buffer command other than READ/WRITE (vcd.c:154-167); HAMMER delivers BUF_CMD_FLUSH on mount recovery and every flusher cycle, so mounting a HAMMER filesystem on a vcd-backed volume kills the vkernel inside mount(2). Reproduced on the guest with a purpose-built VKERNEL64: 'panic: vcd: bad b_cmd 5' at uptime 3s (run.log:45-46) with the fatal bio instrumented as cmd=5 off=0 bcount=0 - exactly hammer_io_flush_sync()'s shape. The vdisk sibling handles FLUSH (vdisk.c:332-337); vcd never got the 2016 rework. Scope: crash/DoS of the vkernel userspace process (guest-root trigger); no host-kernel impact. Currently gated behind DF-1031 (-c crashes in init_disk before any vcd exists) - verified live the moment that two-line bug is fixed.