Spectre (bounds check bypass / speculative execution leakage)
Description
Mitigates a Spectre variant 1 style information disclosure in the LoongArch syscall dispatch path. Previously, the syscall number (nr) is used to index into sys_call_table directly after a bounds check; speculative execution could bypass the check and access beyond the end of the syscall table, leaking kernel data. The patch applies array_index_nospec(nr, NR_syscalls) to sanitize the index during speculative execution, preventing leakage.
Proof of Concept
PoC outline (high-level):\n\nBackground: On LoongArch, the syscall dispatch path reads the function pointer from sys_call_table[nr] when a user-space process makes a system call. If an attacker can influence speculative execution to access an out-of-bounds entry before the bounds check is observed, information could be leaked from kernel memory via a side channel.\n\nBefore the fix (vulnerable behavior):\n- Code path: do_syscall(regs) -> if (nr < NR_syscalls) { syscall_fn = sys_call_table[nr]; ... }\n- Speculation: The CPU may speculatively execute the memory load sys_call_table[nr] even when nr >= NR_syscalls, causing a leakage of a byte/word from the underlying memory if coupled with a cache timing side channel.\n\nProof-of-concept (high-level, not a drop-in exploit):\n1) Prepare a user-space probe array cache_probe[256], aligned to cache lines. Each index i maps to a cache line that encodes the value i.\n2) Train the branch predictor to believe nr < NR_syscalls for a specific out-of-range nr, by issuing many legitimate syscall invocations with nr slightly below NR_syscalls.\n3) Trigger a syscall with nr set to a crafted out-of-bounds value (nr >= NR_syscalls). While the kernel still guards by a check, speculative execution could access sys_call_table[array_index_nospec(nr, NR_syscalls)], leaking a byte from the kernel memory pointed to by that slot into the cache line corresponding to that byte value.\n4) Use a user-space timing attack (Prime+Probe or Flush+Reload) on cache_probe to read the leaked byte value from the kernel memory address that was speculatively accessed.\n\nPost-fix expectation:\n- The speculation uses array_index_nospec to clamp the index to a safe value during speculative execution, preventing the out-of-bounds access and thus preventing the leakage via the cache side channel.\n\nImportant notes:\n- Implementing a reliable PoC requires a LoongArch environment and kernel with and without the patch; this outline is meant to illustrate the attack surface and the mitigation mechanism.
Commit Details
Author: Greg Kroah-Hartman
Date: 2026-04-22 07:45 UTC
Message:
LoongArch: Add spectre boundry for syscall dispatch table
The LoongArch syscall number is directly controlled by userspace, but
does not have a array_index_nospec() boundry to prevent access past the
syscall function pointer tables.
Cc: stable@vger.kernel.org
Assisted-by: gkh_clanker_2000
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
Signed-off-by: Huacai Chen <chenhuacai@loongson.cn>
Triage Assessment
Vulnerability Type: Spectre (bounds check / speculative execution leakage)
Confidence: HIGH
Reasoning:
Adds a spectre boundry using array_index_nospec for syscall dispatch, preventing speculative access beyond the syscall table when the syscall number is user-controlled. This mitigates a potential Spectre variant information disclosure/illegal access in the syscall dispatch path.
Verification Assessment
Vulnerability Type: Spectre (bounds check bypass / speculative execution leakage)
Confidence: HIGH
Affected Versions: LoongArch Linux kernel versions prior to this commit (i.e., before v7.0-rc6 in the LoongArch tree).
Code Diff
diff --git a/arch/loongarch/kernel/syscall.c b/arch/loongarch/kernel/syscall.c
index 1249d82c1cd0ac..dac435c3274337 100644
--- a/arch/loongarch/kernel/syscall.c
+++ b/arch/loongarch/kernel/syscall.c
@@ -9,6 +9,7 @@
#include <linux/entry-common.h>
#include <linux/errno.h>
#include <linux/linkage.h>
+#include <linux/nospec.h>
#include <linux/objtool.h>
#include <linux/randomize_kstack.h>
#include <linux/syscalls.h>
@@ -74,7 +75,7 @@ void noinstr __no_stack_protector do_syscall(struct pt_regs *regs)
add_random_kstack_offset();
if (nr < NR_syscalls) {
- syscall_fn = sys_call_table[nr];
+ syscall_fn = sys_call_table[array_index_nospec(nr, NR_syscalls)];
regs->regs[4] = syscall_fn(regs->orig_a0, regs->regs[5], regs->regs[6],
regs->regs[7], regs->regs[8], regs->regs[9]);
}