โฌข DragonFlyBSD Kernel Audit
DF-2774 / verdict.json
โ† back to finding โ†“ download raw
{
  "finding_id": "DF-2774",
  "status": "reproduced",
  "reproduced": 1,
  "impact": "none",
  "confidence": "certain",
  "verdict": "Reproduced deterministically on the stock INVARIANTS kernel with vfs.quota_enabled=1: vp->v_pfsmp is a raw, never-cleared pointer to the nullfs struct mount (vfs_cache.c:1386-1389); after unmount, mountlist_exists() (vfs_mount.c:692-708) validates it by pointer-compare only, so a fresh mount reusing the freed M_MOUNT chunk makes the guard pass and VFS accounting/enforcement operates on the wrong, unrelated mount โ€” an append through the unquota'd lower tmpfs failed with EDQUOT under an unrelated mount's 10-byte limit, and an O_TRUNC through the lower path drove that mount's usage to 2^64-8192. A concurrent-unmount variant is a struct mount UAF (stale mnt_op read; vfs_stdaccount spin_lock + ac_bytes += delta on freed memory), bounded in practice by M_MOUNT zone type-stability. Default configs (quota_enabled=0) reduce VFS_ACCOUNT to a benign NULL check. fix.diff (mountlist_hold + per-mount cookie + held vq_vptomp) built as kernel #1 in-guest and eliminates both manifestations while preserving alias accounting; no uid0 path (logic-corruption class, not a control-flow primitive).",
  "exploit_chain": "unpriv user file activity on a nullfs-aliased lower fs -> admin unmounts nullfs (v_pfsmp goes stale) -> next mount reuses the freed struct mount address -> mountlist_exists(stale)==1 -> vq_vptomp returns wrong mount -> vq_write_ok/VFS_ACCOUNT enforce/charge the wrong mount (demonstrated: EDQUOT on unquota'd tmpfs + negative usage). Race variant: unmount+free lands between exists() and use -> vfs_stdaccount writes freed mount (limited by zone type-stability).",
  "evidence": [
    "findings/poc/DF-2774/run.log โ€” decisive baseline: 'dd: stdout: Disc quota exceeded' (TEST1) and 'total: 18446744073709543424, limit = 10' (TEST2)",
    "findings/poc/DF-2774/run.fix.log โ€” patched kernel #1: TEST1_RC=0, alias2 stays 'total: 0, limit = 10'",
    "findings/poc/DF-2774/fix.diff โ€” 7-file git-apply-able fix (mountlist_hold, mnt_cookie, v_pfsmp_cookie, held vq_vptomp)",
    "findings/poc/DF-2774/VERDICT.md โ€” full narrative with path:line for every claim"
  ],
  "kernel_refs": [
    "sys/kern/vfs_mount.c:692-708",
    "sys/kern/vfs_mount.c:399-405",
    "sys/kern/vfs_cache.c:1386-1389",
    "sys/kern/vfs_quota.c:421-433",
    "sys/kern/vfs_quota.c:149-172",
    "sys/kern/vfs_vnops.c:325-326",
    "sys/kern/vfs_vopops.c:487-496",
    "sys/kern/vfs_syscalls.c:1040-1117"
  ],
  "poc_changes": "Seed sketch rewritten entirely: DFly dd rejects the '2>/dev/null'-style operand placement used in the draft and vquota syntax is 'limit/show' not 'set limit/get usage'; crucially the victim file must be created/resolved THROUGH the nullfs path first (an already-resolved ncp never re-runs _cache_setvp, so v_pfsmp is never set if the lower path resolves it first) โ€” first two runs failed for those reasons.",
  "attempts": 4,
  "guest_uname": "DragonFly dfbsd 6.5-DEVELOPMENT DragonFly 6.5-DEVELOPMENT #0: Thu Jul 2 06:02:54 UTC 2026 root@dfbsd:/usr/obj/usr/src/sys/X86_64_GENERIC x86_64 (baseline) / #1: Tue Sep 1 02:11:26 UTC 2026 (patched)",
  "runtime_sec": 1140,
  "guest_dirty": 1,
  "build_cmd": "cd /usr/src && patch -p1 < /root/fix.diff && make -j6 nativekernel KERNCONF=X86_64_GENERIC && make installkernel KERNCONF=X86_64_GENERIC",
  "run_cmd": "sh /root/poc.sh  (as root; file activity via 'su -m qa', vfs.quota_enabled=1 in /boot/loader.conf)",
  "code_hash": "ac6712ff03ac2b3179fe1d256686c62e88428f09e50b830022e41d3b0dc1eccc",
  "notes": "impact field: no memory-safety manifestation demonstrated (enum has no 'logic-corruption' class); demonstrated effects are cross-mount quota enforcement (EDQUOT on unquota'd fs) and accounting corruption (2^64-8192 usage). UAF-write variant is race-bound and bounded by M_MOUNT zone type-stability + DFly slab retaining freed-chunk contents. vfs.usermount is irrelevant here: the unprivileged side only opens/truncates files; mount/unmount is privileged admin activity that arms the stale pointer persistently.",
  "recommended_fix": "Replace the refcount-free mountlist_exists() probe in vq_vptomp() with mountlist_hold() (a real-hold lookup) plus a per-mount generation cookie (mnt_cookie/v_pfsmp_cookie) so a stale v_pfsmp can neither be used after free nor validate against a different mount that reused the address; callers mount_drop() after accounting.",
  "fix_status": "fixed",
  "fix_kernel_uname": "DragonFly dfbsd 6.5-DEVELOPMENT DragonFly 6.5-DEVELOPMENT #1: Tue Sep  1 02:11:26 UTC 2026 root@dfbsd:/usr/obj/usr/src/sys/X86_64_GENERIC x86_64",
  "fix_baseline_reproduced": 1,
  "fix_patched_reproduced": 0,
  "fix_verdict": "Built fix.diff in-guest (make -j6 nativekernel, 362s), installed, rebooted into kernel #1, re-ran the identical poc.sh: baseline EDQUOT denial and negative-usage charge are gone (TEST1_RC=0, alias2 total: 0) while legitimate alias-path accounting still charges the nullfs mount 4096. Behavior change is exactly the bug disappearing.",
  "fix_evidence": [
    "findings/poc/DF-2774/run.fix.log",
    "findings/poc/DF-2774/build.log (head of in-guest build.log)",
    "findings/poc/DF-2774/fix.diff"
  ]
}