DF-3955 / verdict.json
{ "finding_id": "DF-3955", "status": "inconclusive", "reproduced": 0, "impact": "none", "confidence": "likely", "verdict": "Source-certain error-path defect, amdgpu twin DF-3891 (Low). kv_parse_power_table (kv_dpm.c:2673-2677) on mid-loop kzalloc failure: kfree(rdev->pm.dpm.ps) without NULLing, num_ps (assigned only at :2700) stays 0, ps_priv objects of states 0..i-1 leak. radeon_pm.c:1448 discards kv_dpm_init()s -ENOMEM, so the device persists with the dangling pointer; kv_dpm_fini (:2861-2871) at detach then kfrees rdev->pm.dpm.ps a second time (silent kernel heap corruption) and its num_ps==0 loop skips the leaked ps_privs. Harness replays the exact sequence with the OOM injected: dangling pointer + leaked ps_priv count = 2 + silent second free on DFly libc. Runtime-inconclusive: no AMD GPU, and the trigger itself needs a small kzalloc to fail at boot under memory pressure on KV hardware (rare) - hence Low.", "exploit_chain": "boot-time memory pressure on KV APU -> kzalloc(sizeof(struct kv_ps)) fails mid-parse -> dangling rdev->pm.dpm.ps -> driver detach/unload -> kv_dpm_fini double free -> silent kernel heap corruption. Principled exploitability of a heap double-free exists but the gating makes it negligible; primarily DoS/robustness.", "evidence": "run.log: state 2: kzalloc(kv_ps) failed -> kfree(dpm.ps); return -ENOMEM, dangling dpm_ps = 0x8004a16c0 ; leaked ps_priv count = 2 ; num_ps = 0, SECOND FREE COMPLETED SILENTLY (userland no-abort; kernel: silent heap corruption); VERDICT.md; fix.diff", "kernel_refs": [ "sys/dev/drm/radeon/kv_dpm.c:2675", "sys/dev/drm/radeon/kv_dpm.c:2700", "sys/dev/drm/radeon/kv_dpm.c:2861", "sys/dev/drm/radeon/kv_dpm.c:2868", "sys/dev/drm/radeon/radeon_pm.c:1448" ], "poc_changes": "No seed (pass-2 self-authored). One fix cycle: naive replay expected malloc(72) to fail on demand (it never does) - replaced with an explicit injected failure at state #2; also discovered DFly libc does NOT abort on double free, so the harness now reports the silent completion explicitly instead of implying a libc abort.", "attempts": 2, "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": 100, "guest_dirty": 0, "build_cmd": "cd /tmp/df395x && cc -O2 -Wall -o trigger_df3955 trigger_df3955.c", "run_cmd": "cd /tmp/df395x && ./trigger_df3955 && ./trigger_df3955 crash", "code_hash": "9b4c540bc9972b4672017f8b46dd892d42383e4ca04e8424ccc87a7b9fe5679c", "notes": "Distinct from pass-1 DF-1305/1306/1307. The radeon_pm.c:1448 error-swallow (shared enabler with DF-3953) turns a would-be-contained boot failure into a live dangling pointer.", "recommended_fix": "On the parse error path free the already-parsed ps_priv states, NULL rdev->pm.dpm.ps, and track num_ps incrementally (fix.diff); honor kv_dpm_init()s return value at radeon_pm.c:1448.", "fix_status": "not_testable", "fix_baseline_reproduced": 0, "fix_patched_reproduced": 0, "fix_verdict": "fix.diff authored against the read-only sys/ tree; not built - no AMD hardware on the guest. Removes both the double free and the leak by construction.", "fix_evidence": "findings/poc/DF-3955/fix.diff" } |