⬢ DragonFlyBSD Kernel Audit
DF-4510 / verdict.json
← back to finding ↓ download raw
{
  "finding_id": "DF-4510",
  "status": "inconclusive",
  "reproduced": 0,
  "impact": "none",
  "confidence": "likely",
  "verdict": "Race-certain by construction, untestable on this guest. drm_syncobj_fence_get() (drm_syncobj.h:120-130) loads syncobj->fence and takes a reference via dma_fence_get_rcu_safe() (dma-fence.h:189-199) which on this port is a bare load + unconditional kref_get, under rcu_read_lock() that is only preempt_disable() (rcupdate.h:46-56). drm_syncobj_replace_fence() (drm_syncobj.c:170-196) swaps the pointer under syncobj->lock and then dma_fence_put()s the old fence OUTSIDE the lock (:195); for a syncobj holding only a SIGNAL-created stub fence that put is the last reference and frees synchronously via dma_fence_release->kfree (linux_fence.c:43-51; stub ops have no ->release). A concurrent waiter on another CPU that loaded the pointer before the swap executes kref_get on freed memory (UAF write) and then uses the freed fence \u2014 dma_fence_is_signaled dispatches fence->ops and dma_fence_add_callback writes its cb_list \u2014 a heap UAF (read+write) type-confusable through slab grooming; all syncobj ioctls are DRM_UNLOCKED|DRM_RENDER_ALLOW and i915/amdgpu set DRIVER_SYNCOBJ, so this is unprivileged on real desktop hardware. Could not be exercised: the guest has no DRM device at all (only QEMU virtio-gpu hardware, unsupported by this port; no /dev/dri \u2014 run.log), so the racing ioctls cannot be issued; trigger program builds clean on the guest (build.log) and runs unmodified on any i915/amdgpu box with 2+ cores. Narrow load->inc window makes practical exploitation likely rather than certain, but the window is attacker-repeatable indefinitely at ioctl speed. Same no-op-RCU family as DF-4329/DF-1881/DF-4506, distinct file and object; unrelated to the known DF-1666 fd leak on this file.",
  "exploit_chain": "unpriv render-node fd -> SYNCOBJ_CREATE(SIGNALED) once -> thread A: SYNCOBJ_SIGNAL loop (each call frees the previous stub fence via replace_fences unlocked put) while thread B: SYNCOBJ_WAIT(0) loop (fence_get load+inc) -> B interleaves load/kfree/inc -> kref_get on freed kmalloc slot + use of freed dma_fence -> groom the freed bucket with controlled data -> fake dma_fence ops pointer -> dma_fence_is_signaled/fence_cb list write primitives -> code exec on this unhardened target",
  "evidence": "VERDICT.md (line-accurate race proof + exploitation sketch); syncobj_race.c + run.log (builds clean; device-absent diagnosis on guest); fix.diff (git-apply clean; two halves: kref_get_unless_zero+retry and kfree_rcu deferred fence free)",
  "kernel_refs": [
    "sys/dev/drm/drm_syncobj.c:170-216",
    "sys/dev/drm/drm_syncobj.c:195",
    "sys/dev/drm/drm_syncobj.c:242",
    "sys/dev/drm/drm_syncobj.c:715",
    "sys/dev/drm/include/drm/drm_syncobj.h:120-130",
    "sys/dev/drm/include/linux/dma-fence.h:97-110",
    "sys/dev/drm/include/linux/dma-fence.h:189-199",
    "sys/dev/drm/include/linux/rcupdate.h:46-56",
    "sys/dev/drm/linux_fence.c:43-51",
    "sys/dev/drm/drm_ioctl.c:685-698"
  ],
  "poc_changes": "authored fresh: self-contained uapi structs + ioctl numbers taken from sys/dev/drm/include/uapi/drm/drm.h (0xBF/0xC3/0xC5), two racing pthreads, device-absent diagnosis path",
  "attempts": 1,
  "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": "cc -O2 -pthread -o /tmp/syncobj_race /root/poc/DF-4510/syncobj_race.c",
  "run_cmd": "/tmp/syncobj_race 5",
  "code_hash": "ccbb9e85f3d5446fb315e9542d4025f07bd7bebbfd23a2370fbb5fafb267e3b4",
  "fix_status": "not_testable",
  "fix_baseline_reproduced": 0,
  "fix_patched_reproduced": 0,
  "fix_verdict": "fix.diff (kref_get_unless_zero + retry in dma_fence_get_rcu/_rcu_safe, deferred kfree_rcu(fence, rcu) in dma_fence_release/dma_fence_free \u2014 the ports __kfree_rcu delay-line gives >=1s grace, ample for the few-instruction window) is git-apply clean against sys/; baseline/patched comparison impossible on the guest because no DRM device exists to run the trigger against a patched kernel.",
  "fix_evidence": "git apply --check findings/poc/DF-4510/fix.diff -> OK; kref_get_unless_zero verified present in sys/dev/drm/include/linux/kref.h:85; struct dma_fence.rcu member verified in sys/dev/drm/include/linux/dma-fence.h:45; __kfree_rcu delay-line verified in sys/dev/drm/linux_rcu.c:80-110,162-184",
  "notes": "Cross-references (family, not duplicates): DF-4329 (i915_request RCU no-op), DF-1881 (reservation dma_fence_get_rcu unconditionally kref_get), DF-4506 (linux_idr grow kfree), DF-4362 (fd shim leak \u2014 different bug on this file). Known and NOT re-reported on this file: DF-1666 fd leak via drm_syncobj_get_fd -ENOSYS-after-get_unused_fd_flags and via export_sync_files no-op put_unused_fd (both instances re-confirmed but covered). Also killed for this file: idr usage is fully serialized under file_private->syncobj_table_lock (find/alloc/remove all take it) so the DF-4506 idr_grow race does NOT apply here; drm_syncobj_fence_get_or_add_callbacks WARN_ON overwrite leaks one fence ref only in an equally-tight corner (upstream-inherited, folded into notes); no timeline/pt/transfer code exists in this port of drm_syncobj.c.",
  "recommended_fix": "dma_fence_get_rcu(_safe): kref_get_unless_zero + retry loop, and free fences via kfree_rcu (dma_fence_release default path and dma_fence_free) so the ports RCU delay-line covers lockless readers"
}