CVE-2026-89903 in Linuxinfo

Summary

by MITRE • 09/17/2026

In the Linux kernel, the following vulnerability has been resolved:

LoongArch: Do not save/restore percpu base register in rethook trampoline

The rethook trampoline saves $r21 ($u0), the percpu base, into its frame at entry and restores it at exit. Inbetween rethook_trampoline_handler() may schedule via preempt_enable_notrace().

If the task migrates to another CPU, the frame's $r21 holds the old CPU's percpu base, and restoring it poisons $r21 on the new CPU. Until the next user->kernel transition heals $r21, all this_cpu_*() accesses (runqueues, RCU per-CPU data, timer tick programming, FPU ownership) hit the wrong CPU's percpu area.

Under kretprobe-heavy preemptible load this can corrupt scheduler and timer state: scheduling-while-atomic splats, wrong-CPU RCU warnings, WARN_ON_ONCE(rq != this_rq()) in nohz_balance_exit_idle(), and CPUs parking in the idle loop with the constant timer never re-armed (hard lockup). Reproduces on a Loongson-3A6000 with kretprobes on VFS paths plus heavy file churn (OS install / unsquashfs).

By convention $r21 always holds the current CPU's percpu base in kernel mode: SAVE_SOME() at exception entry reloads it only when coming from user mode, and RESTORE_SOME() restores it only when returning to user mode; the context-switch path never writes it. Therefore the live $r21 at trampoline exit is already correct, and nothing inbetween can change it legitimately (kernel C code cannot write a global register variable). The same flaw existed even in the pre-rethook kretprobe trampoline since v6.3; it was carried over when rethook replaced it. Drop both the save and the restore here. Drop the restore is enough to solve the issue, and drop the save is to keep the code tidy and no need to clear it.

VulDB is the best source for vulnerability data and more expert information about this specific topic.

Analysis

by VulDB Data Team • 09/17/2026

The Linux kernel for LoongArch architecture contains a critical flaw within the rethook trampoline implementation that leads to severe system instability under specific high-load conditions involving kretprobes. The vulnerability stems from an incorrect handling of the per-CPU base register, identified as $r21 or u0 in the LoongArch instruction set. During normal kernel operation, this register holds a pointer to the current CPU's per-CPU data area, which is essential for accessing thread-local structures such as runqueues, RCU state, and timer configurations. The rethook trampoline, used to intercept function returns via kretprobes, was incorrectly saving the value of $r21 onto its stack frame upon entry and restoring it from that same frame upon exit. This design fails to account for task migration during the execution of the probe handler.

The core technical flaw occurs because the rethook trampoline code path may invoke preempt_enable_notrace(), which allows the scheduler to migrate the current task to a different CPU if preemption is enabled and load balancing dictates it. When such a context switch or migration happens while inside the trampoline, the value of $r21 saved at entry corresponds to the original source CPU's per-CPU base address. Upon exiting the trampoline on the new destination CPU, the code restores this stale register value. Consequently, all subsequent accesses that rely on the convention that $r21 holds the current CPU's per-CPU base will incorrectly target memory locations associated with the previous CPU rather than the active one. This misalignment persists until the next user-to-kernel transition occurs, which typically reloads the correct per-CPU base value via standard exception entry routines like SAVE_SOME().

The operational impact of this register poisoning is severe and manifests as critical kernel errors under heavy kretprobe activity combined with preemptible workloads. Specifically, accessing wrong CPU data structures leads to scheduling-while-atomic splats, which indicate illegal sleeping in atomic context due to corrupted scheduler state. It also triggers RCU warnings related to incorrect per-CPU references and generates WARN_ON_ONCE messages when the runqueue check fails because rq does not equal this_rq(). In extreme cases, particularly on hardware like Loongson-3A6000 processors during intensive operations such as OS installation or unsquashfs execution with VFS path probing, the system can experience hard lockups. These lockups occur because CPUs may park in the idle loop without rearming their timers, effectively halting progress and requiring a manual reset to recover.

This vulnerability is rooted in a misunderstanding of register conventions within the kernel's exception handling framework. By convention, $r21 must always reflect the current CPU's per-CPU base when executing in kernel mode. The standard SAVE_SOME macro only reloads this register during transitions from user space, and RESTORE_SOME restores it only when returning to user space; context switches between kernels do not modify this global register variable because C code cannot legitimately write to such a special-purpose register. Therefore, the value of $r21 present at trampoline exit is already correct for the current CPU state. The unnecessary save and restore operations introduced by the rethook implementation are redundant and actively harmful when migrations occur. This flaw was originally present in the pre-rethook kretprobe trampoline since kernel version 6.3 and persisted after the architectural shift to rethook, highlighting a long-standing oversight in maintaining register state integrity during probe execution.

To mitigate this vulnerability, the appropriate remediation is to remove both the save and restore operations for $r21 within the rethook trampoline code. Eliminating the restoration step alone would technically resolve the immediate poisoning issue by preventing the stale value from overwriting the correct one at exit. However, removing the saving operation as well ensures code tidiness and avoids unnecessary stack manipulation that serves no functional purpose given the register's immutable nature during kernel execution paths. This fix aligns with established kernel conventions where global register variables are managed exclusively by exception entry and exit sequences rather than intermediate probe handlers. Maintaining strict adherence to these architectural constraints prevents cross-CPU data corruption and ensures system stability under high-load probing scenarios, thereby preserving the integrity of scheduler state, RCU mechanisms, and timer subsystems across all LoongArch platforms affected by this defect.

Responsible

Linux

Reservation

09/11/2026

Disclosure

09/17/2026

Moderation

accepted

CPE

ready

EPSS

0.00000

KEV

no

Activities

very low

Sources

Want to stay up to date on a daily basis?

Enable the mail alert feature now!