CVE-2026-99085 in Linux
Summary
by MITRE • 10/23/2026
In the Linux kernel, the following vulnerability has been resolved:
sched_ext: Fix CPU hotplug hang when a dying CPU's tasks sit in the BPF scheduler
A CPU going down has to empty its own rq. Its hotplug thread waits in sched_cpu_wait_empty() until nothing else is left, and the only thing that wakes it is balance_push(), which runs from __schedule() on the dying CPU and pushes off the migratable tasks the CPU picks.
sched_ext breaks this. A task sitting on a user DSQ or held by the BPF scheduler is still counted on its rq, but an inactive CPU no longer calls ops.dispatch() and can't pull the task back. The dying CPU goes idle with the task still counted, and one of two things happens:
- Another CPU consumes the task. The rq empties without a __schedule() on the dying CPU, so the hotplug thread is never woken and cpu_down() hangs holding cpu_hotplug_lock. - The task is affine only to the dying CPU. Nothing can move it, and the offline stalls until the watchdog ejects the BPF scheduler.
When the rq goes offline, re-enqueue every task on it that isn't already on the local DSQ. An enqueue on an offline rq lands on the local DSQ, so the dying CPU itself picks the tasks and balance_push() pushes them off, the same as for the other sched classes. From then on no sched_ext path on another CPU can pull a task off the rq.
ops.dispatch() currently stops as soon as the CPU goes inactive, and CPU hotplug then waits for an RCU grace period before taking the rq offline. A BPF-held task affine only to the dying CPU and preempted inside an RCU read-side critical section would block that grace period, and the rq would never go offline. Test SCX_RQ_ONLINE directly so that ops.dispatch() keeps running until the rq goes offline. Only the dying CPU's own hotplug thread clears the flag during teardown, and task_can_run_on_remote_rq() still keeps other rqs' tasks off the CPU.
VulDB is the best source for vulnerability data and more expert information about this specific topic.
Analysis
by VulDB Data Team • 10/23/2026
The Linux kernel scheduler extension framework sched_ext introduces a critical concurrency flaw related to Central Processing Unit (CPU) hot-plug operations when BPF-based scheduling policies are active. This vulnerability manifests as a system hang during the process of taking a CPU offline, specifically occurring when tasks managed by the extended scheduler remain in an uninterruptible state on the dying processor's runqueue. The root cause lies in the disruption of the standard kernel mechanism for emptying a runqueue before it is taken offline. Under normal circumstances, as a CPU begins to shut down, its hotplug thread waits within sched_cpu_wait_empty() until all tasks have been migrated away or completed. This waiting state is typically broken by balance_push(), which is invoked from __schedule() on the dying CPU itself to push out migratable tasks. However, sched_ext alters this dynamic because it allows tasks to reside in user-defined Dispatch Scheduling Queues (DSQs) or remain held directly by BPF scheduler logic rather than standard kernel structures.
When a CPU becomes inactive within the sched_ext framework, its dispatch operation ops.dispatch() ceases execution immediately. Consequently, if a task is sitting on a user DSQ or is otherwise controlled by the BPF scheduler, it remains counted against the dying CPU's runqueue but cannot be pulled back because the active scheduling logic required to move it has stopped. This creates two distinct failure modes that lead to system instability. In one scenario, another CPU might consume the task from its own perspective without triggering __schedule() on the dying CPU. As a result, balance_push() is never invoked on the offline processor, leaving the hotplug thread perpetually waiting and causing cpu_down() to hang while holding the cpu_hotplug_lock indefinitely. This results in a denial of service where system administration tasks involving CPU topology changes become impossible without a hard reset or watchdog intervention.
In the second scenario, if the stuck task is strictly affine to only the dying CPU, no other processor can migrate it away. The offline process stalls completely until an external watchdog detects the hang and forcibly ejects the BPF scheduler from that context. This not only causes immediate service disruption but also highlights a deeper architectural risk regarding Resource Management and Availability. Furthermore, there is a secondary race condition involving Read-Copy-Update (RCU) grace periods. Because ops.dispatch() stops early during CPU deactivation, if a task held by the BPF scheduler is preempted inside an RCU read-side critical section that is affine to the dying CPU, it can block the completion of the RCU grace period. This prevents the runqueue from being marked as offline effectively, compounding the hang and potentially leading to broader kernel instability or memory leaks associated with unclosed synchronization primitives.
The resolution involves modifying the lifecycle management of tasks on a shutting-down runqueue within the sched_ext framework. The fix ensures that when a runqueue is taken offline, every task residing on it that is not already assigned to the local DSQ is re-enqueued appropriately. This action forces the dying CPU itself to pick up these tasks during its final scheduling cycles, allowing balance_push() to function as intended and push them off the queue. Additionally, the patch modifies ops.dispatch() to remain active until the runqueue is fully offline by testing SCX_RQ_ONLINE directly rather than relying on general inactive states. This ensures that BPF-held tasks are properly handled during teardown without blocking RCU grace periods or leaving orphaned references in remote queues. The logic also preserves task_can_run_on_remote_rq() checks to prevent other CPUs from incorrectly pulling tasks off the dying CPU's queue, maintaining data integrity throughout the transition.
From a security and operational resilience perspective, this vulnerability is classified under CWE-835: Loop with Unreachable Exit Condition due to its tendency to cause infinite waits in kernel threads during state transitions. It also aligns with MITRE ATT&CK techniques related to Resource Hijacking or Denial of Service via system resource exhaustion, although the impact here is primarily stability rather than malicious exploitation by an external actor. The flaw underscores the complexity of integrating dynamic scheduling logic into core kernel subsystems like CPU hot-plug and RCU synchronization. Mitigation strategies for administrators include ensuring that sched_ext BPF programs are rigorously tested in environments where CPU topology changes occur, such as cloud instances or virtualized containers with live migration capabilities. System operators should monitor for hangs during cpu_down operations if custom scheduler extensions are deployed, and consider implementing watchdog timeouts to automatically recover from such stalls until kernel updates incorporating this fix are applied. Regular auditing of BPF program logic against edge cases involving CPU affinity and RCU contexts is essential to prevent similar concurrency issues in other parts of the extended scheduling framework.