⬢ DragonFlyBSD Kernel Audit
DF-3134 / verdict.json
← back to finding ↓ download raw
{
  "finding_id": "DF-3134",
  "status": "reproduced",
  "reproduced": 1,
  "impact": "dos",
  "confidence": "likely",
  "verdict": "REPRODUCED as a deterministic orchestrated interleaving (dirfs is vkernel-only; the same interleaving occurs stochastically on SMP vkernels because nothing synchronizes dn_fd). dirfs_node_setpassive(dmp,dnp,0) (dirfs_subr.c:848-867) check-then-closes dn_fd (refcnt==2 check at :849, close at :856) while dirfs_findfd (:470-496) walks reading dn_fd and takes its node reference only at :486 \u2014 after the walk \u2014 and consumers then use pathnp->dn_fd after the return (alloc_file subr:194/:202, getattr vnops:391/:393, readlink vnops:1329, nremove unlinkat vnops:909, nmkdir vnops:1051-1058, open_helper subr:602, strategy pread/pwrite vnops:819-822). Harness (verbatim branch conditions, real descriptors, step-barrier orchestration): consumer captures fd=4; setpassive closes it; a concurrent open recycles fd 4 for an unrelated decoy directory; the consumers openat(stale fd,sentinel) READS THE DECOYs file and its O_CREAT CREATES a file inside the decoy directory \u2014 wrong-file metadata + write-side operations bounded only by the vkernels host credentials. Without the recycler the consumer gets EBADF (use-after-close; in dirfs_alloc_file that path is additionally the DF-0856 node leak). FIXED variant (dm_token held across findfd..dropfd and across setpassives check+close): correct-file access, no EBADF. Sibling of DF-3060 (unlocked dm_fdlist walk \u2014 list UAF); this is the fd-lifetime race with the recycled-descriptor wrong-file primitive.",
  "exploit_chain": "SMP vkernel, fd-cache eviction pressure (dirfs_fd_limit default 100; >100 open files churns setpassive(0) closes) + concurrent path operations on a subtree from another cpu -> T1 findfd walks/selects ancestor fd; T2 setpassive checks refcnt==2 (T1 not yet refd) and close()s dn_fd; any intervening open (the vkernel opens host files constantly) recycles the fd number; T1 refs, returns, consumer openat/unlinkat/fstatat/mkdirat/pread/pwrite on the recycled descriptor -> metadata/data/create/unlink operations against the WRONG host directory/file",
  "evidence": "harness.c (verbatim findfd walk condition :470-481 and setpassive close branch :848-867, real openat/fstatat/O_CREAT/close, three threads with step barriers); run.log \u2014 consumer used fd=4; setpassive closed it; recycler got fd=4, BUG 1b: openat(stale fd) read the DECOY directorys file, BUG 1c: O_CREAT created a file in the DECOY directory, BUG 2: EBADF use-after-close, FIXED lines; exit code 2; 3/3 runs identical MD5; fix.diff applies cleanly (git apply --check RC=0 local + guest /usr/src)",
  "kernel_refs": [
    "sys/vfs/dirfs/dirfs_subr.c:470",
    "sys/vfs/dirfs/dirfs_subr.c:486",
    "sys/vfs/dirfs/dirfs_subr.c:483",
    "sys/vfs/dirfs/dirfs_subr.c:848",
    "sys/vfs/dirfs/dirfs_subr.c:849",
    "sys/vfs/dirfs/dirfs_subr.c:856",
    "sys/vfs/dirfs/dirfs_subr.c:867",
    "sys/vfs/dirfs/dirfs_subr.c:194",
    "sys/vfs/dirfs/dirfs_vnops.c:391",
    "sys/vfs/dirfs/dirfs_vnops.c:909",
    "sys/vfs/dirfs/dirfs_vnops.c:819",
    "sys/vfs/dirfs/dirfs_vfsops.c:77"
  ],
  "poc_changes": "Harness written fresh (no seed). Iterations: (1) -lpthread missing on DragonFlys cc; (2) variant-2 deadlock (no recycler to publish the step) \u2014 setpassive publishes step 3 directly when no recycler is present; (3) FIXED-variant ordering made deterministic with a t1_locked barrier; (4) exit-code latch counters reset by later variants \u2014 latched per-variant globals.",
  "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",
  "runtime_sec": 15,
  "guest_dirty": 0,
  "build_cmd": "cd poc/DF-3134 && sh build.sh",
  "run_cmd": "cd poc/DF-3134 && sh run.sh",
  "code_hash": "41111a5cdc2be384c0fd02909b480d1e0105cc18b19a0c63b4d362e76d9aec97",
  "notes": "Race window in the real code is this exact instruction sequence; the harness proves the interleaving is possible and its consequences, not a stochastic guest hit (would need a multi-cpu vkernel under eviction pressure). Confidence likely reflects that. Distinct from DF-3060 (fdlist walk UAF/corruption in nresolve): different sites, different primitive (recycled-fd wrong-file vs list corruption). The dm_token fix is reentrant per-thread so nested acquisitions (nremoves setpassive under findfds token; node_free->setpassive under dropfd) are safe.",
  "recommended_fix": "Serialize dn_fd lifetime: hold dmp->dm_token from dirfs_findfds selection until dirfs_dropfd, and across dirfs_node_setpassives check-then-close section (fix.diff); full passive-cache lockdown belongs with DF-3060.",
  "fix_status": "not_testable",
  "fix_kernel_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",
  "fix_baseline_reproduced": 1,
  "fix_patched_reproduced": 1,
  "fix_verdict": "fix.diff (dm_token held across findfd..dropfd for non-NULL node args \u2014 exactly the findfd callers; both early exits release it; plus the same token across setpassives check+close) applies cleanly (git apply --check RC=0 on the local sys/ tree and guest /usr/src). Compile-neutral: identical first error vs baseline in the vkernel64 ad-hoc env (pre-existing errno.h breakage) \u2014 fix_base_cc.log vs fix_patched_cc.log. Behavior validated by the harness FIXED variant (correct-file sentinel read, creation in the intended directory, no EBADF). Live boot validation not_testable: dirfs is vkernel-only and not compiled into the guest host kernel. The dm_token serialization addresses the fd-lifetime race scoped here; the unlocked nresolve fdlist walk remains DF-3060s scope.",
  "fix_evidence": "fix.diff; fix_base_cc.log vs fix_patched_cc.log (identical first error); harness FIXED variant output in run.log (variant 3 lines)"
}