⬢ DragonFlyBSD Kernel Audit
DF-4604 / verdict.json
← back to finding ↓ download raw
{
  "finding_id": "DF-4604",
  "status": "inconclusive",
  "reproduced": 0,
  "impact": "none",
  "confidence": "likely",
  "verdict": "Source-proven use-after-free: radeon_gem_va_ioctl (radeon_gem.c:671-702) looks up bo_va under the BO reservation, but radeon_vm_bo_set_addr() drops that reservation internally (radeon_vm.c:538/:581), and the ioctl then passes bo_va to radeon_gem_va_update_vm() whose first dereferences (radeon_gem.c:565/569/573/585/590) are unprotected. A concurrent DRM_IOCTL_GEM_CLOSE (or close of a dupd fd) on the same file_priv executes radeon_gem_object_close (radeon_gem.c:193-205), acquires the now-free reservation, drops bo_va->ref_count 1->0 and frees bo_va via radeon_vm_bo_rmv()->kfree (radeon_vm.c:1137 immediate for the UNMAP case; radeon_vm.c:1065 via vm->freed for MAP - which thread As own update_vm then triggers before touching bo_va again at :590). Runtime verification on the audit QEMU guest is impossible: the guest has no radeon GPU (vgapci 0x1111:1234 Bochs, no /dev/dri, no drm in kernel config) and QEMU cannot emulate AMD GPUs. The trigger compiles cleanly on the guest and runs, exiting rc=3 with the documented SKIP. Verification requires a physical Cayman-or-newer radeon machine with accel working.",
  "exploit_chain": "On real hw: (1) unprivileged user opens render node/authenticated DRM fd; (2) thread A: GEM_CREATE -> VA_MAP, then arms thread B and issues VA_UNMAP; (3) thread B blocks in radeon_bo_reserve inside radeon_gem_object_close, is woken when set_addr internally unreserves, and kfree()s the ~128-byte bo_va; (4) thread As radeon_gem_va_update_vm() runs on the freed object: reads bo_va->bo and bo_va->vm from reclaimed memory, mutex_lock()s a reclaimed vm, and calls radeon_vm_bo_update() which walks vm->page_tables[] from reclaimed memory; (5) groom the 128-size slab with attacker-controlled kernel data to aim the GPU page-table update at chosen physical pages -> GPU-mediated arbitrary phys r/w -> uid=0. Step (5) not demonstrated (no hardware).",
  "evidence": "VERDICT.md contains the full path:line interleaving diagram; radeon_va_uaf.c is the self-contained trigger (phase-barrier design so GEM_CLOSE blocks on the reservation exactly when set_addr drops it); run.log shows the guest build+run with the honest SKIP; fix.diff re-looks-up bo_va after ttm_eu_reserve_buffers() re-establishes the reservation.",
  "kernel_refs": [
    "sys/dev/drm/radeon/radeon_gem.c:701-702",
    "sys/dev/drm/radeon/radeon_gem.c:565",
    "sys/dev/drm/radeon/radeon_gem.c:569",
    "sys/dev/drm/radeon/radeon_gem.c:585",
    "sys/dev/drm/radeon/radeon_gem.c:590-591",
    "sys/dev/drm/radeon/radeon_vm.c:538",
    "sys/dev/drm/radeon/radeon_gem.c:193-205",
    "sys/dev/drm/radeon/radeon_vm.c:1137",
    "sys/dev/drm/drm_gem.c:336-365"
  ],
  "poc_changes": "Seed sketch had Linux 2-arg _IOWR macros and required system drm headers; rewrote with BSD 3-arg _IOWR(d, nr, type) matching sys/dev/drm/include/uapi/drm/drm.h:796-801, vendored the radeon UAPI subset, added render-node fallback and the rc=3 SKIP convention, and redesigned the race harness (phase variable) so the GEM_CLOSE thread is already blocked on the BO reservation at the moment radeon_vm_bo_set_addr() drops it.",
  "attempts": 2,
  "guest_uname": "DragonFly dfbsd 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": 95,
  "guest_dirty": 0,
  "build_cmd": "./build.sh  (cc -O2 -Wall -pthread -o radeon_va_uaf radeon_va_uaf.c)",
  "run_cmd": "./run.sh /dev/dri/card0 100",
  "code_hash": "960aca6e820c6618917344115660a76a28e6cffc532ac314dcdac00b5308d32a",
  "notes": "Guest has no radeon GPU (QEMU Bochs VGA chip=0x11111234, no /dev/dri, no drm in X86_64_GENERIC) - hardware-gated, cannot execute. Distinct from known DF-4412 (vm->max_pde_used TOCTOU in radeon_vm_get_bos) and DF-4413 (NULL page tables after failed pt alloc): different freed object, different race partner (GEM_CLOSE), different primitive. Guest left clean (only /tmp/df4604 scratch, no kernel changes).",
  "recommended_fix": "Pass refcounted objects (fpriv->vm, rbo from the gobj lookup) into radeon_gem_va_update_vm() and re-lookup bo_va with radeon_vm_bo_find() after ttm_eu_reserve_buffers() has re-established the BO reservation; bail out cleanly if a racing GEM_CLOSE already removed it.",
  "fix_status": "not_testable",
  "fix_baseline_reproduced": 0,
  "fix_patched_reproduced": 0,
  "fix_verdict": "fix.diff authored against the read-only sys/ tree (never applied); validation impossible on this guest - no radeon hardware, so neither baseline nor patched behavior can be executed. The fix closes the window by construction: every bo_va dereference happens either under the BO reservation re-established by ttm_eu_reserve_buffers() or under vm->mutex, and the bo/vm pointers come from objects whose lifetime the ioctl pins.",
  "fix_evidence": "fix.diff in this directory; reasoning in VERDICT.md Recommended fix section."
}