sys_fhopen returns spurious success (fd 0) on VREG-without-VM-object invariant violation
| Field | Value |
|---|---|
| ID | DF-0002 |
| Status | new |
| Severity | Info |
| CVSS 3.1 | CVSS:3.1/AV:L/AC:H/PR:H/UI:N/S:U/C:N/I:N/A:N |
| CWE | CWE-388 7PK - Errors |
| File | sys/kern/vfs_syscalls.c |
| Lines | 4933-4938 |
| Area | kern |
| Confidence | certain |
| Discovered | 2026-06-29 |
| Reported | pending |
Summary
In sys_fhopen, when VOP_OPEN succeeds for a VREG vnode that unexpectedly
has no VM object, the code branches to the error-cleanup label without first
setting error. Because error is still 0 from the successful VOP_OPEN,
the syscall returns 0 (success) to the caller while the result fd
(sysmsg_result, initialized to 0 at the syscall entry) is never assigned a
real descriptor and the reserved slot is freed. This is a correctness bug,
reported as Info because fhopen is gated by SYSCAP_RESTRICTEDROOT
(root-only).
Root cause
sys/kern/vfs_syscalls.c:4933-4938:
if (vp->v_type == VREG && vp->v_object == NULL) {
kprintf("fhopen: regular file did not have VM object: %p\n", vp);
goto bad_drop; /* error is still 0 here from VOP_OPEN */
}
The bad_drop path (4986-4988) clears the reserved fd slot
(fsetfd(fdp, NULL, indx)) and fdrop()s fp, then falls into bad
(vput(vp)) and done (mount_drop(mp)), finally return (error); with
error == 0. sysmsg->sysmsg_result is only written on the true success path
at line 4981, so it retains the syscall-entry default of 0. The caller
therefore sees "success, fd 0" (its own stdin) even though no descriptor was
allocated for the fhopen result.
Threat model & preconditions
- Attacker position: root-equivalent only (
caps_priv_check_td(td, SYSCAP_RESTRICTEDROOT)atsys/kern/vfs_syscalls.c:4832). - Privileges gained or impact: none (no privilege-boundary crossing). Practical effect is limited to caller confusion (operating on fd 0 instead of a new fd).
- Required config or capabilities: root. Trigger requires a
VREGvnode whosev_objectis stillNULLafter a successfulVOP_OPEN, i.e. an internal FS invariant violation (e.g. a buggy/out-of-tree/fuse filesystem). - Reachability:
fhopen(2)on such a vnode. On normal filesystems the invariant holds and the path is unreachable.
Proof of concept
PoC source: findings/poc/DF-0002/fhopen_spur.c
Build & run
cc -o fhopen_spur findings/poc/DF-0002/fhopen_spur.c ./fhopen_spur /some/target # as root, on a filesystem that violates the invariant
Expected output
fhopen returned 0 (success) BUG: fd 0 is stdin, no real descriptor was allocated
(Fixed: fhopen failed as expected: Invalid argument.)
Impact
None security-wise โ root-only. Reported for correctness/hardening: an FS invariant violation should not present itself to userspace as a silent success returning a recycled descriptor.
Recommended fix
Set error before the goto.
--- a/sys/kern/vfs_syscalls.c
+++ b/sys/kern/vfs_syscalls.c
@@ -4933,6 +4933,7 @@
kprintf("fhopen: regular file did not "
"have VM object: %p\n",
vp);
+ error = EINVAL;
goto bad_drop;
}
References
sys/kern/vfs_syscalls.c:4832โSYSCAP_RESTRICTEDROOTgate onfhopen.sys/kern/vfs_syscalls.c:4981โ the only success-path assignment tosysmsg_result.fhopen(2)man page.
Timeline
- 2026-06-29 Discovered during automated file-by-file audit of
sys/kern/vfs_syscalls.c. - pending Reported to DragonFlyBSD security contact.