dirfs_mount copyinstr fallback re-interprets a failed user data pointer as a kernel address β wild vkernel-kernel read / fatal trap from mount(2)
Summary
When copyinstr(data,...) fails (EFAULT on a bad user pointer), dirfs_mount retries with copystr(data,...) treating the same user-supplied value as a kernel pointer. The vkernel copystr (vkernel64/platform/copyio.c:251-263) is an unprotected byte loop: unmapped address -> fatal kernel-mode fault; a mapped one copies up to 1023 bytes of arbitrary vkernel-kernel memory into dm_path (bounded write, indirectly observable read primitive). No in-tree caller passes a kernel data pointer - the vkernel root mount passes data=NULL which takes the same crash path - so the fallback serves nobody. PROVEN on guest vkernel: mount('dirfs','/hostroot',0,(void*)0x1000) -> 'Fatal trap 12 ... Stopped at copystr+0x5 (movzbl (%rdi),%eax)' - the loop's first load of the attacker-chosen address. Fix validated: fallback removed -> clean EFAULT, vkernel alive. Requires mount privilege (vkernel root/fscap/usermount); multi-tenant vkernel = cross-tenant crash.
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-3222 Β· 11 files| File | Type | Description | Size | |
|---|---|---|---|---|
| run.log | β | 2.8 KB | view raw | |
| run.fixed.log | β | 2.8 KB | view raw | |
| run.sh | β | 157 B | view raw | |
| run-fixed.sh | β | 1.2 KB | view raw | |
| vkmount2.c | β | 536 B | view raw | |
| fix.diff | β | 514 B | view raw | |
| fix-build.log | β | 10.0 KB | view raw | |
| build.fixed-kernel.log | β | 10.0 KB | view raw | |
| VERDICT.md | β | 2.4 KB | β raw | |
| manifest.json | β | 775 B | view raw | |
| verdict.json | β | 3.1 KB | view raw |
DF-3222 VERDICT
status: reproduced / impact: panic (vkernel DoS from a mount-privileged user) / confidence: certain
Environment
Same harness as DF-3218: vkernel (VKDIRFS) built and run inside the QEMU
guest, minimal FFS root, fifo console. Guest uname in
../DF-3218/env.txt.
Chain
/bin/vkmount2 /hostroot inside the vkernel calls
mount("dirfs", "/hostroot", 0, (void *)0x1000).
domount (sys/kern/vfs_syscalls.c:416) passes uap->data through;
dirfs_mount copyinstr(0x1000) β EFAULT β fallback
copystr(0x1000, dm_path, MAXPATHLEN, &done) (dirfs_vfsops.c:129) treats
the user value as a kernel pointer; vkernel copystr (copyio.c:255-260)
loads ((const char *)0x1000)[0] β fatal kernel-mode page fault.
Observed
Baseline (run.log):
Fatal trap 12: page fault while in kernel mode current process = 2 (vkmount2) Stopped at 0x726b85: movzbl (%rdi),%eax
Symbolized: copystr+0x5 (nm: copystr at 0x726b80; objdump shows the
faulting instruction is the loop's first movzbl (%rdi),%eax load).
The vkernel panics to ddb; the vkernel OS is dead.
Fixed (run.fixed.log + fix.diff β fallback removed):
mount failed: Bad address # echo STILL-ALIVE STILL-ALIVE
copyinstr's EFAULT propagates as a clean mount(2) error; vkernel alive. fix_status: fixed.
Why Low severity
Requires mount privilege (vkernel root, dirfs fscap holder, or vfs.usermount=1 + mountpoint ownership β the caps gate is sys/kern/vfs_syscalls.c:152-172). For the single-tenant vkernel the crasher owns the process anyway; the security-relevant variant is the capability-granted multi-tenant vkernel (tenant crashes the kernel for all users), or the mapped-address variant (bounded arbitrary vkernel-kernel read into dm_path, only indirectly observable). No memory write outside the bounded dm_path destination.
Note
The vkernel root-mount path (vfs_conf.c:457 VFS_MOUNT(mp, NULL, NULL, ...))
passes data=NULL, so even the "kernel mount" case the comment claims to
serve would take this same crash path on a dirfs root mount β the fallback
serves no in-tree caller.
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
fixedFallback removed, vkernel relinked, identical PoC re-run: copyinstr EFAULT returns as 'mount failed: Bad address'; vkernel alive and shell responsive (run.fixed.log).
['run.fixed.log', 'fix-build.log', 'build.fixed-kernel.log', 'fix.diff']
Confirmed kernel references
Detail
Exploit chain
mount-privileged user in vkernel calls mount("dirfs", dir, 0, (void*)0x1000) -> domount passes uap->data -> copyinstr(0x1000) EFAULT -> copystr(0x1000,...) as kernel pointer -> first byte load faults -> Fatal trap 12, whole-vkernel panic. Mapped-address variant: bounded read of up to 1023 bytes of vkernel-kernel memory into dm_path (indirectly observable only); no out-of-bounds write.
Evidence (decisive lines)
["run.log: 'Fatal trap 12 ... current process = 2 (vkmount2)', 'Stopped at 0x726b85: movzbl (%rdi),%eax'", 'VERDICT.md: 0x726b85 symbolized as copystr+0x5 (vkernel copyio.c:255 loop head)', "run.fixed.log: 'mount failed: Bad address' + 'STILL-ALIVE' β vkernel alive with fix.diff"]
PoC changes
vkmount2.c written fresh (mount(2) with data=(void*)0x1000); harness identical to DF-3218's (custom vkernel build after bit-rot include fixes, FFS root image, vkinit wrapper, fifo console).
Verified recommended fix
Delete the copystr fallback; fail the mount on copyinstr error (see fix.diff). If a kernel-internal mount API ever needs dirfs, it should pass the path through a UIO_SYSSPACE-aware interface, not by guessing pointer provenance.
Verdict
dirfs_mount's copyinstr fallback re-interprets a failed user data pointer as a kernel address and copystr()s from it (dirfs_vfsops.c:129); the vkernel copystr has no fault handling, so a mount(2) with data=(void*)0x1008 crashes the vkernel in kernel mode. Verified on the guest: Fatal trap 12 in copystr+0x5 (movzbl (%rdi),%eax), current process vkmount2. Requires mount privilege (vkernel root / dirfs fscap / vfs.usermount=1), hence Low. fix.diff (remove the fallback) validated: mount fails cleanly with EFAULT (Bad address) and the vkernel survives.
No comments yet.