DF-4741 / verdict.json
{ "finding_id": "DF-4741", "status": "reproduced", "reproduced": 1, "impact": "panic", "confidence": "certain", "verdict": "Reproduced on a fresh-reset guest: drm_sched_fini (sched_main.c:652-656) only calls kthread_stop \u2014 it does not cancel the armed work_tdr delayed work and does not drain ring_mirror_list. The harness (test 2 of df4740.c) left a hung job on the list (work_tdr armed at sched_main.c:246), called drm_sched_fini, then kfree()d the scheduler exactly as amdgpu_fence_driver_fini frees struct amdgpu_ring after drm_sched_fini(&ring->sched) (amdgpu_fence.c:495-519); the block was reclaimed by a zeroed allocation at the identical address. ~2s later the armed callout fired on the freed object and softclocks verifier tripped: panic: _callout ... verifier ... (expected ...) ... softclock_handler, guest down at db> (panic.txt). Absent the verifier trip, the queued drm_sched_job_timedout work itself executes on freed memory (lockmgr on freed job_list_lock, ops->timedout_job indirect call). Trigger is teardown-time (driver unload/rebind/device removal \u2014 privileged), hence Medium; the freed-memory execution is unconditional once fini runs with a pending TDR. Fix (cancel_delayed_work_sync after kthread_stop, fix.diff hunk 3) validated: patched amdgpu.ko, same test -> no callout fire, no panic, guest stays up (run_fixed.log).", "exploit_chain": "driver teardown (module unload / rebind / GPU removal) with any job pending on ring_mirror_list -> drm_sched_fini returns without cancelling work_tdr -> caller frees scheduler container (amdgpu_ring) -> armed delayed work fires on freed+reclaimed memory -> drm_sched_job_timedout executes from freed struct: freed-memory lockmgr, ring_mirror_list walk on dangling head, ops->timedout_job call through the freed sched->ops pointer.", "evidence": "panic.txt (test2 sequence + panic: _callout ... verifier + softclock_handler trace, db> wedge); run_fixed.log (patched module: no fire, STILL-UP); ../DF-4740/df4740.c (harness); VERDICT.md", "kernel_refs": [ "sys/dev/drm/scheduler/sched_main.c:652", "sys/dev/drm/scheduler/sched_main.c:246", "sys/dev/drm/scheduler/sched_main.c:194", "sys/dev/drm/amd/amdgpu/amdgpu_fence.c:509" ], "poc_changes": "Shares the DF-4740 harness; test-2 timing tuned after the first run showed the TDR firing before fini (2*hz timeout + 2-tick pre-fini wait) and the reclaim switched from 0xde fill to zeroed blocks because queue_work skips entries whose on_queue/running flags are truthy.", "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": 2100, "guest_dirty": 0, "build_cmd": "make in /tmp/df4740 (guest) \u2014 same df4740.ko as DF-4740", "run_cmd": "kldload /tmp/df4740/df4740.ko; sysctl hw.df4740_test=2 (panic ~2s later)", "code_hash": "751649c068ff459041fad8be03c35fc9ad6136287ec4b1c6dac9d5152b0635de", "notes": "Baseline from vm.sh reset with-src; guest reset with-src at end of run. Fix validated in the same rebuilt amdgpu.ko as DF-4740.", "recommended_fix": "cancel_delayed_work_sync(&sched->work_tdr) in drm_sched_fini after kthread_stop (fix.diff hunk 3; full upstream-grade fix should also drain ring_mirror_list).", "fix_status": "fixed", "fix_baseline_reproduced": 1, "fix_patched_reproduced": 0, "fix_verdict": "fixed: patched amdgpu.ko rebuild; test 2 re-run shows no callout fire on the freed scheduler, no panic, guest stays up (run_fixed.log) vs baseline softclock verifier panic (panic.txt).", "fix_evidence": "fix.diff; run_fixed.log; ../DF-4740/fix_build.log" } |