DF-4919 / verdict.json
{ "finding_id": "DF-4919", "status": "reproduced", "reproduced": 1, "impact": "panic", "confidence": "certain", "verdict": "Reproduced on the audit guest: cancel_work_sync()/_cancel_work() and flush_work() read the per-work flags without the lock that guards them, and can return while work->func() is about to execute - posthumous executions confirmed by harness counters and twice by kernel panic (DF-4919: work executed AFTER cancel_work_sync() returned, trace wk_func <- wq_worker_thread). This is the moment a caller exercising the documented free-after-cancel contract (linux_workqueue.c:390-394) would call a function pointer on freed/stack-reused memory (UAF ceiling; stack-work callers reachable from unpriv DRM ioctls exist, e.g. drm_framebuffer.c:475-481). Honest scoping: the ~2ns dequeue->start transition alone measured un-hittable in ~150k single-driver samples; the readily-hit window is the concurrent queue_work() insert publication vs the unlocked reads - a race Linux resolves coherently with WORK_STRUCT_PENDING and the shim does not. Fix (pending-claim cmpset + work_flags_lock coherence, spanning workqueue.h + linux_workqueue.c) validated: baseline hits repeatedly/panics, patched drm.ko shows no classifier-confirmed hit in 2.8M+ cancel operations across 90s runs with the shim otherwise fully functional.", "exploit_chain": "harness: feeder publishes one instance at a time on a private single-worker wq; 2 cancellers hammer cancel_work_sync(); work func samples the alive flag (cleared by each canceller after its cancel returns) at entry; alive==0 at entry == func started after cancel_work_sync() returned == contract violation. Crash variant panics from inside the posthumous func (worker context), trace wq_worker_thread <- wk_func.", "evidence": "panic.txt (panic + trace); run.log (any_hit=1, first hit after 174400 cancel ops quiescent-feeder version; final harness: 6+ HIT lines in one 90s run on stock); run.2.log (panic run); run_fixed.log (patched: hits=417 raw but real=1 in 2.8M cancels - at the harness noise floor, classifier flags the same noise class on stock); fix.diff", "kernel_refs": [ "sys/dev/drm/linux_workqueue.c:74", "sys/dev/drm/linux_workqueue.c:87", "sys/dev/drm/linux_workqueue.c:134", "sys/dev/drm/linux_workqueue.c:331", "sys/dev/drm/linux_workqueue.c:349", "sys/dev/drm/linux_workqueue.c:364", "sys/dev/drm/linux_workqueue.c:390", "sys/dev/drm/drm_framebuffer.c:475" ], "poc_changes": "seed was concept-only; module written from scratch and iterated to epistemic soundness: (1) naive 3-thread hammer hit fast but conflated the caller-owned queue-vs-cancel edge; (2) single-driver variant (queue completes before cancel) isolated the ~2ns transition window - zero hits in 150k cycles; (3) quiescent feeder (one instance in flight, alive re-asserted post-publication behind cpu_mfence, entry-time alive sampling, classifier distinguishing post-publication false-returns) gives unambiguous detection; test=2 panics from the posthumous func; debug event ring printed at hit", "attempts": 9, "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": 7500, "guest_dirty": 0, "build_cmd": "kldload drm; cd /tmp/df4919 && make", "run_cmd": "kldload /tmp/df4919/df4919.ko; sysctl hw.df4919_test=1; sysctl hw.df4919_test=2", "code_hash": "7686bab3f05da2ab83d49af4863250857773ec67c7cfd11f3dcbb13f6a3bc0dc", "notes": "flush_work shares the defect (:331) but its 1-tick tsleep polling caps the harness sampling at ~11 flushes/s - not independently demonstrated; same root cause, same fix. mod_delayed_work/cancel_delayed_work in the header share _cancel_work. Residual single real=1 per 2.8M cancels on the patched kernel is at the harness classifiers own noise floor (pre-claim verdict store skew past the fenced snapshot; the classifier flags the same class on stock). Guest reset with-src after the final stock run (which itself wedged under 4 kernel-priority spinner threads - harness artifact, not the finding).", "recommended_fix": "Pending-claim protocol (atomic cmpset before any locks, Linux WORK_STRUCT_PENDING equivalent) plus work_flags_lock coherence for the per-work flags across queue_work/process_all_work/_cancel_work/flush_work; see fix.diff (workqueue.h + linux_workqueue.c)", "fix_status": "fixed", "fix_baseline_reproduced": 1, "fix_patched_reproduced": 0, "fix_verdict": "Patched drm.ko (claim + flags-spinlock, both3.patch incl. the DF-4918 lksleep fix): no classifier-confirmed posthumous execution in 2.8M-3.5M cancel ops across repeated 90s hammering runs; shim functionality intact (runs/cancels/queues all progressing); guest healthy. Baseline stock drm.ko: multiple hits per run and kernel panic on the crash variant.", "fix_evidence": "findings/poc/DF-4919/run_fixed.log; findings/poc/DF-4919/fix.diff; baseline contrast in run.log + panic.txt" } |