* [PATCH RFC] x86/syscall: Dispatch via sys_call_table when Spectre v2 and BHI are off
@ 2026-10-29 10:27 Breno Leitao
2026-10-29 10:49 ` Peter Zijlstra
0 siblings, 1 reply; 4+ messages in thread
From: Breno Leitao @ 2026-10-29 10:27 UTC (permalink / raw)
To: Andy Lutomirski, Thomas Gleixner, Ingo Molnar, Borislav Petkov,
Dave Hansen, x86, H. Peter Anvin, Peter Zijlstra, Josh Poimboeuf,
Pawan Gupta, Nathan Chancellor, Nick Desaulniers, Bill Wendling,
Justin Stitt, torvalds
Cc: linux-kernel, llvm, kernel-team, Breno Leitao
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>
^ permalink raw reply [flat|nested] 4+ messages in thread
* Re: [PATCH RFC] x86/syscall: Dispatch via sys_call_table when Spectre v2 and BHI are off
2026-10-29 10:27 [PATCH RFC] x86/syscall: Dispatch via sys_call_table when Spectre v2 and BHI are off Breno Leitao
@ 2026-10-29 10:49 ` Peter Zijlstra
2026-10-29 12:30 ` Breno Leitao
0 siblings, 1 reply; 4+ messages in thread
From: Peter Zijlstra @ 2026-10-29 10:49 UTC (permalink / raw)
To: Breno Leitao
Cc: Andy Lutomirski, Thomas Gleixner, Ingo Molnar, Borislav Petkov,
Dave Hansen, x86, H. Peter Anvin, Josh Poimboeuf, Pawan Gupta,
Nathan Chancellor, Nick Desaulniers, Bill Wendling, Justin Stitt,
torvalds, linux-kernel, llvm, kernel-team
On Thu, Oct 29, 2026 at 03:27:23AM -0700, Breno Leitao wrote:
> 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.
>
Bah, this reminds me I still have these patches:
https://git.kernel.org/pub/scm/linux/kernel/git/peterz/queue.git/log/?h=x86/syscall
which make your patch impossible. The reason for not dispatching through
the syscall table is that syscall hijacking becomes a _LOT_ more
difficult.
^ permalink raw reply [flat|nested] 4+ messages in thread
* Re: [PATCH RFC] x86/syscall: Dispatch via sys_call_table when Spectre v2 and BHI are off
2026-10-29 10:49 ` Peter Zijlstra
@ 2026-10-29 12:30 ` Breno Leitao
2026-10-29 12:39 ` Peter Zijlstra
0 siblings, 1 reply; 4+ messages in thread
From: Breno Leitao @ 2026-10-29 12:30 UTC (permalink / raw)
To: Peter Zijlstra
Cc: Andy Lutomirski, Thomas Gleixner, Ingo Molnar, Borislav Petkov,
Dave Hansen, x86, H. Peter Anvin, Josh Poimboeuf, Pawan Gupta,
Nathan Chancellor, Nick Desaulniers, Bill Wendling, Justin Stitt,
torvalds, linux-kernel, llvm, kernel-team
Hello Peter,
On Thu, Oct 29, 2026 at 11:49:38AM +0100, Peter Zijlstra wrote:
> On Thu, Oct 29, 2026 at 03:27:23AM -0700, Breno Leitao wrote:
> Bah, this reminds me I still have these patches:
>
> https://git.kernel.org/pub/scm/linux/kernel/git/peterz/queue.git/log/?h=x86/syscall
>
> which make your patch impossible. The reason for not dispatching through
> the syscall table is that syscall hijacking becomes a _LOT_ more
> difficult.
Cool, this would in fact solve my problem as well.
My goal is to make booting with mitigations=off equivalent to building
with CONFIG_CPU_MITIGATIONS=n, so we can ship a single kernel and pick
the mitigations at boot time. Your patches get there in a different way,
but that doesn't matter for my use case.
With "x86_64/entry: Unconditionally use -fno-jump-table", x64_sys_call()
becomes a compare tree on every x86_64 build, so system call dispatch is
the same whether the mitigations are disabled at boot or at build time,
which is basically my end goal.
That said, can I do anything to help those patches land? I see they are
from June, and I am wondering if anything is blocking them.
Also, KBUILD_RISTFLAGS in that patch looks like a typo for
KBUILD_RUSTFLAGS.
Thanks,
--breno
^ permalink raw reply [flat|nested] 4+ messages in thread
* Re: [PATCH RFC] x86/syscall: Dispatch via sys_call_table when Spectre v2 and BHI are off
2026-10-29 12:30 ` Breno Leitao
@ 2026-10-29 12:39 ` Peter Zijlstra
0 siblings, 0 replies; 4+ messages in thread
From: Peter Zijlstra @ 2026-10-29 12:39 UTC (permalink / raw)
To: Breno Leitao
Cc: Andy Lutomirski, Thomas Gleixner, Ingo Molnar, Borislav Petkov,
Dave Hansen, x86, H. Peter Anvin, Josh Poimboeuf, Pawan Gupta,
Nathan Chancellor, Nick Desaulniers, Bill Wendling, Justin Stitt,
torvalds, linux-kernel, llvm, kernel-team
On Thu, Oct 29, 2026 at 05:30:25AM -0700, Breno Leitao wrote:
> Hello Peter,
>
> On Thu, Oct 29, 2026 at 11:49:38AM +0100, Peter Zijlstra wrote:
> > On Thu, Oct 29, 2026 at 03:27:23AM -0700, Breno Leitao wrote:
> > Bah, this reminds me I still have these patches:
> >
> > https://git.kernel.org/pub/scm/linux/kernel/git/peterz/queue.git/log/?h=x86/syscall
> >
> > which make your patch impossible. The reason for not dispatching through
> > the syscall table is that syscall hijacking becomes a _LOT_ more
> > difficult.
>
> Cool, this would in fact solve my problem as well.
>
> My goal is to make booting with mitigations=off equivalent to building
> with CONFIG_CPU_MITIGATIONS=n, so we can ship a single kernel and pick
> the mitigations at boot time. Your patches get there in a different way,
> but that doesn't matter for my use case.
>
> With "x86_64/entry: Unconditionally use -fno-jump-table", x64_sys_call()
> becomes a compare tree on every x86_64 build, so system call dispatch is
> the same whether the mitigations are disabled at boot or at build time,
> which is basically my end goal.
>
> Also, KBUILD_RISTFLAGS in that patch looks like a typo for
> KBUILD_RUSTFLAGS.
Haha, good one, let me fix.
> That said, can I do anything to help those patches land? I see they are
> from June, and I am wondering if anything is blocking them.
Mostly I just completely forgot about them :/
I've since rebased them and went back to look for robot reports on the
tree, and found one weird case where GCC-14 with an UNWIND_FP=y build
insists on not doing tail-calls which then trips up objtool noinst
validation.
I need to figure a way out of the noinstr validation puzzle. Also, lunch
and a school run. I'll try and post them soon.
Thanks!
^ permalink raw reply [flat|nested] 4+ messages in thread
end of thread, other threads:[~2026-10-29 12:39 UTC | newest]
Thread overview: 4+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2026-10-29 10:27 [PATCH RFC] x86/syscall: Dispatch via sys_call_table when Spectre v2 and BHI are off Breno Leitao
2026-10-29 10:49 ` Peter Zijlstra
2026-10-29 12:30 ` Breno Leitao
2026-10-29 12:39 ` Peter Zijlstra
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®