CVE-2026-98865: sched_ext: Don't run ops.dequeue() with a DSQ lock held
In the Linux kernel, the following vulnerability has been resolved:
schedext: Don't run ops.dequeue() with a DSQ lock held
ops.dequeue() is invoked with the source user DSQ's lock still held on the consume and move paths (scxconsumedispatchq(), movetaskbetweendsqs()). A BPF scheduler which locks the source user DSQ from ops.dequeue() - e.g. by iterating it with bpfiterscxdsq - self-deadlocks.
ops.dequeue() can only call the "any" kfuncs and none of them can lock a builtin DSQ, so the global and bypass paths can't deadlock; however, all DSQ locks share one lockdep class, so iterating any user DSQ from ops.dequeue() on those paths trips the recursion check.
Move the invocation after the DSQ unlock on all three paths. SCXTASKINCUSTODY is cleared under the lock serializing the transfer so that the callback is invoked exactly once.
Affected Software
Event History
Frequently Asked Questions
Which systems are exposed to this issue?
Systems using the Linux kernel's sched_ext framework with a BPF scheduler are relevant. The deadlock scenario specifically involves a scheduler whose ops.dequeue() callback locks or iterates the source user dispatch queue, such as through bpf_iter_scx_dsq.
What conditions trigger the deadlock?
The issue occurs when ops.dequeue() is called while the source user DSQ lock remains held on consume or task-move paths, and the callback attempts to lock that DSQ. Iterating any user DSQ from ops.dequeue() can also trigger lockdep recursion checking because DSQ locks share a lockdep class.
Are global or bypass dispatch-queue paths affected by the deadlock?
No. On the global and bypass paths, ops.dequeue() can call only the "any" kfuncs, and those kfuncs cannot lock a built-in DSQ, so those paths cannot deadlock in this way.
How does the fix prevent repeated dequeue callbacks during a transfer?
The fix moves ops.dequeue() invocation until after the DSQ lock is released on all three affected paths. It also clears SCX_TASK_IN_CUSTODY while holding the lock that serializes the transfer, ensuring the callback is invoked exactly once.