DF-4685 / verdict.json
{ "finding_id": "DF-4685", "status": "reproduced", "reproduced": 1, "impact": "panic", "confidence": "certain", "verdict": "Reproduced as a deterministic fatal trap on the guest: amdgpu_fence_slab_fini (amdgpu_fence.c:69-73) relies on rcu_barrier() to drain the ports call_rcu delay line (every fence free is call_rcu(&f->rcu, amdgpu_fence_free), amdgpu_fence.c:646-649, executed >=1s later by linux_rcu.c rcu_timer) before module text may be unmapped \u2014 but rcu_barrier() is a static-inline no-op (rcutree.h:30). The KLD replica (same module as DF-4684) enqueued a call_rcu whose callback lives in module text, called the ports rcu_barrier() (returned instantly), and kldunload(8) then completed and unmapped the text; ~1s later rcu_timer called the callback at its old address: Fatal trap 12: page fault while in kernel mode, fault virtual address = 0xffffffff831dd020, supervisor read instruction, page not present \u2014 guest down at db> (panic.txt). This is exactly what kldunload amdgpu after GPU fence activity does on real hardware. Root-triggered (module unload), so Low/DoS. Two earlier attempts wedged instead of panicking \u2014 root-caused to DFs linker not refcounting module dependencies (kern_linker.c:574 linker_file_add_dependancy never bumps dep->refs), which made kldunload recursively tear down drm.ko (auto-loaded via MODULE_DEPEND) whose headless exit blocks forever in a DRM wait_event (wchan lwe, linux/wait.h:133) \u2014 removing the MODULE_DEPEND made the panic reproduce deterministically; that linker refcount gap is a separate latent issue worth its own look.", "exploit_chain": "kldunload amdgpu (root) within ~1s of fence activity -> fence_driver_finis puts enqueue amdgpu_fence_free callbacks -> no-op rcu_barrier drains nothing -> module text unmapped -> rcu_timer executes elm->func into unmapped text -> fatal trap 12 (page-not-present instruction fetch at the old callback address) -> kernel panic / DoS", "evidence": "panic.txt (verbatim serial console: callback armed at 0xffffffff831dd020, Fatal trap 12 at exactly that address after df4684: unloading); run.fixed.log (patched kernel: real rcu_barrier blocks, dummy RCU callback executed ... still mapped prints BEFORE rcu_barrier() returned, kldunload completes, guest survives); VERDICT.md (full narrative + the linker-dependency side-discovery)", "kernel_refs": [ "sys/dev/drm/amd/amdgpu/amdgpu_fence.c:69-73", "sys/dev/drm/amd/amdgpu/amdgpu_fence.c:646-649", "sys/dev/drm/include/linux/rcutree.h:30", "sys/dev/drm/linux_rcu.c:56-78", "sys/dev/drm/linux_rcu.c:80-110" ], "poc_changes": "mode 2 of the DF-4684 KLD (shared module): call_rcu with an in-module callback + the ports rcu_barrier + operator kldunload; first two runs used MODULE_DEPEND(drm) which triggered the DF linker dep-teardown wedge \u2014 removed the dependency (manual kldload drm) and the panic reproduced deterministically", "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 panic); fix validated on rebuilt nativekernel #0: Tue Sep 22 20:34:56 UTC 2026", "runtime_sec": 2700, "guest_dirty": 1, "build_cmd": "cd /root/poc/DF-4684 && make -m /usr/share/mk KMODDIR=$PWD # df4684.ko, stock headers", "run_cmd": "kldload drm && kldload ./df4684.ko && sysctl kern.df4684_run=2 && kldunload df4684; sleep 4", "code_hash": "8644e7cfcde0e6f7e78a631b4bff42a0f3de90aaa545db7ffc59c4f98b96a47f", "notes": "Same class contract as DF-4684s pack (shared module). The fix (real rcu_barrier draining every per-CPU delay line) was compile- and runtime-validated on the guest: make -j6 nativekernel RC=0 under -Werror with all patches applied, installkernel + reboot, module rebuilt against the patched rcutree.h, mode 2 re-run -> barrier blocked until the callback fired, unload clean, guest survived indefinitely (run.fixed.log; the no-op on this port kprintf text is the modules static string \u2014 the ordering of the lines is the proof). /usr/src restored pristine (md5 rcutree.h=585d9abb..., linux_rcu.c=b452ca08...); guest reset with-src before return. Side-discovery for the record: kern_linker.c:574 linker_file_add_dependancy() never increments dep->refs, so unloading a module tears down (or over-drops) its auto-loaded dependencies \u2014 separate latent bug.", "recommended_fix": "implement rcu_barrier() in linux_rcu.c (drain all per-CPU delay lines) and make rcutree.h declare it instead of the no-op inline", "fix_status": "fixed", "fix_baseline_reproduced": 1, "fix_patched_reproduced": 0, "fix_verdict": "A/B on the guest: baseline kernel -> Fatal trap 12 at the unmapped callback (panic.txt); after applying fix.diff to the guest /usr/src, rebuilding (nativekernel RC=0 under -Werror), installing and rebooting, the same trigger drains: the real rcu_barrier blocks inside the sysctl until dummy RCU callback executed prints, then kldunload completes and the guest survives indefinitely (run.fixed.log, uptime confirmed). amdgpus slab_fini unload contract restored.", "fix_evidence": "panic.txt vs run.fixed.log; fix.diff (git-apply clean against sys/; touches rcutree.h + linux_rcu.c); build_kernel.log (nativekernel RC=0); post-validation md5 restore of rcutree.h/linux_rcu.c to pristine" } |