CVE-2026-45919
Description
In the Linux kernel, the following vulnerability has been resolved:
sched/rt: Skip currently executing CPU in rto_next_cpu()
CPU0 becomes overloaded when hosting a CPU-bound RT task, a non-CPU-bound RT task, and a CFS task stuck in kernel space. When other CPUs switch from RT to non-RT tasks, RT load balancing (LB) is triggered; with HAVE_RT_PUSH_IPI enabled, they send IPIs to CPU0 to drive the execution of rto_push_irq_work_func. During push_rt_task on CPU0, if next_task->prio < rq->donor->prio, resched_curr() sets NEED_RESCHED and after the push operation completes, CPU0 calls rto_next_cpu(). Since only CPU0 is overloaded in this scenario, rto_next_cpu() should ideally return -1 (no further IPI needed).
However, multiple CPUs invoking tell_cpu_to_push() during LB increments rd->rto_loop_next. Even when rd->rto_cpu is set to -1, the mismatch between rd->rto_loop and rd->rto_loop_next forces rto_next_cpu() to restart its search from -1. With CPU0 remaining overloaded (satisfying rt_nr_migratory && rt_nr_total > 1), it gets reselected, causing CPU0 to queue irq_work to itself and send self-IPIs repeatedly. As long as CPU0 stays overloaded and other CPUs run pull_rt_tasks(), it falls into an infinite self-IPI loop, which triggers a CPU hardlockup due to continuous self-interrupts.
The trigging scenario is as follows:
cpu0 cpu1 cpu2 pull_rt_task tell_cpu_to_push <------------irq_work_queue_on rto_push_irq_work_func push_rt_task resched_curr(rq) pull_rt_task rto_next_cpu tell_cpu_to_push <-------------------------- atomic_inc(rto_loop_next) rd->rto_loop != next rto_next_cpu irq_work_queue_on rto_push_irq_work_func
Fix redundant self-IPI by filtering the initiating CPU in rto_next_cpu(). This solution has been verified to effectively eliminate spurious self-IPIs and prevent CPU hardlockup scenarios.
AI Insight
LLM-synthesized narrative grounded in this CVE's description and references.
A Linux kernel RT scheduling flaw causes a CPU to self-IPI in an infinite loop, leading to a hardlockup when CPU0 is overloaded with RT tasks and other CPUs trigger load balancing.
Vulnerability
The vulnerability is a race condition in the Linux kernel's real-time (RT) scheduling load balancing logic, affecting versions that include HAVE_RT_PUSH_IPI (typically CONFIG_HAVE_RT_PUSH_IPI). When CPU0 is overloaded with a mix of CPU-bound RT tasks, non-CPU-bound RT tasks, and a CFS task stuck in kernel space, and other CPUs switch from RT to non-RT tasks, RT load balancing is triggered. The function rto_next_cpu() in kernel/sched/rt.c fails to skip the currently executing CPU, causing CPU0 to repeatedly self-IPI and execute rto_push_irq_work_func in an infinite loop. This leads to a CPU hardlockup [1].
Exploitation
An attacker needs to be able to create processes with real-time scheduling priority (e.g., via sched_setscheduler with SCHED_RR or SCHED_FIFO) on a system with the vulnerable kernel configuration (HAVE_RT_PUSH_IPI). By creating a CPU-bound RT task, a non-CPU-bound RT task, and a CFS task that blocks in kernel space on CPU0, while other CPUs run pull_rt_tasks() during load balancing, the race condition can be triggered. The attacker does not need to have precise timing control; the sequence described in the CVE description shows that multiple CPUs calling tell_cpu_to_push() increments rd->rto_loop_next, causing rto_next_cpu() to reselect CPU0 even when rd->rto_cpu is -1. This results in CPU0 queuing irq_work to itself infinitely [1].
Impact
On successful exploitation, CPU0 enters a hardlockup state due to continuous self-IPIs, causing the system to become unresponsive or crash. This is a denial-of-service (DoS) condition affecting availability. The attacker does not gain elevated privileges or data access, but can cause a system-wide hang or reboot, impacting the operation of the machine [1].
Mitigation
The fix is in the Linux kernel commit a6a73403733e86748421f2eeaf028c85683ef896, which filters the initiating CPU in rto_next_cpu() to prevent redundant self-IPI. This commit has been applied to stable kernel branches (the exact version depends on the distribution backport). Users should update to a kernel version that includes this commit. There is no known workaround beyond disabling HAVE_RT_PUSH_IPI via kernel configuration (not practical for running RT workloads) [1].
AI Insight generated on May 27, 2026. Synthesized from this CVE's description and the cited reference URLs; citations are validated against the source bundle.
Affected products
1Patches
1652aeb1e07ec2sched/rt: Skip currently executing CPU in rto_next_cpu()
1 file changed · +5 −1
kernel/sched/rt.c+5 −1 modifieddiff --git a/kernel/sched/rt.c b/kernel/sched/rt.c index fb07dcfc60a244..d4d994fb8999a4 100644 --- a/kernel/sched/rt.c +++ b/kernel/sched/rt.c @@ -2100,6 +2100,7 @@ static void push_rt_tasks(struct rq *rq) */ static int rto_next_cpu(struct root_domain *rd) { + int this_cpu = smp_processor_id(); int next; int cpu; @@ -2123,6 +2124,10 @@ static int rto_next_cpu(struct root_domain *rd) rd->rto_cpu = cpu; + /* Do not send IPI to self */ + if (cpu == this_cpu) + continue; + if (cpu < nr_cpu_ids) return cpu; -- cgit 1.3-korg
8ad5577b2d4asched/rt: Skip currently executing CPU in rto_next_cpu()
1 file changed · +5 −1
kernel/sched/rt.c+5 −1 modifieddiff --git a/kernel/sched/rt.c b/kernel/sched/rt.c index 2d0acdd32108ab..0b420a65b31dc9 100644 --- a/kernel/sched/rt.c +++ b/kernel/sched/rt.c @@ -2219,6 +2219,7 @@ static void push_rt_tasks(struct rq *rq) */ static int rto_next_cpu(struct root_domain *rd) { + int this_cpu = smp_processor_id(); int next; int cpu; @@ -2242,6 +2243,10 @@ static int rto_next_cpu(struct root_domain *rd) rd->rto_cpu = cpu; + /* Do not send IPI to self */ + if (cpu == this_cpu) + continue; + if (cpu < nr_cpu_ids) return cpu; -- cgit 1.3-korg
a6a73403733esched/rt: Skip currently executing CPU in rto_next_cpu()
1 file changed · +5 −1
kernel/sched/rt.c+5 −1 modifieddiff --git a/kernel/sched/rt.c b/kernel/sched/rt.c index c437a150262384..ffcce501ed40c6 100644 --- a/kernel/sched/rt.c +++ b/kernel/sched/rt.c @@ -2151,6 +2151,7 @@ static void push_rt_tasks(struct rq *rq) */ static int rto_next_cpu(struct root_domain *rd) { + int this_cpu = smp_processor_id(); int next; int cpu; @@ -2174,6 +2175,10 @@ static int rto_next_cpu(struct root_domain *rd) rd->rto_cpu = cpu; + /* Do not send IPI to self */ + if (cpu == this_cpu) + continue; + if (cpu < nr_cpu_ids) return cpu; -- cgit 1.3-korg
d57d0746276asched/rt: Skip currently executing CPU in rto_next_cpu()
1 file changed · +5 −1
kernel/sched/rt.c+5 −1 modifieddiff --git a/kernel/sched/rt.c b/kernel/sched/rt.c index 1289991c970e1c..cc6950fc6061e0 100644 --- a/kernel/sched/rt.c +++ b/kernel/sched/rt.c @@ -2005,6 +2005,7 @@ static void push_rt_tasks(struct rq *rq) */ static int rto_next_cpu(struct root_domain *rd) { + int this_cpu = smp_processor_id(); int next; int cpu; @@ -2028,6 +2029,10 @@ static int rto_next_cpu(struct root_domain *rd) rd->rto_cpu = cpu; + /* Do not send IPI to self */ + if (cpu == this_cpu) + continue; + if (cpu < nr_cpu_ids) return cpu; -- cgit 1.3-korg
9f25edc5a20csched/rt: Skip currently executing CPU in rto_next_cpu()
1 file changed · +5 −1
kernel/sched/rt.c+5 −1 modifieddiff --git a/kernel/sched/rt.c b/kernel/sched/rt.c index f1867fe8e5c535..e0ff909050190b 100644 --- a/kernel/sched/rt.c +++ b/kernel/sched/rt.c @@ -2100,6 +2100,7 @@ static void push_rt_tasks(struct rq *rq) */ static int rto_next_cpu(struct root_domain *rd) { + int this_cpu = smp_processor_id(); int next; int cpu; @@ -2123,6 +2124,10 @@ static int rto_next_cpu(struct root_domain *rd) rd->rto_cpu = cpu; + /* Do not send IPI to self */ + if (cpu == this_cpu) + continue; + if (cpu < nr_cpu_ids) return cpu; -- cgit 1.3-korg
3b3c672a66dbsched/rt: Skip currently executing CPU in rto_next_cpu()
1 file changed · +5 −1
kernel/sched/rt.c+5 −1 modifieddiff --git a/kernel/sched/rt.c b/kernel/sched/rt.c index 9720b3c19ab97f..c5122e5f258e43 100644 --- a/kernel/sched/rt.c +++ b/kernel/sched/rt.c @@ -2068,6 +2068,7 @@ static void push_rt_tasks(struct rq *rq) */ static int rto_next_cpu(struct root_domain *rd) { + int this_cpu = smp_processor_id(); int next; int cpu; @@ -2091,6 +2092,10 @@ static int rto_next_cpu(struct root_domain *rd) rd->rto_cpu = cpu; + /* Do not send IPI to self */ + if (cpu == this_cpu) + continue; + if (cpu < nr_cpu_ids) return cpu; -- cgit 1.3-korg
16ca9f3117e9sched/rt: Skip currently executing CPU in rto_next_cpu()
1 file changed · +5 −1
kernel/sched/rt.c+5 −1 modifieddiff --git a/kernel/sched/rt.c b/kernel/sched/rt.c index 3a2335bc1d58b2..99e5d37b3f6eba 100644 --- a/kernel/sched/rt.c +++ b/kernel/sched/rt.c @@ -2230,6 +2230,7 @@ static void push_rt_tasks(struct rq *rq) */ static int rto_next_cpu(struct root_domain *rd) { + int this_cpu = smp_processor_id(); int next; int cpu; @@ -2253,6 +2254,10 @@ static int rto_next_cpu(struct root_domain *rd) rd->rto_cpu = cpu; + /* Do not send IPI to self */ + if (cpu == this_cpu) + continue; + if (cpu < nr_cpu_ids) return cpu; -- cgit 1.3-korg
94894c9c477esched/rt: Skip currently executing CPU in rto_next_cpu()
1 file changed · +5 −1
kernel/sched/rt.c+5 −1 modifieddiff --git a/kernel/sched/rt.c b/kernel/sched/rt.c index 0a9b2cd6da7208..a7680477fa6f6b 100644 --- a/kernel/sched/rt.c +++ b/kernel/sched/rt.c @@ -2106,6 +2106,7 @@ static void push_rt_tasks(struct rq *rq) */ static int rto_next_cpu(struct root_domain *rd) { + int this_cpu = smp_processor_id(); int next; int cpu; @@ -2129,6 +2130,10 @@ static int rto_next_cpu(struct root_domain *rd) rd->rto_cpu = cpu; + /* Do not send IPI to self */ + if (cpu == this_cpu) + continue; + if (cpu < nr_cpu_ids) return cpu; -- cgit 1.3-korg
94894c9c477esched/rt: Skip currently executing CPU in rto_next_cpu()
1 file changed · +5 −1
kernel/sched/rt.c+5 −1 modifieddiff --git a/kernel/sched/rt.c b/kernel/sched/rt.c index 0a9b2cd6da7208..a7680477fa6f6b 100644 --- a/kernel/sched/rt.c +++ b/kernel/sched/rt.c @@ -2106,6 +2106,7 @@ static void push_rt_tasks(struct rq *rq) */ static int rto_next_cpu(struct root_domain *rd) { + int this_cpu = smp_processor_id(); int next; int cpu; @@ -2129,6 +2130,10 @@ static int rto_next_cpu(struct root_domain *rd) rd->rto_cpu = cpu; + /* Do not send IPI to self */ + if (cpu == this_cpu) + continue; + if (cpu < nr_cpu_ids) return cpu; -- cgit 1.3-korg
9f25edc5a20csched/rt: Skip currently executing CPU in rto_next_cpu()
1 file changed · +5 −1
kernel/sched/rt.c+5 −1 modifieddiff --git a/kernel/sched/rt.c b/kernel/sched/rt.c index f1867fe8e5c535..e0ff909050190b 100644 --- a/kernel/sched/rt.c +++ b/kernel/sched/rt.c @@ -2100,6 +2100,7 @@ static void push_rt_tasks(struct rq *rq) */ static int rto_next_cpu(struct root_domain *rd) { + int this_cpu = smp_processor_id(); int next; int cpu; @@ -2123,6 +2124,10 @@ static int rto_next_cpu(struct root_domain *rd) rd->rto_cpu = cpu; + /* Do not send IPI to self */ + if (cpu == this_cpu) + continue; + if (cpu < nr_cpu_ids) return cpu; -- cgit 1.3-korg
a6a73403733esched/rt: Skip currently executing CPU in rto_next_cpu()
1 file changed · +5 −1
kernel/sched/rt.c+5 −1 modifieddiff --git a/kernel/sched/rt.c b/kernel/sched/rt.c index c437a150262384..ffcce501ed40c6 100644 --- a/kernel/sched/rt.c +++ b/kernel/sched/rt.c @@ -2151,6 +2151,7 @@ static void push_rt_tasks(struct rq *rq) */ static int rto_next_cpu(struct root_domain *rd) { + int this_cpu = smp_processor_id(); int next; int cpu; @@ -2174,6 +2175,10 @@ static int rto_next_cpu(struct root_domain *rd) rd->rto_cpu = cpu; + /* Do not send IPI to self */ + if (cpu == this_cpu) + continue; + if (cpu < nr_cpu_ids) return cpu; -- cgit 1.3-korg
d57d0746276asched/rt: Skip currently executing CPU in rto_next_cpu()
1 file changed · +5 −1
kernel/sched/rt.c+5 −1 modifieddiff --git a/kernel/sched/rt.c b/kernel/sched/rt.c index 1289991c970e1c..cc6950fc6061e0 100644 --- a/kernel/sched/rt.c +++ b/kernel/sched/rt.c @@ -2005,6 +2005,7 @@ static void push_rt_tasks(struct rq *rq) */ static int rto_next_cpu(struct root_domain *rd) { + int this_cpu = smp_processor_id(); int next; int cpu; @@ -2028,6 +2029,10 @@ static int rto_next_cpu(struct root_domain *rd) rd->rto_cpu = cpu; + /* Do not send IPI to self */ + if (cpu == this_cpu) + continue; + if (cpu < nr_cpu_ids) return cpu; -- cgit 1.3-korg
16ca9f3117e9sched/rt: Skip currently executing CPU in rto_next_cpu()
1 file changed · +5 −1
kernel/sched/rt.c+5 −1 modifieddiff --git a/kernel/sched/rt.c b/kernel/sched/rt.c index 3a2335bc1d58b2..99e5d37b3f6eba 100644 --- a/kernel/sched/rt.c +++ b/kernel/sched/rt.c @@ -2230,6 +2230,7 @@ static void push_rt_tasks(struct rq *rq) */ static int rto_next_cpu(struct root_domain *rd) { + int this_cpu = smp_processor_id(); int next; int cpu; @@ -2253,6 +2254,10 @@ static int rto_next_cpu(struct root_domain *rd) rd->rto_cpu = cpu; + /* Do not send IPI to self */ + if (cpu == this_cpu) + continue; + if (cpu < nr_cpu_ids) return cpu; -- cgit 1.3-korg
3b3c672a66dbsched/rt: Skip currently executing CPU in rto_next_cpu()
1 file changed · +5 −1
kernel/sched/rt.c+5 −1 modifieddiff --git a/kernel/sched/rt.c b/kernel/sched/rt.c index 9720b3c19ab97f..c5122e5f258e43 100644 --- a/kernel/sched/rt.c +++ b/kernel/sched/rt.c @@ -2068,6 +2068,7 @@ static void push_rt_tasks(struct rq *rq) */ static int rto_next_cpu(struct root_domain *rd) { + int this_cpu = smp_processor_id(); int next; int cpu; @@ -2091,6 +2092,10 @@ static int rto_next_cpu(struct root_domain *rd) rd->rto_cpu = cpu; + /* Do not send IPI to self */ + if (cpu == this_cpu) + continue; + if (cpu < nr_cpu_ids) return cpu; -- cgit 1.3-korg
52aeb1e07ec2sched/rt: Skip currently executing CPU in rto_next_cpu()
1 file changed · +5 −1
kernel/sched/rt.c+5 −1 modifieddiff --git a/kernel/sched/rt.c b/kernel/sched/rt.c index fb07dcfc60a244..d4d994fb8999a4 100644 --- a/kernel/sched/rt.c +++ b/kernel/sched/rt.c @@ -2100,6 +2100,7 @@ static void push_rt_tasks(struct rq *rq) */ static int rto_next_cpu(struct root_domain *rd) { + int this_cpu = smp_processor_id(); int next; int cpu; @@ -2123,6 +2124,10 @@ static int rto_next_cpu(struct root_domain *rd) rd->rto_cpu = cpu; + /* Do not send IPI to self */ + if (cpu == this_cpu) + continue; + if (cpu < nr_cpu_ids) return cpu; -- cgit 1.3-korg
8ad5577b2d4asched/rt: Skip currently executing CPU in rto_next_cpu()
1 file changed · +5 −1
kernel/sched/rt.c+5 −1 modifieddiff --git a/kernel/sched/rt.c b/kernel/sched/rt.c index 2d0acdd32108ab..0b420a65b31dc9 100644 --- a/kernel/sched/rt.c +++ b/kernel/sched/rt.c @@ -2219,6 +2219,7 @@ static void push_rt_tasks(struct rq *rq) */ static int rto_next_cpu(struct root_domain *rd) { + int this_cpu = smp_processor_id(); int next; int cpu; @@ -2242,6 +2243,10 @@ static int rto_next_cpu(struct root_domain *rd) rd->rto_cpu = cpu; + /* Do not send IPI to self */ + if (cpu == this_cpu) + continue; + if (cpu < nr_cpu_ids) return cpu; -- cgit 1.3-korg
Vulnerability mechanics
Root cause
"Missing self-CPU filter in rto_next_cpu() allows the overloaded CPU to select itself for IPI, causing an infinite self-IPI loop."
Attack vector
An attacker who can create a workload where CPU0 hosts a CPU-bound RT task, a non-CPU-bound RT task, and a CFS task stuck in kernel space can trigger the bug. When other CPUs switch from RT to non-RT tasks, RT load balancing (LB) is triggered and, with `HAVE_RT_PUSH_IPI` enabled, they send IPIs to CPU0 via `tell_cpu_to_push()`. Multiple CPUs invoking `tell_cpu_to_push()` increment `rd->rto_loop_next`, causing a mismatch with `rd->rto_loop` that forces `rto_next_cpu()` to restart its search from -1. Since CPU0 remains overloaded (satisfying `rt_nr_migratory && rt_nr_total > 1`), it gets reselected, causing CPU0 to queue `irq_work` to itself and send self-IPIs repeatedly, leading to an infinite self-IPI loop and a CPU hardlockup.
Affected code
The vulnerability resides in the `rto_next_cpu()` function in `kernel/sched/rt.c`. The function iterates over CPUs in the root domain to select a target for RT push IPIs but did not exclude the currently executing CPU from the search.
What the fix does
The patch adds `int this_cpu = smp_processor_id();` at the start of `rto_next_cpu()` and inserts a check `if (cpu == this_cpu) continue;` inside the loop that iterates over candidate CPUs. This prevents the function from selecting the CPU that is currently executing `rto_next_cpu()` as the target for an IPI, thereby eliminating the self-IPI loop. The fix is minimal — it simply skips the local CPU rather than sending an IPI to itself, which was both unnecessary and harmful.
Preconditions
- configCPU0 must host a CPU-bound RT task, a non-CPU-bound RT task, and a CFS task stuck in kernel space
- configHAVE_RT_PUSH_IPI must be enabled in the kernel configuration
- inputOther CPUs must be running pull_rt_tasks() and switching from RT to non-RT tasks
Generated on May 27, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.
References
8- git.kernel.org/stable/c/16ca9f3117e9a294646c897daf08a5ab546c711bnvd
- git.kernel.org/stable/c/3b3c672a66db3de3b40f8a7057864bc1f874ede3nvd
- git.kernel.org/stable/c/52aeb1e07ec223caf212f036817976c98d2aa250nvd
- git.kernel.org/stable/c/8ad5577b2d4acfd83f03d97a0aece2d18aac5f07nvd
- git.kernel.org/stable/c/94894c9c477e53bcea052e075c53f89df3d2a33envd
- git.kernel.org/stable/c/9f25edc5a20cb52a5abbf25f0724bb4732b81801nvd
- git.kernel.org/stable/c/a6a73403733e86748421f2eeaf028c85683ef896nvd
- git.kernel.org/stable/c/d57d0746276a88ea43a2cc62b849fd8a95e32e41nvd
News mentions
0No linked articles in our index yet.