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

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)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-3131 Β· 10 files
FileTypeDescriptionSize
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.

VERDICT.md
↓ download raw

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_testable
↓ fix.diffper-fix-DF-3131

Confirmed 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).