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

vfs_mountroot_try ignores the ';' candidate bound when copying into mf: every root-mount candidate except the last gets a device name contaminated with the ';<tail>' of later candidates β€” fallback lists silently degenerate (valid candidates skipped; boot panic or wrong-device-as-root)

Field Value
ID DF-2863
Status new
Severity Low
CVSS 3.1 CVSS:3.1/AV:P/AC:L/PR:L/UI:N/S:U/C:N/I:L/A:L
CWE CWE-20 (delimiter not enforced on copy)
File sys/kern/vfs_conf.c
Lines 423, 427, 431
Area kern
Confidence certain
Discovered 2026-09-02
Pass 2 (GLM 5.3 second pass)
Bucket base:kern
Reported pending
Known CVE none
CVE match novel

Summary

The multi-candidate loop scans ep to the next ';' but strncpy(mf, cp, 96) copies the whole remaining string, and %80s terminates devname only on whitespace β€” never on ';' β€” so candidate N's devname absorbs ';candidateN+1;...'. Every non-final candidate's device lookup then fails, so only the LAST candidate of vfs.root.mountfrom can ever mount: if the last is absent while an earlier one is valid, boot panics ("Root mount failed, startup aborted."); if the last is a different device, it is silently mounted as root instead of the operator's primary. Boot-time integrity/availability; loader.conf/console-gated.

Proof of contest

VERIFIED on the guest (findings/poc/DF-2863/): (1) KLD replica: candidate 1 devname = 'da0s1a;ufs:da1s1a' (len 17) on stock vs 'da0s1a' with the fix; (2) real boot, stock kernel: no disk named 'da0s1a;ufs:' β€” the intended device never looked up; (3) fix validated on rebuilt kernel #1 with identical input: no disk named 'da0s1a' β€” contamination gone.

Same hunk as DF-2862 (seglen = min(epβˆ’cp, MFSNAMELEN+MNAMELENβˆ’1); bcopy) β€” fix.diff in the pack (validated).

Timeline

  • 2026-09-02 Discovered during pass-2 audit of vfs_conf.c (GLM 5.3); replica + real-boot reproduction + fix validation same run.

Discussion (0)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-2863 Β· 13 files
FileTypeDescriptionSize
replica.c β€” 6.7 KB view raw
Makefile β€” 52 B ↓ download
build.sh β€” 159 B view raw
run.sh β€” 145 B view raw
build.log β€” 6.1 KB view raw
run.log β€” 1.8 KB view raw
run.2.log β€” 1.2 KB view raw
bootpath-baseline.log β€” 829 B view raw
bootpath-patched.log β€” 508 B view raw
env.txt β€” 534 B view raw
fix.diff β€” 571 B view raw
VERDICT.md β€” 2.5 KB ↓ raw
verdict.json β€” 3.9 KB view raw
VERDICT.md
↓ download raw

DF-2863 VERDICT

Status: reproduced (KLD replica with the kernel's own ksscanf on the live stock kernel #0, AND on the real boot path with a crafted vfs.root.mountfrom; fix validated on rebuilt kernel #1). Impact: dos / root-selection integrity β€” the ;-separated candidate fallback list is silently broken for every candidate except the last; a valid earlier candidate can never mount, so an operator fallback configuration can panic at boot (Root mount failed, startup aborted., vfs_conf.c:208) or mount an unintended (last) device as root. Severity Low: boot-time, loader/console-gated.

Root cause (path:line)

  1. sys/kern/vfs_conf.c:422-423 β€” the loop computes ep = end of the current ;-delimited candidate.
  2. sys/kern/vfs_conf.c:427 β€” strncpy(mf, cp, MFSNAMELEN+MNAMELEN) copies from cp to the end of the whole string (or 96 bytes), ignoring ep.
  3. sys/kern/vfs_conf.c:430-431 β€” pattern %16[a-z0-9]:%80s; %80s (subr_scanf.c:359-386 CT_STRING) terminates only on whitespace, never on ; β€” so all remaining candidates fold into devname.
  4. sys/kern/vfs_conf.c:435-436,447 β€” the contaminated devname goes to vfs_rootmountalloc()/setrootbyname() β†’ kgetdiskbyname() β†’ device lookup fails (no disk named 'da0s1a;ufs:' β€” observed verbatim at boot, bootpath-baseline.log).

Proof

  • Replica A1 (run.log): devname='da0s1a;ufs:da1s1a' for candidate 1 of ufs:da0s1a;ufs:da1s1a β€” kernel's own parser, verbatim parse copy.
  • Boot path (bootpath-baseline.log): stock kernel #0 with vfs.root.mountfrom="ufs:da0s1a;ufs:<92 spaces>E;hammer2:vbd0s1d": no disk named 'da0s1a;ufs:' printed at boot; candidate 1 (and 2) fail; only the last candidate mounts. This shows the exact intended primary device da0s1a was never even looked up.
  • Fixed (bootpath-patched.log): kernel #1 with the shared fix (copy bounded at ep): no disk named 'da0s1a' β€” candidate parses exactly as configured; machine boots via the last candidate as intended.

Exploit chain

None (boot-time configuration handling). The realistic worst cases are (a) boot panic when the last candidate is absent though an earlier candidate is valid, and (b) silent selection of the wrong root device when the primary is the non-last entry β€” an integrity property of root selection, not an unprivileged runtime attack.

Fix validation

Shared fix.diff with DF-2862 (same hunk): validated on kernel #1 β€” contamination gone at boot with identical input. Guest restored with vm.sh reset with-src.

Fix verification

fixed
baseline reproduced→ patch + rebuild →patched clean

kernel #1 with fix.diff, identical test loader.conf: candidate 1's device name is exactly 'da0s1a' ('no disk named da0s1a') instead of 'da0s1a;ufs:' β€” contamination eliminated; boot completes via the last candidate as configured; replica A2 confirms per-candidate parsing

['bootpath-baseline.log (kernel #0) vs bootpath-patched.log (kernel #1)', 'run.log A1 (BUGGY) vs A2 (FIXED)']
↓ fix.diffDragonFly dfbsd 6.5-DEVELOPMENT DragonFly 6.5-DEVELOPMENT #1: Wed Sep 2 13:46:57 UTC 2026 root@dfbsd:/usr/obj/usr/src/sys/X86_64_GENERIC x86_64

Confirmed kernel references

Detail

Exploit chain

no escalation chain: boot-time configuration handling (loader.conf / mountroot console). Worst cases: boot-time DoS via valid-candidate-skipped -> panic('Root mount failed'), or wrong-device-as-root selection

Evidence (decisive lines)

["run.log: A1 'cand 1: vfsname=ufs devname=da0s1a;ufs:da1s1a (len=17)' vs A2 FIXED 'devname=da0s1a (len=6)'", "bootpath-baseline.log: stock kernel #0 boot β€” 'no disk named da0s1a;ufs:' then candidate skipped", "bootpath-patched.log: fixed kernel #1, identical loader.conf β€” 'no disk named da0s1a' (contamination gone)"]

PoC changes

authored fresh (no seed): KLD replica embedding a verbatim copy of vfs_conf.c:419-431 plus a fixed variant, driven through the kernel's own ksscanf/kmalloc; boot-path run via /boot/loader.conf vfs.root.mountfrom

Verified recommended fix

bound the copy at the ';' segment: seglen = min(ep - cp, MFSNAMELEN+MNAMELEN - 1); bcopy(cp, mf, seglen) (see fix.diff, shared with DF-2862)

Verdict

vfs_mountroot_try() computes the ';' segment end (ep, vfs_conf.c:423) but strncpy(mf, cp, MFSNAMELEN+MNAMELEN) at :427 ignores it, copying the whole remaining candidate list into the 96-byte parse buffer; the %80s conversion terminates devname only on whitespace, so every non-final root-mount candidate gets a device name contaminated with the ';' tail and fails device lookup. The fallback list silently degenerates to 'last candidate only': a valid earlier candidate can never mount (boot panic if the last is absent, vfs_conf.c:208) or a different-than-intended device gets mounted as root. Proven with the kernel's own ksscanf in a KLD replica (devname='da0s1a;ufs:da1s1a' for candidate 1) and on the real boot path (stock kernel #0, crafted vfs.root.mountfrom: 'no disk named da0s1a;ufs:' printed at boot, candidate never attempted). Fixed on rebuilt kernel #1: 'no disk named da0s1a' β€” clean segment parse, same input.