From: Breno Leitao <leitao@debian.org>
To: Andy Lutomirski <luto@kernel.org>,
Thomas Gleixner <tglx@kernel.org>,
Ingo Molnar <mingo@redhat.com>, Borislav Petkov <bp@alien8.de>,
Dave Hansen <dave.hansen@linux.intel.com>,
x86@kernel.org, "H. Peter Anvin" <hpa@zytor.com>,
Peter Zijlstra <peterz@infradead.org>,
Josh Poimboeuf <jpoimboe@kernel.org>,
Pawan Gupta <pawan.kumar.gupta@linux.intel.com>,
Nathan Chancellor <nathan@kernel.org>,
Nick Desaulniers <ndesaulniers@google.com>,
Bill Wendling <morbo@google.com>,
Justin Stitt <justinstitt@google.com>,
torvalds@linux-foundation.org
Cc: linux-kernel@vger.kernel.org, llvm@lists.linux.dev,
kernel-team@meta.com, Breno Leitao <leitao@debian.org>
Subject: [PATCH RFC] x86/syscall: Dispatch via sys_call_table when Spectre v2 and BHI are off
Date: Thu, 29 Oct 2026 03:27:23 -0700 [thread overview]
Message-ID: <20261029-syscall-table-dispatch-v1-1-bca9dbb886de@debian.org> (raw)
Linus' commit 1e3ad78334a6 ("x86/syscall: Don't force use of indirect
calls for system calls") dispatches system calls with a switch
statement, so that syscall entry has no indirect branch for BHI to
target.
As noted by Linus in the commit above, the compiler can either
decide to generate an indirect jump, or just a series of conditional
branches (which is more expensive).
With CONFIG_MITIGATION_RETPOLINE, clang emits no indirect jump, and the
switch becomes a tree of compares and conditional branches. That code is
built in, so a kernel booted with mitigations=off still walks the tree
on every system call.
When neither the Spectre v2 nor the BHI mitigation is enabled, nothing in
the kernel protects indirect branches, and the switch buys nothing. Use a
static key to call through sys_call_table[] in that case.
The compiler emits that call through a retpoline thunk, which boot-time
patching turns into a plain indirect call when retpolines are off.
On next-20261028 built with clang and booted with mitigations=off on a
Xeon Gold 6138 at a fixed 2.0 GHz, perf bench syscall basic (getppid)
and getpgid. Mean and standard deviation of 30 to 45 runs over 2 to 3
boots, and kernel instructions and cycles per call from perf stat:
CPU_MITIGATIONS=n =y =y + patch
getppid() ns 131.2 +- 0.2 134.1 +- 0.1 131.7 +- 0.4
insns/cyc 310 / 212.1 331 / 218.1 314 / 213.1
getpgid() ns 129.8 +- 0.3 132.5 +- 0.2 130.0 +- 0.5
insns/cyc 302 / 209.4 323 / 214.1 306 / 209.8
Kernels that enable either mitigation keep the switch, behind one extra
jump.
Signed-off-by: Breno Leitao <leitao@debian.org>
---
The reason behind this change is to make the mitigations a boot command
line option rather than a compilation option.
It is common to find different kernel configurations where the major
difference is CONFIG_CPU_MITIGATIONS.
With this patch, CONFIG_CPU_MITIGATIONS can be enabled at build time, and
when the mitigations are disabled at boot, system call dispatch has no
performance impact.
---
arch/x86/entry/syscall_64.c | 15 +++++++++++----
arch/x86/include/asm/syscall.h | 3 ++-
arch/x86/kernel/cpu/bugs.c | 12 ++++++++++++
3 files changed, 25 insertions(+), 5 deletions(-)
diff --git a/arch/x86/entry/syscall_64.c b/arch/x86/entry/syscall_64.c
index 966d3d1b75868..1da8b6c90171e 100644
--- a/arch/x86/entry/syscall_64.c
+++ b/arch/x86/entry/syscall_64.c
@@ -21,9 +21,8 @@
#define __SYSCALL_NORETURN __SYSCALL
/*
- * The sys_call_table[] is no longer used for system calls, but
- * kernel/trace/trace_syscalls.c still wants to know the system
- * call address.
+ * Used for dispatch only when x64_syscall_use_table is enabled;
+ * kernel/trace/trace_syscalls.c also wants the system call addresses.
*/
#define __SYSCALL(nr, sym) __x64_##sym,
const sys_call_ptr_t sys_call_table[] = {
@@ -31,6 +30,11 @@ const sys_call_ptr_t sys_call_table[] = {
};
#undef __SYSCALL
+/*
+ * Decide about syscall dispatch method: x64_sys_call() or sys_call_table
+ */
+DEFINE_STATIC_KEY_FALSE(x64_syscall_use_table);
+
#define __SYSCALL(nr, sym) case nr: return __x64_##sym(regs);
/* The unsigned int @nr argument is intentional as it creates denser code */
@@ -58,7 +62,10 @@ static __always_inline bool do_syscall_x64(struct pt_regs *regs, unsigned long n
{
if (likely(nr < NR_syscalls)) {
nr = array_index_nospec(nr, NR_syscalls);
- regs->ax = x64_sys_call(regs, (unsigned int)nr);
+ if (static_branch_likely(&x64_syscall_use_table))
+ regs->ax = sys_call_table[nr](regs);
+ else
+ regs->ax = x64_sys_call(regs, (unsigned int)nr);
return true;
}
return false;
diff --git a/arch/x86/include/asm/syscall.h b/arch/x86/include/asm/syscall.h
index cfe4251fc1fe3..509449a70febc 100644
--- a/arch/x86/include/asm/syscall.h
+++ b/arch/x86/include/asm/syscall.h
@@ -13,12 +13,13 @@
#include <uapi/linux/audit.h>
#include <linux/sched.h>
#include <linux/err.h>
+#include <linux/jump_label.h>
#include <asm/thread_info.h> /* for TS_COMPAT */
#include <asm/unistd.h>
-/* This is used purely for kernel/trace/trace_syscalls.c */
typedef long (*sys_call_ptr_t)(const struct pt_regs *);
extern const sys_call_ptr_t sys_call_table[];
+DECLARE_STATIC_KEY_FALSE(x64_syscall_use_table);
/*
* Only the low 32 bits of orig_ax are meaningful, so we return int.
diff --git a/arch/x86/kernel/cpu/bugs.c b/arch/x86/kernel/cpu/bugs.c
index 000ebee2d247b..1823b4d963de3 100644
--- a/arch/x86/kernel/cpu/bugs.c
+++ b/arch/x86/kernel/cpu/bugs.c
@@ -32,6 +32,7 @@
#include <asm/hypervisor.h>
#include <asm/tlbflush.h>
#include <asm/cpu.h>
+#include <asm/syscall.h>
#include "cpu.h"
@@ -2134,6 +2135,16 @@ static void __init bhi_apply_mitigation(void)
setup_force_cpu_cap(X86_FEATURE_CLEAR_BHB_VMEXIT);
}
+static void __init syscall_dispatch_apply_mitigation(void)
+{
+ if (!IS_ENABLED(CONFIG_X86_64) || spectre_v2_enabled != SPECTRE_V2_NONE ||
+ bhi_mitigation != BHI_MITIGATION_OFF)
+ return;
+
+ pr_info("BHI mitigation off, dispatching syscalls through sys_call_table\n");
+ static_branch_enable(&x64_syscall_use_table);
+}
+
static void __init spectre_v2_select_mitigation(void)
{
if ((spectre_v2_cmd == SPECTRE_V2_CMD_RETPOLINE ||
@@ -3371,6 +3382,7 @@ void __init cpu_select_mitigations(void)
bhi_apply_mitigation();
tsa_apply_mitigation();
vmscape_apply_mitigation();
+ syscall_dispatch_apply_mitigation();
}
#ifdef CONFIG_SYSFS
---
base-commit: 6d0933ae9d46674a6ea63a87e9e9ab4e662670c9
base-tree: git://git.kernel.org/pub/scm/linux/kernel/git/next/linux-next.git master
change-id: 20261028-syscall-table-dispatch-03d30189dc5b
Best regards,
--
Breno Leitao <leitao@debian.org>
next reply other threads:[~2026-10-29 10:30 UTC|newest]
Thread overview: 4+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-10-29 10:27 Breno Leitao [this message]
2026-10-29 10:49 ` Peter Zijlstra
2026-10-29 12:30 ` Breno Leitao
2026-10-29 12:39 ` Peter Zijlstra
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20261029-syscall-table-dispatch-v1-1-bca9dbb886de@debian.org \
--to=leitao@debian.org \
--cc=bp@alien8.de \
--cc=dave.hansen@linux.intel.com \
--cc=hpa@zytor.com \
--cc=jpoimboe@kernel.org \
--cc=justinstitt@google.com \
--cc=kernel-team@meta.com \
--cc=linux-kernel@vger.kernel.org \
--cc=llvm@lists.linux.dev \
--cc=luto@kernel.org \
--cc=mingo@redhat.com \
--cc=morbo@google.com \
--cc=nathan@kernel.org \
--cc=ndesaulniers@google.com \
--cc=pawan.kumar.gupta@linux.intel.com \
--cc=peterz@infradead.org \
--cc=tglx@kernel.org \
--cc=torvalds@linux-foundation.org \
--cc=x86@kernel.org \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox
all inboxes | Powered by JetHome®