KernelScan.io

HIGH

drm/i915 Heartbeat Race

CVE-2026-31656

CVSS 7.8 / 10.0 NVD

CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H

KernelScan AI7.8HIGH

01

In the Linux kernel, the following vulnerability has been resolved: drm/i915/gt: fix refcount underflow in intel_engine_park_heartbeat A use-after-free / refcount underflow is possible when the heartbeat worker and intel_engine_park_heartbeat() race to release the same engine->heartbeat.systole request. The heartbeat worker reads engine->heartbeat.systole and calls i915_request_put() on it when the request is complete, but clears the pointer in a separate, non-atomic step. Concurrently, a request retirement on another CPU can drop the engine wakeref to zero, triggering __engine_park() -> intel_engine_park_heartbeat(). If the heartbeat timer is pending at that point, cancel_delayed_work() returns true and intel_engine_park_heartbeat() reads the stale non-NULL systole pointer and calls i915_request_put() on it again, causing a refcount underflow: ``` <4> [487.221889] Workqueue: i915-unordered engine_retire [i915] <4> [487.222640] RIP: 0010:refcount_warn_saturate+0x68/0xb0 ... <4> [487.222707] Call Trace: <4> [487.222711] <TASK> <4> [487.222716] intel_engine_park_heartbeat.part.0+0x6f/0x80 [i915] <4> [487.223115] intel_engine_park_heartbeat+0x25/0x40 [i915] <4> [487.223566] __engine_park+0xb9/0x650 [i915] <4> [487.223973] ____intel_wakeref_put_last+0x2e/0xb0 [i915] <4> [487.224408] __intel_wakeref_put_last+0x72/0x90 [i915] <4> [487.224797] intel_context_exit_engine+0x7c/0x80 [i915] <4> [487.225238] intel_context_exit+0xf1/0x1b0 [i915] <4> [487.225695] i915_request_retire.part.0+0x1b9/0x530 [i915] <4> [487.226178] i915_request_retire+0x1c/0x40 [i915] <4> [487.226625] engine_retire+0x122/0x180 [i915] <4> [487.227037] process_one_work+0x239/0x760 <4> [487.227060] worker_thread+0x200/0x3f0 <4> [487.227068] ? __pfx_worker_thread+0x10/0x10 <4> [487.227075] kthread+0x10d/0x150 <4> [487.227083] ? __pfx_kthread+0x10/0x10 <4> [487.227092] ret_from_fork+0x3d4/0x480 <4> [487.227099] ? __pfx_kthread+0x10/0x10 <4> [487.227107] ret_from_fork_asm+0x1a/0x30 <4> [487.227141] </TASK> ``` Fix this by replacing the non-atomic pointer read + separate clear with xchg() in both racing paths. xchg() is a single indivisible hardware instruction that atomically reads the old pointer and writes NULL. This guarantees only one of the two concurrent callers obtains the non-NULL pointer and performs the put, the other gets NULL and skips it. (cherry picked from commit 13238dc0ee4f9ab8dafa2cca7295736191ae2f42)

02

Engine v0.2.0

Risk summary

This vulnerability can cause kernel crashes or memory corruption when Intel graphics drivers manage GPU engine heartbeats. The race condition between worker threads can lead to use-after-free conditions, potentially causing system instability or crashes during normal graphics operations. While exploitation for privilege escalation would be difficult, the memory corruption could potentially be leveraged by attackers with local access.

Affecteddrivers/gpu/drm/i915/gt/intel_engine_heartbeat.c

Vulnerability analysis

Root Cause: A race condition exists between the heartbeat worker thread and intel_engine_park_heartbeat() function when accessing the engine->heartbeat.systole pointer. Both code paths can read the pointer and call i915_request_put() on the same request object, but the pointer clearing happens in a separate non-atomic operation. This creates a window where both threads can obtain the same non-NULL pointer value and attempt to decrement the reference count, leading to a double-free condition and refcount underflow.

Attack Surface: This vulnerability affects systems with Intel i915 graphics hardware where GPU engine management operations occur. The race condition is triggered during normal GPU workload processing when engine retirement and heartbeat management happen concurrently on different CPU cores. No special privileges or network access are required - the race can occur during regular GPU operations.

Fix Mechanism: The fix replaces the non-atomic pointer read + separate clear operations with atomic xchg() operations in both racing code paths. The xchg() instruction atomically reads the current pointer value and writes NULL in a single indivisible hardware operation. This ensures that only one of the two concurrent threads obtains the non-NULL pointer and performs the reference count decrement, while the other gets NULL and skips the operation entirely.

03

BranchFixed inPatch commit
5.105.10.25882034799c6c1
5.155.15.20370d3e622b100
6.16.1.1698ce44d28a84f
6.126.12.82a00e92bf6583
6.186.18.232af8b200cae3
6.196.19.13455d98ed527f
6.66.6.135ca3f48c3567d
mainline7.04c71fd099513