{
  "finding_id": "DF-4914",
  "status": "reproduced",
  "reproduced": 1,
  "impact": "panic",
  "confidence": "certain",
  "verdict": "Use-after-free + double-free: radeon_fbdev_init() publishes rfbdev into rdev->mode_info.rfbdev at radeon_fb.c:379 before any fallible step, and its error exits (fini:/free:) kfree(rfbdev) at :405 WITHOUT clearing the pointer. radeon_modeset_init() ignores the failure (radeon_display.c:1640), so the driver carries the dangling pointer until teardown; the production teardown radeon_modeset_fini -> radeon_fbdev_fini (radeon_display.c:1655) then walks the freed rfbdev in radeon_fbdev_destroy() and kfree()s it AGAIN at radeon_fb.c:415. Reproduced on the guest against the stock production radeon.ko: stage-2 run panics deterministically memory chunk ... is already free! with stack chunk_mark_free <- _kfree <- radeon_fbdev_fini+0x78; stage-3 run first reclaims the freed slab block with an ordinary same-size allocation (RECLAIMED, 0x41-filled) and the production teardown then chases the fully controlled fb->obj[0]=0x4141414141414141 into radeonfb_destroy_pinned_object, GPF at radeonfb_destroy_pinned_object+0x1c (movl -0x2d0(%rdi)). Also en route: radeon_fbdev_robj_is_fb() and radeon_fb_{add,remove}_connector() deref the same dangling pointer during normal driver operation. Because no radeon GPU exists in the QEMU guest, the graceful init-failure entry could not be driven live (oversized kcalloc wedges in M_WAITOK paging; num_crtc=0 succeeds via -EAGAIN->deferred), so stage 1 injects the byte-exact post-failure state (the statements of radeon_fb.c:374-405) and stages 2/3 drive the real teardown code from the loaded production module.",
  "exploit_chain": "local attacker on a system with a radeon GPU: (1) induce radeon_fbdev_init failure at driver load (memory pressure for drm_fb_helper_init/-ENOMEM, or VRAM exhaustion during fb_probe/radeonfb_create on small-VRAM cards) -> mode_info.rfbdev dangling for the whole driver lifetime (failure ignored at radeon_display.c:1640); (2) reclaim the freed ~1.2KB slab block with controlled kernel allocations; (3) at driver unload/reboot/hotplug, radeon_fbdev_fini walks attacker-controlled fields (fb->obj[0] -> radeonfb_destroy_pinned_object -> radeon_bo_reserve/kunmap/unpin on forged radeon_bo) and double-kfrees the block. Demonstrated through step 3s wild-pointer chase (stage 3); weaponization to uid=0 (forging radeon_bo/ttm objects) not pursued on the no-GPU guest.",
  "evidence": "panic-stage2.txt (double free: chunk_mark_free <- _kfree <- radeon_fbdev_fini+0x78 = radeon_fb.c:415), panic-stage3.txt (GPF at radeonfb_destroy_pinned_object+0x1c after slab reclaim, fb.obj[0]=0x4141414141414141), run.log (all stages + fixed-module check), console.log, fix_build.log (patched radeon.ko BUILD=0)",
  "kernel_refs": [
    "sys/dev/drm/radeon/radeon_fb.c:379",
    "sys/dev/drm/radeon/radeon_fb.c:402",
    "sys/dev/drm/radeon/radeon_fb.c:405",
    "sys/dev/drm/radeon/radeon_fb.c:415",
    "sys/dev/drm/radeon/radeon_fb.c:336",
    "sys/dev/drm/radeon/radeon_fb.c:427",
    "sys/dev/drm/radeon/radeon_display.c:1640",
    "sys/dev/drm/radeon/radeon_display.c:1655"
  ],
  "poc_changes": "Initial draft forced drm_fb_helper_init ENOMEM via mode_config.num_crtc=0x20000000; on this kernel the GFP_KERNEL kcalloc wedges the guest in M_WAITOK paging instead of failing (pagedaemon Continuous paging), and num_crtc=0 / disconnected-connector variants SUCCEED (-EAGAIN becomes deferred-setup success). Restructured to documented state injection of the exact failed-init state (radeon_fb.c:374-405) with real drm_fb_helper_prepare, driving the REAL production radeon_fbdev_fini from stock /boot/kernel/radeon.ko. Also fixed DFly DECLARE_MODULE usage (moduledata_t struct required; passing the handler function GPFs in module_register/strcmp at module registration).",
  "attempts": 5,
  "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": 1500,
  "guest_dirty": 1,
  "build_cmd": "cd /root/rfb && make (KLD rfbuaf.ko; stock kldload drm + kldload radeon)",
  "run_cmd": "kldload drm; kldload radeon; kldload ./rfbuaf.ko; sysctl hw.rfbuaf=1 (dangling print); sysctl hw.rfbuaf=2 (double-free panic); fresh boot + sysctl hw.rfbuaf=3 (reclaim+UAF panic)",
  "code_hash": "e1b8dac54845bfc0cd728dd1cca6d81a3a993410306252e92cd92f34d6947b21",
  "notes": "Panics are deterministic across runs. The double-free detector (memory chunk ... is already free) caught the second kfree; builds without that detector would corrupt the slab freelist instead. Fixed-module build (fix.diff) compiled and loaded OK on the guest.",
  "fix_status": "not_testable",
  "fix_baseline_reproduced": 1,
  "fix_patched_reproduced": 0,
  "fix_verdict": "fix.diff (rdev->mode_info.rfbdev = NULL at the free: label of radeon_fbdev_init) compiles cleanly into radeon.ko on the guests md5-identical /usr/src tree and the patched module loads (fix_build.log). A dynamic baseline-vs-patched PoC re-run is not possible on this guest: the graceful failure entry cannot be produced without a radeon GPU (allocators wedge or panic rather than return ENOMEM; num_crtc=0 succeeds via deferred-setup), and the PoC injects the post-failure state directly, bypassing the fixed statement by construction.",
  "fix_evidence": "fix.diff; fix_build.log (BUILD=0 for patched radeon.ko); run.log tail (FIXED-RADEON-LOADED + stage 1 executes); VERDICT.md fix-validation section.",
  "recommended_fix": "Clear rdev->mode_info.rfbdev (NULL) before kfree(rfbdev) on the radeon_fbdev_init error paths (single line at the free: label covers fini+free)."
}