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.
Recommended fix
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)
PoC verification
Evidence pack
findings/poc/DF-2863 Β· 13 files| File | Type | Description | Size | |
|---|---|---|---|---|
| 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 |
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)
sys/kern/vfs_conf.c:422-423β the loop computesep= end of the current;-delimited candidate.sys/kern/vfs_conf.c:427βstrncpy(mf, cp, MFSNAMELEN+MNAMELEN)copies fromcpto the end of the whole string (or 96 bytes), ignoringep.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 intodevname.sys/kern/vfs_conf.c:435-436,447β the contaminateddevnamegoes tovfs_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 ofufs: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 deviceda0s1awas 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
fixedkernel #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)']
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 ';
No comments yet.