vop_mountctl/vop_markatime/vop_allocate dispatch with partially-uninitialized stack argument structs (a_vp pointer never set)
| Field | Value |
|---|---|
| ID | DF-2703 |
| Status | new |
| Severity | Low |
| CVSS 3.1 | CVSS:3.1/AV:L/AC:H/PR:H/UI:N/S:U/C:N/I:N/A:L |
| CWE | CWE-457 Use of Uninitialized Variable |
| File | sys/kern/vfs_vopops.c |
| Lines | 1300-1310 (markatime :1327, allocate :1349) |
| Area | kern |
| Confidence | certain |
| Discovered | 2026-08-30 |
| Pass | 2 (GLM 5.3 second pass) |
| Bucket | base:kern |
| Reported | pending |
| Known CVE | none |
| CVE match | novel |
Summary
vop_mountctl() builds struct vop_mountctl_args on the kernel stack but never assigns ap.a_vp although the struct declares it β an uninitialized kernel-stack struct vnode* is handed to every filesystem's mountctl handler. vop_markatime() and vop_allocate() likewise never set the vestigial a_op int. Scripted struct-vs-assignment diff confirms these are the only incomplete wrappers in the file. Latent today (all in-tree handlers ignore those fields β verified handler-by-handler); the first future/out-of-tree handler that trusts ap->a_vp gets a wild-pointer dereference with attacker-influenced stack contents.
Recommended fix
Initialize the three fields (or delete the dead a_op members):
ap.a_vp = vp; in vop_mountctl; ap.a_op = 0; in vop_markatime and
vop_allocate.
Timeline
- 2026-08-30 Discovered during pass-2 audit of vfs_vopops.c (GLM 5.3).
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-2703 Β· 4 files| File | Type | Description | Size | |
|---|---|---|---|---|
| README.md | β | 3.0 KB | β raw | |
| VERDICT.md | β | 2.4 KB | β raw | |
| verdict.json | β | 2.1 KB | view raw | |
| manifest.json | β | 801 B | view raw |
DF-2703 β vop_* dispatch wrappers pass partially-uninitialized stack argument structures
File: sys/kern/vfs_vopops.c (pass 2, GLM 5.3) Severity: Low (hardening / latent) β Confidence: certain (by inspection + scripted struct/assignment diff)
What
Three VOP dispatch wrappers in sys/kern/vfs_vopops.c build the struct vop_*_args
on the kernel stack and forget to initialize every field of the ops structure:
| wrapper | missing field | struct definition | type |
|---|---|---|---|
vop_mountctl() (sys/kern/vfs_vopops.c:1292-1315, assignments at 1300-1308) |
ap.a_vp |
sys/sys/vfsops.h:433-443 | struct vnode * β uninitialized kernel-stack pointer |
vop_markatime() (sys/kern/vfs_vopops.c:1320-1337) |
ap.a_op |
sys/sys/vfsops.h:445-450 | int |
vop_allocate() (sys/kern/vfs_vopops.c:1342-1360) |
ap.a_op |
sys/sys/vfsops.h:452-458 | int |
The wrapper takes vp as a parameter and uses it only for VFS_MPLOCK(vp->v_mount)
(vfs_vopops.c:1310) β it never stores it into ap.a_vp. Every other wrapper in the
file initializes 100% of its args-struct fields (verified by scripted diff of
ap.a_* assignments vs. struct definitions in sys/sys/vfsops.h β only these three
fail, apart from the intentionally vestigial a_head.a_reserved[4]).
Why it matters
The uninitialized bytes are kernel stack garbage passed by pointer into every
filesystem's vop_mountctl / vop_markatime / vop_allocate handler. All handlers
in the current tree derive what they need from ap->a_head.a_ops->head.vv_mount
(e.g. vop_stdmountctl sys/kern/vfs_default.c:1301-1307, hammer2_vop_mountctl
sys/vfs/hammer2/hammer2_vnops.c:2278-2299, tmpfs_mountctl:1879-1909,
journal_mountctl sys/kern/vfs_jops.c:154-231) and never read a_vp/a_op, so the
bug is latent today: no current dereference of the garbage pointer. It is a
trap for any future handler (or out-of-tree/filesystem-module handler) that does
the natural thing (ap->a_vp), which would be a wild-pointer dereference with
attacker-influenced stack contents. Reachability of vop_mountctl is root-gated
(sys_mountctl requires SYSCAP_RESTRICTEDROOT, sys/kern/vfs_syscalls.c:1277-1283);
markatime/allocate are reachable by ordinary users.
Recommended fix
--- a/sys/kern/vfs_vopops.c
+++ b/sys/kern/vfs_vopops.c
@@ vop_mountctl(...)
ap.a_head.a_desc = &vop_mountctl_desc;
ap.a_head.a_ops = ops;
+ ap.a_vp = vp;
ap.a_op = op;
@@ vop_markatime(...)
ap.a_head.a_desc = &vop_markatime_desc;
ap.a_head.a_ops = ops;
+ ap.a_op = 0;
ap.a_vp = vp;
@@ vop_allocate(...)
ap.a_head.a_desc = &vop_allocate_desc;
ap.a_head.a_ops = ops;
+ ap.a_op = 0;
ap.a_vp = vp;
(Better: delete the dead a_op fields from struct vop_markatime_args /
struct vop_allocate_args entirely β no wrapper parameter and no handler reads them.)
Reproduction status
Not executed (Phase V skipped): Low/Info hardening finding, latent β no observable misbehavior exists on a stock kernel because no handler reads the uninitialized fields. Verified by scripted marshalling audit + source survey of all handlers.
VERDICT β DF-2703
status: untested (Phase V deliberately skipped: Low-severity latent hardening finding β nothing observable to reproduce on a stock kernel; no handler reads the uninitialized fields).
Method (static proof)
- Read sys/kern/vfs_vopops.c end-to-end (2227 lines) in pass 2.
- Extracted every
struct vop_*_argsfield list from sys/sys/vfsops.h and everyap.a_* =assignment from each wrapper in sys/kern/vfs_vopops.c; scripted diff reports exactly three gaps (plus the shared vestigiala_head.a_reserved[4]): -vop_mountctl:ap.a_vpnever assigned (vfs_vopops.c:1300-1308; struct at vfsops.h:433-443). Thevpfunction parameter is used at vfs_vopops.c:1310 (VFS_MPLOCK(vp->v_mount)) but never stored inap. -vop_markatime:ap.a_opnever assigned (vfs_vopops.c:1327-1330; struct at vfsops.h:445-450). -vop_allocate:ap.a_opnever assigned (vfs_vopops.c:1349-1353; struct at vfsops.h:452-458). - Surveyed every consumer of these three ops in the tree:
- vop_mountctl handlers: vfs_default.c:1301 (vop_stdmountctl β uses
a_ops->head.vv_mount, a_op, a_ctllen, a_ctl, a_fp, a_buf, a_buflen, a_res),
hammer2_vnops.c:2278, hammer_vnops.c:2532, tmpfs_vnops.c:1879,
autofs_vnops.c:434, fuse_vnops.c:1806, dirfs_vnops.c:1418,
null_vnops.c:206, vfs_jops.c:154 (journal_mountctl). None read a_vp.
(null_vnops.c:224-229 references
a_nchonly inside#if 0dead code.) - vop_markatime handlers: ufs_vnops.c:355, hammer_vnops.c:2125, hammer2_vnops.c:2401, vfs_default.c:137 β none reada_op. - vop_allocate handlers: vfs_default.c:1334 (vop_stdallocate) β reads a_vp/a_offset/a_len only.
Conclusion
Certain, latent, Low. Uninitialized kernel-stack pointer (a_vp) is handed to
every mountctl dispatch; today's handlers all ignore it, so there is no runtime
symptom to reproduce. One new handler that trusts ap->a_vp turns this into a
wild-pointer dereference with attacker-influenced stack contents (mountctl itself
is root-gated, sys/kern/vfs_syscalls.c:1277-1283; markatime/allocate are
user-reachable but expose only a garbage int). Fix is a 3-line initialization
(or deleting the dead a_op fields). No fix-kernel build required: no behavioral
change to validate beyond compilation.
Cross-checked: union vop_args_union referencing these structs is #if 0-dead
(vfsops.h:680-745), so no alternate consumer exists.
Fix verification
not_testableConfirmed kernel references
Detail
Evidence (decisive lines)
['findings/poc/DF-2703/VERDICT.md (scripted marshalling diff + full handler survey)', 'sys/kern/vfs_vopops.c:1292-1315 vop_mountctl assignments omit ap.a_vp', 'sys/sys/vfsops.h:433-443 struct vop_mountctl_args declares a_vp as last field', 'sys/kern/vfs_syscalls.c:1277-1283 mountctl is SYSCAP_RESTRICTEDROOT-gated']
PoC changes
no PoC built: latent defect, no observable behavior
Verified recommended fix
Initialize ap.a_vp = vp in vop_mountctl and ap.a_op = 0 in vop_markatime/vop_allocate (or remove the unused a_op fields from the structs in sys/sys/vfsops.h).
Verdict
Latent marshalling defect, proven by scripted struct-vs-assignment audit: vop_mountctl (sys/kern/vfs_vopops.c:1300-1308) never sets ap.a_vp (an uninitialized kernel-stack struct vnode*), and vop_markatime/vop_allocate (vfs_vopops.c:1327-1330, 1349-1353) never set the vestigial a_op int. Every current handler derives state from a_head.a_ops->head.vv_mount and ignores the uninitialized fields, so there is no runtime symptom on a stock kernel; any future handler reading ap->a_vp would dereference attacker-influenced stack garbage. Phase V skipped per contract (Low hardening; nothing observable to reproduce).
No comments yet.