Skip to content

i915/GuC: RCU use-after-free in signal_irq_work() during GPU-hang recovery #489

Description

@JustAnotherHumanBeing

Describe the bug
After an i915 GPU hang on Meteor Lake with GuC submission enabled, the recovery path can panic while signal_irq_work() calls
guc_retire_inflight_request_prio().

The kernel dump shows that rq->context referred to an intel_context that was concurrently freed. The panic occurred while
attempting to lock context->guc_state.lock.

This report concerns the recovery-path panic, not the underlying cause of the preceding GPU hang.

Panic backtrace

Fatal trap 12: page fault while in kernel mode
current thread: kernel/linuxkpi_short_wq_2

witness_checkorder()
__mtx_lock_flags()
guc_retire_inflight_request_prio()
signal_irq_work()
taskqueue_run_locked()
taskqueue_thread_loop()

The mutex address was inside the freed intel_context. Inspection of the dump showed that the context allocation had been
overwritten with FreeBSD's 0xdeadc0de freed-memory poison pattern.

The i915_request itself was still referenced and valid. It was specifically the associated context whose lifetime had ended.

Root cause

signal_irq_work() currently uses this ordering:

rcu_read_lock();

/* Traverse signalers and move completed requests to a local list. */

rcu_read_unlock();

llist_for_each_safe(signal, sn, signal) {
struct i915_request *rq = ...;

      if (rq->engine->sched_engine->retire_inflight_request_prio)
              rq->engine->sched_engine->retire_inflight_request_prio(rq);

      ...

}

Holding a request reference does not keep its intel_context alive. Concurrent request retirement can call intel_context_unpin(),
drop the final context reference and schedule rcu_context_free().

On Linux, irq_work normally runs in interrupt context, which supplies an implicit RCU read-side critical section on the non-
PREEMPT_RT kernels supported by i915. FreeBSD LinuxKPI executes irq_work callbacks on an ordinary taskqueue. Consequently, once
the explicit rcu_read_unlock() above executes, the RCU callback may free the context while the taskqueue worker is still
processing the local request list.

The dump and disassembly show that guc_retire_inflight_request_prio() loaded the context and examined it, after which the
context was freed before the function attempted to lock the embedded GuC-state mutex.

Proposed fix

The attached patch moves rcu_read_unlock() below the local request-processing loop, keeping every post-processing use of
rq->context inside the explicit RCU read-side critical section.

The patch builds successfully and passes git apply --check against:

  • freebsd/drm-kmod master at b6f7264
  • the Meteor Lake development tree at 74d460b

PR #469 still contains the original ordering and therefore does not currently fix this race. Its required LinuxKPI branch also
continues to invoke irq_work callbacks from a taskqueue without adding equivalent RCU protection.

drm-kmod-mtl-irq-work-rcu-lifetime.patch

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions