msdosfs_mount() ignores the copyinstr(fspec) return and uses the then-uninitialized size as both offset and length in memset(f_mntfromname + size, 0, MNAMELEN - size) β garbage-indexed OOB write if the fspec string is raced out between nlookup and the copyin
Summary
At :281 copyinstr(args.fspec, mp->mnt_stat.f_mntfromname, MNAMELEN-1, &size) has its return value discarded. args.fspec was validated earlier by nlookup (:229-234), but a second thread of the same process can munmap/replace the string during the whole mountmsdosfs() window; a faulting copyinstr then leaves size as uninit stack garbage and :282 executes memset(mntfromname + size, 0, MNAMELEN - size) - size>MNAMELEN wraps the length huge and writes far outside the 80-byte field (kernel OOB write/panic). Newer FreeBSD replaced this with the vfs_mountedfrom() helper; DF kept the racy pattern. Requires mount privilege racing its own syscall (root: no gain) or unpriv user with vfs.usermount=1 (non-default) - a self-raced mount is then an unpriv kernel OOB write, realistically a panic (size/length stack garbage, not attacker-shaped). Phase V skipped (Low, non-default config). Fix: check the copyinstr return. [VERIFIED pass2: reproduced - ignored copyinstr plus stale-size memset executed 21 of 800 mounts with empty f_mntfromname observed; OOB variant conditional on stale stack value; caps layer blocks unpriv variant on stock]
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-3131 Β· 10 files| File | Type | Description | Size | |
|---|---|---|---|---|
| race_mount.c | β | 3.8 KB | view raw | |
| race2.c | β | 2.9 KB | view raw | |
| README.md | β | 583 B | β raw | |
| VERDICT.md | β | 2.5 KB | β raw | |
| build.sh | β | 141 B | view raw | |
| run.sh | β | 119 B | view raw | |
| manifest.json | β | 675 B | view raw | |
| verdict.json | β | 2.4 KB | view raw | |
| fix.diff | β | 504 B | view raw | |
| env.txt | β | 272 B | view raw |
DF-3131 -- verification pack
Finding: msdosfs_mount ignores copyinstr(fspec) return; memset with stale size
Status: reproduced / impact corruption / confidence likely
What was run
guest: cc -O -o race2 race2.c -lpthread
How to reproduce
guest: race2 /dev/vn0 /mnt/raceroot 800
Expected
GARBLED mntfromname lines proving the ignored copyinstr + stale-size memset executed; OOB variant depends on the stale stack value
See VERDICT.md for the full narrative and verdict.json for the persisted machine verdict. Artifacts: race_mount.c, race2.c, run.log.
DF-3131 verification
Kernel: DragonFly dfbsd 6.5-DEVELOPMENT #0: Thu Jul 2 06:02:54 UTC 2026 root@dfbsd:/usr/obj/usr/src/sys/X86_64_GENERIC x86_64
Bottom line
The race landed 21 times out of 800 successful mounts: with the fspec string page munmapped inside the mountmsdosfs device-probe window, the copyinstr at msdosfs_vfsops.c:281 faults, its return is ignored, and the memset at :282 runs with stale stack size - observed live as statfs f_mntfromname coming back EMPTY (stale size 0 zeroed the whole field) on exactly those 21 mounts. That is the un-initialized-length write executing in-kernel; on this kernel build the stale slot held the benign value 0, so the out-of-bounds variant (stale size > MNAMELEN makes MNAMELEN - size wrap to a huge memset length) was not manifested, only its precondition. Requires mount privilege (root self-race, or vfs.usermount=1; note the stock DFly 6.5 caps layer denied the unprivileged mount attempt outright even with vfs.usermount=1, an additional reachability constraint recorded here).
Kernel references
Exploit chain
race2.c: thread A calls mount(2) with fspec pointing into a private page; thread B cycles munmap/mmap with a swept delay tuned into the device-probe window; after each successful mount statfs is checked for a garbled f_mntfromname (= faulted copyinstr + stale-size memset)
Fix
check the copyinstr return before the memset; compile-validated
See fix.diff in this directory (git-apply-able against the read-only sys/ tree; applied only inside the disposable verification guest).
Reproduction
build: guest: cc -O -o race2 race2.c -lpthread
run: guest: race2 /dev/vn0 /mnt/raceroot 800
expected: GARBLED mntfromname lines proving the ignored copyinstr + stale-size memset executed; OOB variant depends on the stale stack value
Notes on the PoC (what changed vs the finding sketch)
v1 (loop mount + free-running unmapper) never landed the window: 50000 attempts, 8712 successful mounts, 0 hits; v2 sweeps the munmap delay 0.3-4.3 ms around each mount and checks statfs, landing 21/800
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
not_testableConfirmed kernel references
Detail
Exploit chain
race2.c: thread A calls mount(2) with fspec pointing into a private page; thread B cycles munmap/mmap with a swept delay tuned into the device-probe window; after each successful mount statfs is checked for a garbled f_mntfromname (= faulted copyinstr + stale-size memset)
Evidence (decisive lines)
run.log: 21 GARBLED mntfromname lines with the winning delays (~2.1-2.4 ms), ok=800 garbled_name=21
PoC changes
v1 (loop mount + free-running unmapper) never landed the window: 50000 attempts, 8712 successful mounts, 0 hits; v2 sweeps the munmap delay 0.3-4.3 ms around each mount and checks statfs, landing 21/800
Verified recommended fix
check the copyinstr return before the memset; compile-validated
Verdict
The race landed 21 times out of 800 successful mounts: with the fspec string page munmapped inside the mountmsdosfs device-probe window, the copyinstr at msdosfs_vfsops.c:281 faults, its return is ignored, and the memset at :282 runs with stale stack size - observed live as statfs f_mntfromname coming back EMPTY (stale size 0 zeroed the whole field) on exactly those 21 mounts. That is the un-initialized-length write executing in-kernel; on this kernel build the stale slot held the benign value 0, so the out-of-bounds variant (stale size > MNAMELEN makes MNAMELEN - size wrap to a huge memset length) was not manifested, only its precondition. Requires mount privilege (root self-race, or vfs.usermount=1; note the stock DFly 6.5 caps layer denied the unprivileged mount attempt outright even with vfs.usermount=1, an additional reachability constraint recorded here).
No comments yet.