Re: [PATCH 4/6] x86_64/entry: Mark all syscalls __noendbr

From: David Laight

Date: Mon Nov 02 2026 - 14:01:14 EST


On Mon, 02 Nov 2026 12:25:53 +0100
Peter Zijlstra <peterz@xxxxxxxxxxxxx> wrote:

> Since system call dispatch is now strictly through direct calls, there is no
> reason for the syscalls to be called indirectly. Mark them all __noendbr.
>
> Notably, the sys_call_table[] is purely a lookup for tracing and the like,
> nobody actually (should) dispatch anything through it, and with this we make
> sure nobody *can*.
>
> Signed-off-by: Peter Zijlstra (Intel) <peterz@xxxxxxxxxxxxx>
> ---
> arch/x86/entry/syscall_32.c | 5 +++--
> arch/x86/entry/syscall_64.c | 7 ++++---
> arch/x86/include/asm/syscall_wrapper.h | 23 ++++++++++++-----------
> tools/objtool/check.c | 9 ++++++++-
> 4 files changed, 27 insertions(+), 17 deletions(-)
>
> --- a/arch/x86/entry/syscall_32.c
> +++ b/arch/x86/entry/syscall_32.c
> @@ -12,6 +12,7 @@
> #include <asm/traps.h>
> #include <asm/cpufeature.h>
> #include <asm/syscall.h>
> +#include <asm/ibt.h>
>
> #ifdef CONFIG_IA32_EMULATION
> #define __SYSCALL_WITH_COMPAT(nr, native, compat) __SYSCALL(nr, compat)
> @@ -19,8 +20,8 @@
> #define __SYSCALL_WITH_COMPAT(nr, native, compat) __SYSCALL(nr, native)
> #endif
>
> -#define __SYSCALL(nr, sym) extern long __ia32_##sym(const struct pt_regs *);
> -#define __SYSCALL_NORETURN(nr, sym) extern long __noreturn __ia32_##sym(const struct pt_regs *);
> +#define __SYSCALL(nr, sym) extern __noendbr long __ia32_##sym(const struct pt_regs *);
> +#define __SYSCALL_NORETURN(nr, sym) extern __noendbr long __noreturn __ia32_##sym(const struct pt_regs *);
> #include <asm/syscalls_32.h>
> #undef __SYSCALL
>
> --- a/arch/x86/entry/syscall_64.c
> +++ b/arch/x86/entry/syscall_64.c
> @@ -8,9 +8,10 @@
> #include <linux/entry-common.h>
> #include <linux/nospec.h>
> #include <asm/syscall.h>
> +#include <asm/ibt.h>
>
> -#define __SYSCALL(nr, sym) extern long __x64_##sym(const struct pt_regs *);
> -#define __SYSCALL_NORETURN(nr, sym) extern long __noreturn __x64_##sym(const struct pt_regs *);
> +#define __SYSCALL(nr, sym) extern __noendbr long __x64_##sym(const struct pt_regs *);
> +#define __SYSCALL_NORETURN(nr, sym) extern __noendbr long __noreturn __x64_##sym(const struct pt_regs *);
> #include <asm/syscalls_64.h>
> #ifdef CONFIG_X86_X32_ABI
> #include <asm/syscalls_x32.h>
> @@ -25,7 +26,7 @@
> * kernel/trace/trace_syscalls.c still wants to know the system
> * call address.
> */
> -#define __SYSCALL(nr, sym) __x64_##sym,
> +#define __SYSCALL(nr, sym) (void *)&__x64_##sym,
> const sys_call_ptr_t sys_call_table[] = {
> #include <asm/syscalls_64.h>
> };
> --- a/arch/x86/include/asm/syscall_wrapper.h
> +++ b/arch/x86/include/asm/syscall_wrapper.h
> @@ -7,9 +7,10 @@
> #define _ASM_X86_SYSCALL_WRAPPER_H
>
> #include <asm/ptrace.h>
> +#include <asm/ibt.h>
>
> -extern long __x64_sys_ni_syscall(const struct pt_regs *regs);
> -extern long __ia32_sys_ni_syscall(const struct pt_regs *regs);
> +extern __noendbr long __x64_sys_ni_syscall(const struct pt_regs *regs);
> +extern __noendbr long __ia32_sys_ni_syscall(const struct pt_regs *regs);
>
> /*
> * Instead of the generic __SYSCALL_DEFINEx() definition, the x86 version takes
> @@ -83,22 +84,22 @@ extern long __ia32_sys_ni_syscall(const
> __MAP(x, __SC_TYPE, __VA_ARGS__)) \
>
> #define __SYS_STUB0(abi, name) \
> - long __##abi##_##name(const struct pt_regs *regs); \
> + long __noendbr __##abi##_##name(const struct pt_regs *regs); \
> ALLOW_ERROR_INJECTION(__##abi##_##name, ERRNO); \
> - long __##abi##_##name(const struct pt_regs *regs) \
> + long __noendbr __##abi##_##name(const struct pt_regs *regs) \
> __alias(__do_##name);
>
> #define __SYS_STUBx(abi, name, ...) \
> - long __##abi##_##name(const struct pt_regs *regs); \
> + long __noendbr __##abi##_##name(const struct pt_regs *regs); \
> ALLOW_ERROR_INJECTION(__##abi##_##name, ERRNO); \
> - long __##abi##_##name(const struct pt_regs *regs) \
> + long __noendbr __##abi##_##name(const struct pt_regs *regs) \
> { \
> return __se_##name(__VA_ARGS__); \
> }
>
> #define __COND_SYSCALL(abi, name) \
> - __weak long __##abi##_##name(const struct pt_regs *__unused); \
> - __weak long __##abi##_##name(const struct pt_regs *__unused) \
> + __weak long __noendbr __##abi##_##name(const struct pt_regs *__unused); \
> + __weak long __noendbr __##abi##_##name(const struct pt_regs *__unused) \
> { \
> return sys_ni_syscall(); \
> }
> @@ -257,8 +258,8 @@ extern long __ia32_sys_ni_syscall(const
> * For VSYSCALLS, we need to declare these three syscalls with the new
> * pt_regs-based calling convention for in-kernel use.
> */
> -long __x64_sys_getcpu(const struct pt_regs *regs);
> -long __x64_sys_gettimeofday(const struct pt_regs *regs);
> -long __x64_sys_time(const struct pt_regs *regs);
> +long __noendbr __x64_sys_getcpu(const struct pt_regs *regs);
> +long __noendbr __x64_sys_gettimeofday(const struct pt_regs *regs);
> +long __noendbr __x64_sys_time(const struct pt_regs *regs);
>
> #endif /* _ASM_X86_SYSCALL_WRAPPER_H */
> --- a/tools/objtool/check.c
> +++ b/tools/objtool/check.c
> @@ -4672,8 +4672,15 @@ static int validate_ibt(struct objtool_f
> !strcmp(sec->name, "__patchable_function_entries"))
> continue;
>
> - for_each_reloc(sec->rsec, reloc)
> + for_each_reloc(sec->rsec, reloc) {
> + struct symbol *sym = find_symbol_containing(reloc->sec->base,
> + reloc_offset(reloc));
> +
> + if (sym && !strcmp(sym->name, "sys_call_table"))
> + continue;

Does that slow things down somewhat?
Could you do it the other way around??
So remember the bounds of sys_call_table and just check whether the relocation
in inside it.

Try building x86-64 allyesconfig :-)

David

> +
> warnings += validate_ibt_data_reloc(file, reloc);
> + }
> }
>
> return warnings;
>
>
>