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

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.

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)

No comments yet.

PoC verification

Evidence pack

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

--- 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.md
↓ download raw

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)

  1. Read sys/kern/vfs_vopops.c end-to-end (2227 lines) in pass 2.
  2. Extracted every struct vop_*_args field list from sys/sys/vfsops.h and every ap.a_* = assignment from each wrapper in sys/kern/vfs_vopops.c; scripted diff reports exactly three gaps (plus the shared vestigial a_head.a_reserved[4]): - vop_mountctl: ap.a_vp never assigned (vfs_vopops.c:1300-1308; struct at vfsops.h:433-443). The vp function parameter is used at vfs_vopops.c:1310 (VFS_MPLOCK(vp->v_mount)) but never stored in ap. - vop_markatime: ap.a_op never assigned (vfs_vopops.c:1327-1330; struct at vfsops.h:445-450). - vop_allocate: ap.a_op never assigned (vfs_vopops.c:1349-1353; struct at vfsops.h:452-458).
  3. 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_nch only inside #if 0 dead code.) - vop_markatime handlers: ufs_vnops.c:355, hammer_vnops.c:2125, hammer2_vnops.c:2401, vfs_default.c:137 β€” none read a_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_testable
per-fix-DF-2703

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