From ee935e8dc757614006ac29a7d98a15882cdcaeb2 Mon Sep 17 00:00:00 2001 From: Gregory Price Date: Mon, 6 Jul 2026 10:00:18 -0400 Subject: [PATCH 01/23] syscall_user_dispatch: Make it configurable in Kconfig Syscall User Dispatch is presently built under CONFIG_GENERIC_SYSCALL and cannot be disabled independently. Add CONFIG_SYSCALL_USER_DISPATCH to make it an optional feature. Signed-off-by: Gregory Price Signed-off-by: Thomas Gleixner Link: https://patch.msgid.link/20260706140020.873735-2-gourry@gourry.net --- arch/Kconfig | 10 +++++++ include/linux/entry-common.h | 6 ++-- include/linux/syscall_user_dispatch.h | 28 +++++++++++++++++-- include/linux/syscall_user_dispatch_types.h | 2 +- kernel/entry/Makefile | 3 +- .../selftests/syscall_user_dispatch/config | 2 +- 6 files changed, 42 insertions(+), 9 deletions(-) diff --git a/arch/Kconfig b/arch/Kconfig index fa7507ac8e13..0c01521c2f3f 100644 --- a/arch/Kconfig +++ b/arch/Kconfig @@ -114,6 +114,16 @@ config GENERIC_ENTRY select GENERIC_IRQ_ENTRY select GENERIC_SYSCALL +config SYSCALL_USER_DISPATCH + bool "Syscall User Dispatch" + depends on GENERIC_ENTRY + default y + help + Syscall User Dispatch lets a thread have its own system calls + intercepted and redirected to a userspace signal handler based + on a prctl() configured instruction pointer range. + If unsure, say Y. + config KPROBES bool "Kprobes" depends on HAVE_KPROBES diff --git a/include/linux/entry-common.h b/include/linux/entry-common.h index 416a3352261f..9336516430a1 100644 --- a/include/linux/entry-common.h +++ b/include/linux/entry-common.h @@ -9,6 +9,7 @@ #include #include #include +#include #include #include @@ -55,7 +56,6 @@ static __always_inline int arch_ptrace_report_syscall_entry(struct pt_regs *regs } #endif -bool syscall_user_dispatch(struct pt_regs *regs); long trace_syscall_enter(struct pt_regs *regs, long syscall); void trace_syscall_exit(struct pt_regs *regs, long ret); @@ -232,10 +232,8 @@ static __always_inline void syscall_exit_work(struct pt_regs *regs, unsigned lon * of these syscalls is unknown. */ if (work & SYSCALL_WORK_SYSCALL_USER_DISPATCH) { - if (unlikely(current->syscall_dispatch.on_dispatch)) { - current->syscall_dispatch.on_dispatch = false; + if (syscall_user_dispatch_clear_on_dispatch()) return; - } } audit_syscall_exit(regs); diff --git a/include/linux/syscall_user_dispatch.h b/include/linux/syscall_user_dispatch.h index 3858a6ffdd5c..3dd30f4b2799 100644 --- a/include/linux/syscall_user_dispatch.h +++ b/include/linux/syscall_user_dispatch.h @@ -6,9 +6,23 @@ #define _SYSCALL_USER_DISPATCH_H #include +#include #include -#ifdef CONFIG_GENERIC_ENTRY +struct pt_regs; + +#ifdef CONFIG_SYSCALL_USER_DISPATCH + +bool syscall_user_dispatch(struct pt_regs *regs); + +static __always_inline bool syscall_user_dispatch_clear_on_dispatch(void) +{ + if (likely(!current->syscall_dispatch.on_dispatch)) + return false; + + current->syscall_dispatch.on_dispatch = false; + return true; +} int set_syscall_user_dispatch(unsigned long mode, unsigned long offset, unsigned long len, char __user *selector); @@ -24,6 +38,16 @@ int syscall_user_dispatch_set_config(struct task_struct *task, unsigned long siz #else +static __always_inline bool syscall_user_dispatch(struct pt_regs *regs) +{ + return false; +} + +static __always_inline bool syscall_user_dispatch_clear_on_dispatch(void) +{ + return false; +} + static inline int set_syscall_user_dispatch(unsigned long mode, unsigned long offset, unsigned long len, char __user *selector) { @@ -46,6 +70,6 @@ static inline int syscall_user_dispatch_set_config(struct task_struct *task, return -EINVAL; } -#endif /* CONFIG_GENERIC_ENTRY */ +#endif /* CONFIG_SYSCALL_USER_DISPATCH */ #endif /* _SYSCALL_USER_DISPATCH_H */ diff --git a/include/linux/syscall_user_dispatch_types.h b/include/linux/syscall_user_dispatch_types.h index 3be36b06c7d7..c0bdd4f760d3 100644 --- a/include/linux/syscall_user_dispatch_types.h +++ b/include/linux/syscall_user_dispatch_types.h @@ -4,7 +4,7 @@ #include -#ifdef CONFIG_GENERIC_ENTRY +#ifdef CONFIG_SYSCALL_USER_DISPATCH struct syscall_user_dispatch { char __user *selector; diff --git a/kernel/entry/Makefile b/kernel/entry/Makefile index 2333d70802e4..f220bae86b12 100644 --- a/kernel/entry/Makefile +++ b/kernel/entry/Makefile @@ -13,5 +13,6 @@ CFLAGS_REMOVE_common.o = -fstack-protector -fstack-protector-strong CFLAGS_common.o += -fno-stack-protector obj-$(CONFIG_GENERIC_IRQ_ENTRY) += common.o -obj-$(CONFIG_GENERIC_SYSCALL) += syscall-common.o syscall_user_dispatch.o +obj-$(CONFIG_GENERIC_SYSCALL) += syscall-common.o +obj-$(CONFIG_SYSCALL_USER_DISPATCH) += syscall_user_dispatch.o obj-$(CONFIG_VIRT_XFER_TO_GUEST_WORK) += virt.o diff --git a/tools/testing/selftests/syscall_user_dispatch/config b/tools/testing/selftests/syscall_user_dispatch/config index 039e303e59d7..22c4dfe167ca 100644 --- a/tools/testing/selftests/syscall_user_dispatch/config +++ b/tools/testing/selftests/syscall_user_dispatch/config @@ -1 +1 @@ -CONFIG_GENERIC_ENTRY=y +CONFIG_SYSCALL_USER_DISPATCH=y From 5b6e32ba7b5967a2787fafaa9f4740c590e1e00b Mon Sep 17 00:00:00 2001 From: Gregory Price Date: Mon, 6 Jul 2026 10:00:19 -0400 Subject: [PATCH 02/23] syscall_user_dispatch: Add kernel.syscall_user_dispatch sysctl Add a matching sysctl to go with CONFIG_SYSCALL_USER_DISPATCH. kernel.syscall_user_dispatch (default 1 - allow) controls whether userspace may arm syscall user dispatch (both via prctl and ptrace). Disarming is always permitted - same semantics as comparable knobs. Disabling while a task has armed syscall user dispatch does not cause it to become inactive - instead it remains active until the user attempts to disable/re-enable via prctl or ptrace. On the next attempt to re-enable, the prctl/ptrace call fails gracefully. The alternative would cause programs translating non-linux syscalls to interpret those syscalls as linux syscalls, resulting in undefined userland behavior. Signed-off-by: Gregory Price Signed-off-by: Thomas Gleixner Link: https://patch.msgid.link/20260706140020.873735-3-gourry@gourry.net --- Documentation/admin-guide/sysctl/kernel.rst | 17 +++++++++ kernel/entry/syscall_user_dispatch.c | 38 +++++++++++++++++---- 2 files changed, 48 insertions(+), 7 deletions(-) diff --git a/Documentation/admin-guide/sysctl/kernel.rst b/Documentation/admin-guide/sysctl/kernel.rst index c6994e55d141..b6328cd0f43e 100644 --- a/Documentation/admin-guide/sysctl/kernel.rst +++ b/Documentation/admin-guide/sysctl/kernel.rst @@ -1402,6 +1402,23 @@ Note that if you change this from 0 to 1, already created segments without users and with a dead originative process will be destroyed. +syscall_user_dispatch +===================== + +Controls whether userspace may arm Syscall User Dispatch via +``prctl(PR_SET_SYSCALL_USER_DISPATCH, ...)`` or the +``PTRACE_SET_SYSCALL_USER_DISPATCH_CONFIG`` ptrace request: + + == =================================================================== + 0 Arming syscall user dispatch is denied with ``-EPERM``. Tasks that + already armed it keep it, and disabling it is always permitted. + 1 (default) Arming syscall user dispatch is permitted. + == =================================================================== + +Only present when the kernel is built with ``CONFIG_SYSCALL_USER_DISPATCH`` +and ``CONFIG_PROC_SYSCTL``. + + sysctl_writes_strict ==================== diff --git a/kernel/entry/syscall_user_dispatch.c b/kernel/entry/syscall_user_dispatch.c index d89dffcc2d64..2002c7aae435 100644 --- a/kernel/entry/syscall_user_dispatch.c +++ b/kernel/entry/syscall_user_dispatch.c @@ -2,21 +2,22 @@ /* * Copyright (C) 2020 Collabora Ltd. */ - +#include #include -#include #include #include -#include -#include -#include -#include - +#include #include #include +#include +#include +#include +#include #include +static bool syscall_user_dispatch_allowed __read_mostly = true; + static void trigger_sigsys(struct pt_regs *regs) { struct kernel_siginfo info; @@ -102,6 +103,10 @@ static int task_set_syscall_user_dispatch(struct task_struct *task, unsigned lon return -EINVAL; } + /* Arming can be denied at runtime via sysctl, disarming is allowed */ + if (mode != PR_SYS_DISPATCH_OFF && !syscall_user_dispatch_allowed) + return -EPERM; + /* * access_ok() will clear memory tags for tagged addresses * if current has memory tagging enabled. @@ -172,3 +177,22 @@ int syscall_user_dispatch_set_config(struct task_struct *task, unsigned long siz return task_set_syscall_user_dispatch(task, cfg.mode, cfg.offset, cfg.len, (char __user *)(uintptr_t)cfg.selector); } + +#ifdef CONFIG_PROC_SYSCTL +static const struct ctl_table syscall_user_dispatch_sysctls[] = { + { + .procname = "syscall_user_dispatch", + .data = &syscall_user_dispatch_allowed, + .maxlen = sizeof(syscall_user_dispatch_allowed), + .mode = 0644, + .proc_handler = proc_dobool, + }, +}; + +static int __init syscall_user_dispatch_sysctl_init(void) +{ + register_sysctl_init("kernel", syscall_user_dispatch_sysctls); + return 0; +} +late_initcall(syscall_user_dispatch_sysctl_init); +#endif /* CONFIG_PROC_SYSCTL */ From 89d163dd9dacb028773244dba0a2c86f3a6b74b7 Mon Sep 17 00:00:00 2001 From: Thomas Gleixner Date: Tue, 7 Jul 2026 21:05:58 +0200 Subject: [PATCH 03/23] powerpc: Move stack randomization after syscall_enter_from_user_mode() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit add_random_kstack_offset() is invoked before syscall_enter_from_user_mode() establishes state. That's wrong because add_random_kstack_offset() calls into instrumentable code. Move it after syscall_enter_from_user_mode() to ensure that state is correctly established. Signed-off-by: Thomas Gleixner Tested-by: Mukesh Kumar Chaurasiya (IBM) Reviewed-by: Shrikanth Hegde Reviewed-by: Radu Rendec Reviewed-by: Jinjie Ruan Reviewed-by: Philippe Mathieu-Daudé Reviewed-by: Mukesh Kumar Chaurasiya (IBM) Link: https://patch.msgid.link/20260707190253.718191130@kernel.org --- arch/powerpc/kernel/syscall.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/arch/powerpc/kernel/syscall.c b/arch/powerpc/kernel/syscall.c index a9da2af6efa8..ee07b2ed5029 100644 --- a/arch/powerpc/kernel/syscall.c +++ b/arch/powerpc/kernel/syscall.c @@ -19,8 +19,8 @@ notrace long system_call_exception(struct pt_regs *regs, unsigned long r0) long ret; syscall_fn f; - add_random_kstack_offset(); r0 = syscall_enter_from_user_mode(regs, r0); + add_random_kstack_offset(); if (unlikely(r0 >= NR_syscalls)) { if (unlikely(trap_is_unsupported_scv(regs))) { From 0b9a3057530cc1c3f87ce2ab9d1278164e81990f Mon Sep 17 00:00:00 2001 From: Thomas Gleixner Date: Tue, 7 Jul 2026 21:06:02 +0200 Subject: [PATCH 04/23] randomize_kstack: Provide add_random_kstack_offset_irqsoff() add_random_kstack_offset() uses get/put_cpu_var() which is pointless overhead when it is invoked from low level entry code with interrupts disabled. Provide a irqsoff() variant, which avoids that. Signed-off-by: Thomas Gleixner Reviewed-by: Radu Rendec Reviewed-by: Kees Cook Reviewed-by: Mukesh Kumar Chaurasiya (IBM) Link: https://patch.msgid.link/20260707190253.768842729@kernel.org --- include/linux/randomize_kstack.h | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/include/linux/randomize_kstack.h b/include/linux/randomize_kstack.h index 024fc20e7762..d8b939f50bba 100644 --- a/include/linux/randomize_kstack.h +++ b/include/linux/randomize_kstack.h @@ -77,8 +77,27 @@ static __always_inline u32 get_kstack_offset(void) } \ } while (0) +/** + * add_random_kstack_offset_irqsoff - Increase stack utilization by a random offset. + * + * This should be used in the syscall entry path after user registers have been + * stored to the stack. Interrupts must be still disabled. + */ +#define add_random_kstack_offset_irqsoff() \ +do { \ + lockdep_assert_irqs_disabled(); \ + if (static_branch_maybe(CONFIG_RANDOMIZE_KSTACK_OFFSET_DEFAULT, \ + &randomize_kstack_offset)) { \ + u32 offset = prandom_u32_state(raw_cpu_ptr(&kstack_rnd_state)); \ + u8 *ptr = __kstack_alloca(KSTACK_OFFSET_MAX(offset)); \ + /* Keep allocation even after "ptr" loses scope. */ \ + asm volatile("" :: "r"(ptr) : "memory"); \ + } \ +} while (0) + #else /* CONFIG_RANDOMIZE_KSTACK_OFFSET */ #define add_random_kstack_offset() do { } while (0) +#define add_random_kstack_offset_irqsoff() do { } while (0) #endif /* CONFIG_RANDOMIZE_KSTACK_OFFSET */ #endif From 855c103f86275a55eba115cabb86eb84f8236933 Mon Sep 17 00:00:00 2001 From: Thomas Gleixner Date: Tue, 7 Jul 2026 21:06:07 +0200 Subject: [PATCH 05/23] entry: Provide [syscall_]enter_from_user_mode_randomize_stack() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Randomizing the syscall stack can only happen after state is established via enter_from_user_mode() or syscall_enter_from_user_mode(). The earlier it happens the better. Provide two new macros to consolidate that: - enter_from_user_mode_randomize_stack() enter_from_user_mode(); add_random_kstack_offset_irqsoff(); - syscall_enter_from_user_mode_randomize_stack() enter_from_user_mode_randomize_stack(); syscall_enter_from_user_mode_work(); to reduce boiler plate code. Those are macros and not inline functions as the latter would limit the stack randomization scope to the inline function itself. Signed-off-by: Thomas Gleixner Tested-by: Mukesh Kumar Chaurasiya (IBM) Reviewed-by: Radu Rendec Reviewed-by: Jinjie Ruan Reviewed-by: Philippe Mathieu-Daudé Reviewed-by: Mukesh Kumar Chaurasiya (IBM) Link: https://patch.msgid.link/20260707190253.816918647@kernel.org --- include/linux/entry-common.h | 56 ++++++++++++++++++++++++++++++++++++ 1 file changed, 56 insertions(+) diff --git a/include/linux/entry-common.h b/include/linux/entry-common.h index 9336516430a1..166b1ed9a9c0 100644 --- a/include/linux/entry-common.h +++ b/include/linux/entry-common.h @@ -6,6 +6,7 @@ #include #include #include +#include #include #include #include @@ -149,6 +150,61 @@ static __always_inline long syscall_enter_from_user_mode_work(struct pt_regs *re return syscall; } +/** + * enter_from_user_mode_randomize_stack - Establish state and add stack randomization + * before invoking syscall_enter_from_user_mode_work() + * @regs: Pointer to currents pt_regs + * + * Invoked from architecture specific syscall entry code with interrupts + * disabled. The calling code has to be non-instrumentable. When the function + * returns all state is correct, interrupts are still disabled and the + * subsequent functions can be instrumented. + * + * Implemented as a macro so that the stack randomization is effective + * throughout the function in which it is invoked. An inline would only make it + * effective in the scope of the inline function. + */ +#define enter_from_user_mode_randomize_stack(regs) \ +do { \ + enter_from_user_mode(regs); \ + instrumentation_begin(); \ + add_random_kstack_offset_irqsoff(); \ + instrumentation_end(); \ +} while (0) + +/** + * syscall_enter_from_user_mode_randomize_stack - Establish state and check and handle work + * before invoking a syscall + * @regs: Pointer to currents pt_regs + * @syscall: The syscall number + * + * Invoked from architecture specific syscall entry code with interrupts + * disabled. The calling code has to be non-instrumentable. When the + * function returns all state is correct, interrupts are enabled and the + * subsequent functions can be instrumented. + * + * This is the combination of enter_from_user_mode_randomize_stack() and + * syscall_enter_from_user_mode_work() to be used when there is no + * architecture specific work to be done between the two. + * + * Returns: The original or a modified syscall number. See + * syscall_enter_from_user_mode_work() for further explanation. + * + * Implemented as a macro to make stack randomization effective in the calling + * scope. + */ +#define syscall_enter_from_user_mode_randomize_stack(regs, syscall) \ +({ \ + enter_from_user_mode_randomize_stack(regs); \ + \ + instrumentation_begin(); \ + local_irq_enable(); \ + long _ret = syscall_enter_from_user_mode_work(regs, syscall); \ + instrumentation_end(); \ + \ + _ret; \ +}) + /** * syscall_enter_from_user_mode - Establish state and check and handle work * before invoking a syscall From 7892c5a22a4e11d1ed3590c04ff73f047c0ce453 Mon Sep 17 00:00:00 2001 From: Thomas Gleixner Date: Tue, 7 Jul 2026 21:06:11 +0200 Subject: [PATCH 06/23] loongarch/syscall: Use syscall_enter_from_user_mode_randomize_stack() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit syscall_enter_from_user_mode_randomize_stack() replaces syscall_enter_from_user_mode() and the subsequent invocation of add_random_kstack_offset(). The advantage is that it applies the stack randomization right after enter_from_user_mode() and thereby avoids the overhead of get/put_cpu_var() as that code is invoked with interrupts disabled. No functional change. Signed-off-by: Thomas Gleixner Reviewed-by: Radu Rendec Reviewed-by: Jinjie Ruan Reviewed-by: Philippe Mathieu-Daudé Reviewed-by: Mukesh Kumar Chaurasiya (IBM) Link: https://patch.msgid.link/20260707190253.865955911@kernel.org --- arch/loongarch/kernel/syscall.c | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/arch/loongarch/kernel/syscall.c b/arch/loongarch/kernel/syscall.c index 94c1c3b5b0b5..142a9c20292c 100644 --- a/arch/loongarch/kernel/syscall.c +++ b/arch/loongarch/kernel/syscall.c @@ -11,7 +11,6 @@ #include #include #include -#include #include #include @@ -70,9 +69,7 @@ void noinstr __no_stack_protector do_syscall(struct pt_regs *regs) regs->orig_a0 = regs->regs[4]; regs->regs[4] = -ENOSYS; - nr = syscall_enter_from_user_mode(regs, nr); - - add_random_kstack_offset(); + nr = syscall_enter_from_user_mode_randomize_stack(regs, nr); if (nr < NR_syscalls) { syscall_fn = sys_call_table[array_index_nospec(nr, NR_syscalls)]; From 3b2b9c0198bbcf077f201cd693a0efc597d05405 Mon Sep 17 00:00:00 2001 From: Thomas Gleixner Date: Tue, 7 Jul 2026 21:06:15 +0200 Subject: [PATCH 07/23] powerpc/syscall: Use syscall_enter_from_user_mode_randomize_stack() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit syscall_enter_from_user_mode_randomize_stack() replaces syscall_enter_from_user_mode() and the subsequent invocation of add_random_kstack_offset(). The advantage is that it applies the stack randomization right after enter_from_user_mode() and thereby avoids the overhead of get/put_cpu_var() as that code is invoked with interrupts disabled. No functional change. Signed-off-by: Thomas Gleixner Tested-by: Mukesh Kumar Chaurasiya (IBM) Reviewed-by: Radu Rendec Reviewed-by: Jinjie Ruan Reviewed-by: Philippe Mathieu-Daudé Reviewed-by: Mukesh Kumar Chaurasiya (IBM) Link: https://patch.msgid.link/20260707190253.918861529@kernel.org --- arch/powerpc/kernel/syscall.c | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/arch/powerpc/kernel/syscall.c b/arch/powerpc/kernel/syscall.c index ee07b2ed5029..b4d0159e29f8 100644 --- a/arch/powerpc/kernel/syscall.c +++ b/arch/powerpc/kernel/syscall.c @@ -2,7 +2,6 @@ #include #include -#include #include #include @@ -19,8 +18,7 @@ notrace long system_call_exception(struct pt_regs *regs, unsigned long r0) long ret; syscall_fn f; - r0 = syscall_enter_from_user_mode(regs, r0); - add_random_kstack_offset(); + r0 = syscall_enter_from_user_mode_randomize_stack(regs, r0); if (unlikely(r0 >= NR_syscalls)) { if (unlikely(trap_is_unsupported_scv(regs))) { From d023abfbc4dca9ad9c9928b5b5a38b1628ee1a5d Mon Sep 17 00:00:00 2001 From: Thomas Gleixner Date: Tue, 7 Jul 2026 21:06:19 +0200 Subject: [PATCH 08/23] riscv/syscall: Use syscall_enter_from_user_mode_randomize_stack() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit syscall_enter_from_user_mode_randomize_stack() replaces syscall_enter_from_user_mode() and the subsequent invocation of add_random_kstack_offset(). The advantage is that it applies the stack randomization right after enter_from_user_mode() and thereby avoids the overhead of get/put_cpu_var() as that code is invoked with interrupts disabled. No functional change. Signed-off-by: Thomas Gleixner Reviewed-by: Radu Rendec Reviewed-by: Jinjie Ruan Reviewed-by: Philippe Mathieu-Daudé Reviewed-by: Mukesh Kumar Chaurasiya (IBM) Link: https://patch.msgid.link/20260707190253.974626922@kernel.org --- arch/riscv/kernel/traps.c | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/arch/riscv/kernel/traps.c b/arch/riscv/kernel/traps.c index 8c62c771a656..bb180116ef0a 100644 --- a/arch/riscv/kernel/traps.c +++ b/arch/riscv/kernel/traps.c @@ -7,7 +7,6 @@ #include #include #include -#include #include #include #include @@ -333,9 +332,7 @@ void do_trap_ecall_u(struct pt_regs *regs) riscv_v_vstate_discard(regs); - syscall = syscall_enter_from_user_mode(regs, syscall); - - add_random_kstack_offset(); + syscall = syscall_enter_from_user_mode_randomize_stack(regs, syscall); if (syscall >= 0 && syscall < NR_syscalls) { syscall = array_index_nospec(syscall, NR_syscalls); From 05a194d8bd005e0887a1e089bbbbd2e1a641638f Mon Sep 17 00:00:00 2001 From: Thomas Gleixner Date: Tue, 7 Jul 2026 21:06:23 +0200 Subject: [PATCH 09/23] s390/syscall: Use enter_from_user_mode_randomize_stack() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit enter_from_user_mode_randomize_stack() replaces enter_from_user_mode() and the subsequent invocation of add_random_kstack_offset_irqsoff(). As a bonus this avoids the overhead of get/put_cpu_var() in add_random_kstack_offset(). No functional change. Signed-off-by: Thomas Gleixner Tested-by: Mukesh Kumar Chaurasiya (IBM) Reviewed-by: Sven Schnelle Reviewed-by: Radu Rendec Reviewed-by: Jinjie Ruan Reviewed-by: Mukesh Kumar Chaurasiya (IBM) Reviewed-by: Philippe Mathieu-Daudé Link: https://patch.msgid.link/20260707190254.030598804@kernel.org --- arch/s390/kernel/syscall.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/arch/s390/kernel/syscall.c b/arch/s390/kernel/syscall.c index 75d5a3cab14e..73abda2a9080 100644 --- a/arch/s390/kernel/syscall.c +++ b/arch/s390/kernel/syscall.c @@ -97,8 +97,8 @@ void noinstr __do_syscall(struct pt_regs *regs, int per_trap) { unsigned long nr; - enter_from_user_mode(regs); - add_random_kstack_offset(); + enter_from_user_mode_randomize_stack(regs); + regs->psw = get_lowcore()->svc_old_psw; regs->int_code = get_lowcore()->svc_int_code; update_timer_sys(); From 9d311796e2ce75b3aaded2719a01d7e248c3a24f Mon Sep 17 00:00:00 2001 From: Thomas Gleixner Date: Tue, 7 Jul 2026 21:06:27 +0200 Subject: [PATCH 10/23] x86/syscall: Use [syscall_]enter_from_user_mode_randomize_stack() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit These functions integrate the stack randomization. syscall_enter_from_user_mode_randomize_stack() has the advantage that the randomization happens early right after enter_from_user_mode(). In both cases also the overhead of get/put_cpu_var() in add_random_kstack_offset() is avoided. No functional change. Signed-off-by: Thomas Gleixner Tested-by: Mukesh Kumar Chaurasiya (IBM) Reviewed-by: Radu Rendec Reviewed-by: Jinjie Ruan Reviewed-by: Philippe Mathieu-Daudé Reviewed-by: Mukesh Kumar Chaurasiya (IBM) Link: https://patch.msgid.link/20260707190254.079478122@kernel.org --- arch/x86/entry/syscall_32.c | 19 +++++-------------- arch/x86/entry/syscall_64.c | 3 +-- arch/x86/include/asm/entry-common.h | 1 - 3 files changed, 6 insertions(+), 17 deletions(-) diff --git a/arch/x86/entry/syscall_32.c b/arch/x86/entry/syscall_32.c index 31b9492fe851..2b13f1c842b8 100644 --- a/arch/x86/entry/syscall_32.c +++ b/arch/x86/entry/syscall_32.c @@ -142,10 +142,9 @@ __visible noinstr void do_int80_emulation(struct pt_regs *regs) * int80_is_external() below which calls into the APIC driver. * Identical for soft and external interrupts. */ - enter_from_user_mode(regs); + enter_from_user_mode_randomize_stack(regs); instrumentation_begin(); - add_random_kstack_offset(); /* Validate that this is a soft interrupt to the extent possible */ if (unlikely(int80_is_external())) @@ -210,11 +209,9 @@ DEFINE_FREDENTRY_RAW(int80_emulation) { int nr; - enter_from_user_mode(regs); + enter_from_user_mode_randomize_stack(regs); instrumentation_begin(); - add_random_kstack_offset(); - /* * FRED pushed 0 into regs::orig_ax and regs::ax contains the * syscall number. @@ -252,10 +249,10 @@ __visible noinstr void do_int80_syscall_32(struct pt_regs *regs) * orig_ax, the int return value truncates it. This matches * the semantics of syscall_get_nr(). */ - nr = syscall_enter_from_user_mode(regs, nr); + nr = syscall_enter_from_user_mode_randomize_stack(regs, nr); + instrumentation_begin(); - add_random_kstack_offset(); do_syscall_32_irqs_on(regs, nr); instrumentation_end(); @@ -268,15 +265,9 @@ static noinstr bool __do_fast_syscall_32(struct pt_regs *regs) int nr = syscall_32_enter(regs); int res; - /* - * This cannot use syscall_enter_from_user_mode() as it has to - * fetch EBP before invoking any of the syscall entry work - * functions. - */ - enter_from_user_mode(regs); + enter_from_user_mode_randomize_stack(regs); instrumentation_begin(); - add_random_kstack_offset(); local_irq_enable(); /* Fetch EBP from where the vDSO stashed it. */ if (IS_ENABLED(CONFIG_X86_64)) { diff --git a/arch/x86/entry/syscall_64.c b/arch/x86/entry/syscall_64.c index 71f032504e73..0acd7e99e8e0 100644 --- a/arch/x86/entry/syscall_64.c +++ b/arch/x86/entry/syscall_64.c @@ -86,10 +86,9 @@ static __always_inline bool do_syscall_x32(struct pt_regs *regs, int nr) /* Returns true to return using SYSRET, or false to use IRET */ __visible noinstr bool do_syscall_64(struct pt_regs *regs, int nr) { - nr = syscall_enter_from_user_mode(regs, nr); + nr = syscall_enter_from_user_mode_randomize_stack(regs, nr); instrumentation_begin(); - add_random_kstack_offset(); if (!do_syscall_x64(regs, nr) && !do_syscall_x32(regs, nr) && nr != -1) { /* Invalid system call, but still a system call. */ diff --git a/arch/x86/include/asm/entry-common.h b/arch/x86/include/asm/entry-common.h index eca24b5e07f4..3fed46bd9685 100644 --- a/arch/x86/include/asm/entry-common.h +++ b/arch/x86/include/asm/entry-common.h @@ -2,7 +2,6 @@ #ifndef _ASM_X86_ENTRY_COMMON_H #define _ASM_X86_ENTRY_COMMON_H -#include #include #include From bad2a27ba8c4a6bd2c47e598898d9dbd0f98511f Mon Sep 17 00:00:00 2001 From: Thomas Gleixner Date: Tue, 7 Jul 2026 21:06:32 +0200 Subject: [PATCH 11/23] entry: Remove syscall_enter_from_user_mode() All architecture use either: nr = syscall_enter_from_user_mode_randomize_stack(regs, nr); or enter_from_user_mode_randomize_stack(regs); nr = syscall_enter_from_user_mode_work(regs, nr); Remove the now unused function. Signed-off-by: Thomas Gleixner Tested-by: Mukesh Kumar Chaurasiya (IBM) Reviewed-by: Jinjie Ruan Reviewed-by: Mukesh Kumar Chaurasiya (IBM) Link: https://patch.msgid.link/20260707190254.132654198@kernel.org --- Documentation/core-api/entry.rst | 17 ++++++++------ include/linux/entry-common.h | 40 ++++---------------------------- include/linux/irq-entry-common.h | 6 ++--- 3 files changed, 17 insertions(+), 46 deletions(-) diff --git a/Documentation/core-api/entry.rst b/Documentation/core-api/entry.rst index 71d8eedc0549..44e9608038ea 100644 --- a/Documentation/core-api/entry.rst +++ b/Documentation/core-api/entry.rst @@ -68,7 +68,7 @@ invoked from low-level assembly code looks like this: noinstr void syscall(struct pt_regs *regs, int nr) { arch_syscall_enter(regs); - nr = syscall_enter_from_user_mode(regs, nr); + nr = syscall_enter_from_user_mode_randomize_stack(regs, nr); instrumentation_begin(); if (!invoke_syscall(regs, nr) && nr != -1) @@ -78,12 +78,14 @@ invoked from low-level assembly code looks like this: syscall_exit_to_user_mode(regs); } -syscall_enter_from_user_mode() first invokes enter_from_user_mode() which -establishes state in the following order: +syscall_enter_from_user_mode_randomize_stack() first invokes +enter_from_user_mode_randomize_stack() which establishes state in the +following order: * Lockdep * RCU / Context tracking * Tracing + * Apply stack randomization and then invokes the various entry work functions like ptrace, seccomp, audit, syscall tracing, etc. After all that is done, the instrumentable invoke_syscall @@ -99,10 +101,11 @@ transition in the reverse order: * RCU / Context tracking * Lockdep -syscall_enter_from_user_mode() and syscall_exit_to_user_mode() are also -available as fine grained subfunctions in cases where the architecture code -has to do extra work between the various steps. In such cases it has to -ensure that enter_from_user_mode() is called first on entry and +syscall_enter_from_user_mode_randomize_stack() and +syscall_exit_to_user_mode() are also available as fine grained subfunctions +in cases where the architecture code has to do extra work between the +various steps. In such cases it has to ensure that +enter_from_user_mode_randomize_stack() is called first on entry and exit_to_user_mode() is called last on exit. Do not nest syscalls. Nested syscalls will cause RCU and/or context tracking diff --git a/include/linux/entry-common.h b/include/linux/entry-common.h index 166b1ed9a9c0..c0f6464ca96b 100644 --- a/include/linux/entry-common.h +++ b/include/linux/entry-common.h @@ -20,7 +20,7 @@ #endif /* - * SYSCALL_WORK flags handled in syscall_enter_from_user_mode() + * SYSCALL_WORK flags handled in syscall_enter_from_user_mode_work() */ #define SYSCALL_WORK_ENTER (SYSCALL_WORK_SECCOMP | \ SYSCALL_WORK_SYSCALL_TRACEPOINT | \ @@ -205,42 +205,10 @@ do { \ _ret; \ }) -/** - * syscall_enter_from_user_mode - Establish state and check and handle work - * before invoking a syscall - * @regs: Pointer to currents pt_regs - * @syscall: The syscall number - * - * Invoked from architecture specific syscall entry code with interrupts - * disabled. The calling code has to be non-instrumentable. When the - * function returns all state is correct, interrupts are enabled and the - * subsequent functions can be instrumented. - * - * This is the combination of enter_from_user_mode() and - * syscall_enter_from_user_mode_work() to be used when there is no - * architecture specific work to be done between the two. - * - * Returns: The original or a modified syscall number. See - * syscall_enter_from_user_mode_work() for further explanation. - */ -static __always_inline long syscall_enter_from_user_mode(struct pt_regs *regs, long syscall) -{ - long ret; - - enter_from_user_mode(regs); - - instrumentation_begin(); - local_irq_enable(); - ret = syscall_enter_from_user_mode_work(regs, syscall); - instrumentation_end(); - - return ret; -} - /* - * If SYSCALL_EMU is set, then the only reason to report is when - * SINGLESTEP is set (i.e. PTRACE_SYSEMU_SINGLESTEP). This syscall - * instruction has been already reported in syscall_enter_from_user_mode(). + * If SYSCALL_EMU is set, then the only reason to report is when SINGLESTEP is + * set (i.e. PTRACE_SYSEMU_SINGLESTEP). This syscall instruction has been + * already reported in syscall_enter_from_user_mode_work(). */ static __always_inline bool report_single_step(unsigned long work) { diff --git a/include/linux/irq-entry-common.h b/include/linux/irq-entry-common.h index 1fabf0f5ea8e..0bb6c03481fa 100644 --- a/include/linux/irq-entry-common.h +++ b/include/linux/irq-entry-common.h @@ -49,9 +49,9 @@ * Defaults to an empty implementation. Can be replaced by architecture * specific code. * - * Invoked from syscall_enter_from_user_mode() in the non-instrumentable - * section. Use __always_inline so the compiler cannot push it out of line - * and make it instrumentable. + * Invoked from enter_from_user_mode() in the non-instrumentable section. Use + * __always_inline so the compiler cannot push it out of line and make it + * instrumentable. */ static __always_inline void arch_enter_from_user_mode(struct pt_regs *regs); From 8af25d0a2e465f3cb73c47c605fddd1664ee79b2 Mon Sep 17 00:00:00 2001 From: Thomas Gleixner Date: Tue, 7 Jul 2026 21:06:36 +0200 Subject: [PATCH 12/23] entry: Use syscall number instead of rereading it MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit rseq_syscall_enter_work() is invoked before the syscall number can be modified. So there is no point in rereading it from pt_regs. Signed-off-by: Thomas Gleixner Tested-by: Mukesh Kumar Chaurasiya (IBM) Reviewed-by: Radu Rendec Reviewed-by: Jinjie Ruan Reviewed-by: Mukesh Kumar Chaurasiya (IBM) Reviewed-by: Philippe Mathieu-Daudé Link: https://patch.msgid.link/20260707190254.181086755@kernel.org --- include/linux/entry-common.h | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/include/linux/entry-common.h b/include/linux/entry-common.h index c0f6464ca96b..c8373525bdb1 100644 --- a/include/linux/entry-common.h +++ b/include/linux/entry-common.h @@ -70,9 +70,10 @@ static inline void syscall_enter_audit(struct pt_regs *regs, long syscall) } } -static __always_inline long syscall_trace_enter(struct pt_regs *regs, unsigned long work) +static __always_inline long syscall_trace_enter(struct pt_regs *regs, unsigned long work, + long syscall) { - long syscall, ret = 0; + long ret = 0; /* * Handle Syscall User Dispatch. This must comes first, since @@ -90,7 +91,7 @@ static __always_inline long syscall_trace_enter(struct pt_regs *regs, unsigned l * through hrtimer_interrupt(). */ if (work & SYSCALL_WORK_SYSCALL_RSEQ_SLICE) - rseq_syscall_enter_work(syscall_get_nr(current, regs)); + rseq_syscall_enter_work(syscall); /* Handle ptrace */ if (work & (SYSCALL_WORK_SYSCALL_TRACE | SYSCALL_WORK_SYSCALL_EMU)) { @@ -145,7 +146,7 @@ static __always_inline long syscall_enter_from_user_mode_work(struct pt_regs *re unsigned long work = READ_ONCE(current_thread_info()->syscall_work); if (work & SYSCALL_WORK_ENTER) - syscall = syscall_trace_enter(regs, work); + syscall = syscall_trace_enter(regs, work, syscall); return syscall; } From 7ba2ba74713c83408cc942b60dd869ab2c34c84f Mon Sep 17 00:00:00 2001 From: Jinjie Ruan Date: Tue, 7 Jul 2026 21:06:40 +0200 Subject: [PATCH 13/23] seccomp, treewide: Rename and convert __secure_computing() to return boolean The return value of __secure_computing() currently uses 0 to indicate that a system call should be allowed, and -1 to indicate that it should be blocked/killed. This 0/-1 pattern is non-intuitive for a security check function and makes the control flow at the call sites less readable. Furthermore, any potential future changes to these return values would require a high-risk, error-prone audit of all its users across different architectures. Sanitize this logic by converting the return type of __secure_computing() to a proper boolean, where 'true' explicitly means 'allow' and 'false' means 'fail/deny'. Update all the two dozen or so call sites across the tree to align with this new boolean semantic. No functional changes are intended, as the callers still return -1 to the lower-level assembly entry code upon seccomp denial. Rename the function to __seccomp_permit_syscall() so that the purpose is entirely clear. [ tglx: Rename the function ] Suggested-by: Thomas Gleixner Suggested-by: Mark Rutland Signed-off-by: Jinjie Ruan Signed-off-by: Thomas Gleixner Tested-by: Mukesh Kumar Chaurasiya (IBM) Reviewed-by: Mukesh Kumar Chaurasiya (IBM) Acked-by: Oleg Nesterov Link: https://patch.msgid.link/20260707190254.230735780@kernel.org --- arch/Kconfig | 2 +- arch/alpha/kernel/ptrace.c | 2 +- arch/arm/kernel/ptrace.c | 2 +- arch/arm64/kernel/ptrace.c | 2 +- arch/csky/kernel/ptrace.c | 2 +- arch/m68k/kernel/ptrace.c | 2 +- arch/mips/kernel/ptrace.c | 2 +- arch/parisc/kernel/ptrace.c | 2 +- arch/sh/kernel/ptrace_32.c | 2 +- arch/um/kernel/skas/syscall.c | 2 +- arch/x86/entry/vsyscall/vsyscall_64.c | 14 +++++------ arch/xtensa/kernel/ptrace.c | 3 +-- include/linux/entry-common.h | 9 +++---- include/linux/seccomp.h | 12 ++++----- kernel/seccomp.c | 36 +++++++++++++-------------- 15 files changed, 45 insertions(+), 49 deletions(-) diff --git a/arch/Kconfig b/arch/Kconfig index 0c01521c2f3f..d2dbed524f15 100644 --- a/arch/Kconfig +++ b/arch/Kconfig @@ -647,7 +647,7 @@ config HAVE_ARCH_SECCOMP_FILTER - syscall_set_return_value() - SIGSYS siginfo_t support - secure_computing is called from a ptrace_event()-safe context - - secure_computing return value is checked and a return value of -1 + - secure_computing return value is checked and if false it results in the system call being skipped immediately. - seccomp syscall wired up - if !HAVE_SPARSE_SYSCALL_NR, have SECCOMP_ARCH_NATIVE, diff --git a/arch/alpha/kernel/ptrace.c b/arch/alpha/kernel/ptrace.c index 0687760ea466..2870101dafc9 100644 --- a/arch/alpha/kernel/ptrace.c +++ b/arch/alpha/kernel/ptrace.c @@ -387,7 +387,7 @@ asmlinkage unsigned long syscall_trace_enter(void) * If this fails, seccomp may already have set up the return value * (e.g. SECCOMP_RET_ERRNO / TRACE). */ - if (secure_computing() == -1) { + if (!seccomp_permit_syscall()) { if (regs->r19 == 0 && regs->r0 == (unsigned long)-1) syscall_set_return_value(current, regs, -ENOSYS, 0); syscall_set_nr(current, regs, -1); diff --git a/arch/arm/kernel/ptrace.c b/arch/arm/kernel/ptrace.c index 7951b2c06fec..45076fc92a06 100644 --- a/arch/arm/kernel/ptrace.c +++ b/arch/arm/kernel/ptrace.c @@ -855,7 +855,7 @@ asmlinkage int syscall_trace_enter(struct pt_regs *regs) /* Do seccomp after ptrace; syscall may have changed. */ #ifdef CONFIG_HAVE_ARCH_SECCOMP_FILTER - if (secure_computing() == -1) + if (!seccomp_permit_syscall()) return -1; #else /* XXX: remove this once OABI gets fixed */ diff --git a/arch/arm64/kernel/ptrace.c b/arch/arm64/kernel/ptrace.c index 4d08598e2891..253ce5d78c5a 100644 --- a/arch/arm64/kernel/ptrace.c +++ b/arch/arm64/kernel/ptrace.c @@ -2420,7 +2420,7 @@ int syscall_trace_enter(struct pt_regs *regs) } /* Do the secure computing after ptrace; failures should be fast. */ - if (secure_computing() == -1) + if (!seccomp_permit_syscall()) return NO_SYSCALL; if (test_thread_flag(TIF_SYSCALL_TRACEPOINT)) diff --git a/arch/csky/kernel/ptrace.c b/arch/csky/kernel/ptrace.c index 6bb685a2646b..b902358f599b 100644 --- a/arch/csky/kernel/ptrace.c +++ b/arch/csky/kernel/ptrace.c @@ -323,7 +323,7 @@ asmlinkage int syscall_trace_enter(struct pt_regs *regs) if (ptrace_report_syscall_entry(regs)) return -1; - if (secure_computing() == -1) + if (!seccomp_permit_syscall()) return -1; if (test_thread_flag(TIF_SYSCALL_TRACEPOINT)) diff --git a/arch/m68k/kernel/ptrace.c b/arch/m68k/kernel/ptrace.c index cfa2df24eced..28546d723c29 100644 --- a/arch/m68k/kernel/ptrace.c +++ b/arch/m68k/kernel/ptrace.c @@ -281,7 +281,7 @@ asmlinkage int syscall_trace_enter(void) if (test_thread_flag(TIF_SYSCALL_TRACE)) ret = ptrace_report_syscall_entry(task_pt_regs(current)); - if (secure_computing() == -1) + if (!seccomp_permit_syscall()) return -1; return ret; diff --git a/arch/mips/kernel/ptrace.c b/arch/mips/kernel/ptrace.c index 3f4c94c88124..af1e005b2689 100644 --- a/arch/mips/kernel/ptrace.c +++ b/arch/mips/kernel/ptrace.c @@ -1328,7 +1328,7 @@ asmlinkage long syscall_trace_enter(struct pt_regs *regs) return -1; } - if (secure_computing()) + if (!seccomp_permit_syscall()) return -1; if (unlikely(test_thread_flag(TIF_SYSCALL_TRACEPOINT))) diff --git a/arch/parisc/kernel/ptrace.c b/arch/parisc/kernel/ptrace.c index 8a17ab7e6e0b..ac5d077762bc 100644 --- a/arch/parisc/kernel/ptrace.c +++ b/arch/parisc/kernel/ptrace.c @@ -351,7 +351,7 @@ long do_syscall_trace_enter(struct pt_regs *regs) } /* Do the secure computing check after ptrace. */ - if (secure_computing() == -1) + if (!seccomp_permit_syscall()) return -1; #ifdef CONFIG_HAVE_SYSCALL_TRACEPOINTS diff --git a/arch/sh/kernel/ptrace_32.c b/arch/sh/kernel/ptrace_32.c index 06f765d71a29..7594bd5cbb4a 100644 --- a/arch/sh/kernel/ptrace_32.c +++ b/arch/sh/kernel/ptrace_32.c @@ -460,7 +460,7 @@ asmlinkage long do_syscall_trace_enter(struct pt_regs *regs) return -1; } - if (secure_computing() == -1) + if (!seccomp_permit_syscall()) return -1; if (unlikely(test_thread_flag(TIF_SYSCALL_TRACEPOINT))) diff --git a/arch/um/kernel/skas/syscall.c b/arch/um/kernel/skas/syscall.c index ba7494f9bfe4..bc117733e8ef 100644 --- a/arch/um/kernel/skas/syscall.c +++ b/arch/um/kernel/skas/syscall.c @@ -27,7 +27,7 @@ void handle_syscall(struct uml_pt_regs *r) goto out; /* Do the seccomp check after ptrace; failures should be fast. */ - if (secure_computing() == -1) + if (!seccomp_permit_syscall()) goto out; syscall = UPT_SYSCALL_NR(r); diff --git a/arch/x86/entry/vsyscall/vsyscall_64.c b/arch/x86/entry/vsyscall/vsyscall_64.c index ea36de9fa864..1b68c1cc0d4f 100644 --- a/arch/x86/entry/vsyscall/vsyscall_64.c +++ b/arch/x86/entry/vsyscall/vsyscall_64.c @@ -118,10 +118,10 @@ static bool write_ok_or_segv(unsigned long ptr, size_t size) static bool __emulate_vsyscall(struct pt_regs *regs, unsigned long address) { - unsigned long caller; - int vsyscall_nr, syscall_nr, tmp; + unsigned long caller, orig_dx; + int vsyscall_nr, syscall_nr; + bool skip; long ret; - unsigned long orig_dx; /* Confirm that the fault happened in 64-bit user mode */ if (!user_64bit_mode(regs)) @@ -197,16 +197,16 @@ static bool __emulate_vsyscall(struct pt_regs *regs, unsigned long address) */ regs->orig_ax = syscall_nr; regs->ax = -ENOSYS; - tmp = secure_computing(); - if ((!tmp && regs->orig_ax != syscall_nr) || regs->ip != address) { + skip = !seccomp_permit_syscall(); + if ((!skip && regs->orig_ax != syscall_nr) || regs->ip != address) { warn_bad_vsyscall(KERN_DEBUG, regs, "seccomp tried to change syscall nr or ip"); force_exit_sig(SIGSYS); return true; } regs->orig_ax = -1; - if (tmp) - goto do_ret; /* skip requested */ + if (skip) + goto do_ret; /* * With a real vsyscall, page faults cause SIGSEGV. diff --git a/arch/xtensa/kernel/ptrace.c b/arch/xtensa/kernel/ptrace.c index b80d54b2ea34..5c3ebb03513b 100644 --- a/arch/xtensa/kernel/ptrace.c +++ b/arch/xtensa/kernel/ptrace.c @@ -553,8 +553,7 @@ int do_syscall_trace_enter(struct pt_regs *regs) return 0; } - if (regs->syscall == NO_SYSCALL || - secure_computing() == -1) { + if (regs->syscall == NO_SYSCALL || !seccomp_permit_syscall()) { do_syscall_trace_leave(regs); return 0; } diff --git a/include/linux/entry-common.h b/include/linux/entry-common.h index c8373525bdb1..99c49a402a9a 100644 --- a/include/linux/entry-common.h +++ b/include/linux/entry-common.h @@ -102,9 +102,8 @@ static __always_inline long syscall_trace_enter(struct pt_regs *regs, unsigned l /* Do seccomp after ptrace, to catch any tracer changes. */ if (work & SYSCALL_WORK_SECCOMP) { - ret = __secure_computing(); - if (ret == -1L) - return ret; + if (!__seccomp_permit_syscall()) + return -1L; } /* Either of the above might have changed the syscall number */ @@ -115,7 +114,7 @@ static __always_inline long syscall_trace_enter(struct pt_regs *regs, unsigned l syscall_enter_audit(regs, syscall); - return ret ? : syscall; + return syscall; } /** @@ -138,7 +137,7 @@ static __always_inline long syscall_trace_enter(struct pt_regs *regs, unsigned l * It handles the following work items: * * 1) syscall_work flag dependent invocations of - * ptrace_report_syscall_entry(), __secure_computing(), trace_sys_enter() + * ptrace_report_syscall_entry(), __seccomp_permit_syscall(), trace_sys_enter() * 2) Invocation of audit_syscall_entry() */ static __always_inline long syscall_enter_from_user_mode_work(struct pt_regs *regs, long syscall) diff --git a/include/linux/seccomp.h b/include/linux/seccomp.h index 9b959972bf4a..fcb3eb9825e5 100644 --- a/include/linux/seccomp.h +++ b/include/linux/seccomp.h @@ -22,14 +22,14 @@ #include #include -extern int __secure_computing(void); +extern bool __seccomp_permit_syscall(void); #ifdef CONFIG_HAVE_ARCH_SECCOMP_FILTER -static inline int secure_computing(void) +static __always_inline bool seccomp_permit_syscall(void) { if (unlikely(test_syscall_work(SECCOMP))) - return __secure_computing(); - return 0; + return __seccomp_permit_syscall(); + return true; } #else extern void secure_computing_strict(int this_syscall); @@ -50,11 +50,11 @@ static inline int seccomp_mode(struct seccomp *s) struct seccomp_data; #ifdef CONFIG_HAVE_ARCH_SECCOMP_FILTER -static inline int secure_computing(void) { return 0; } +static inline bool seccomp_permit_syscall(void) { return true; } #else static inline void secure_computing_strict(int this_syscall) { return; } #endif -static inline int __secure_computing(void) { return 0; } +static inline bool __seccomp_permit_syscall(void) { return true; } static inline long prctl_get_seccomp(void) { diff --git a/kernel/seccomp.c b/kernel/seccomp.c index 066909393c38..86cf4460d69e 100644 --- a/kernel/seccomp.c +++ b/kernel/seccomp.c @@ -1100,12 +1100,13 @@ void secure_computing_strict(int this_syscall) else BUG(); } -int __secure_computing(void) + +bool __seccomp_permit_syscall(void) { int this_syscall = syscall_get_nr(current, current_pt_regs()); secure_computing_strict(this_syscall); - return 0; + return true; } #else @@ -1256,7 +1257,7 @@ out: return -1; } -static int __seccomp_filter(int this_syscall, const bool recheck_after_trace) +static bool __seccomp_filter(int this_syscall, const bool recheck_after_trace) { u32 filter_ret, action; struct seccomp_data sd; @@ -1294,7 +1295,7 @@ static int __seccomp_filter(int this_syscall, const bool recheck_after_trace) case SECCOMP_RET_TRACE: /* We've been put in this state by the ptracer already. */ if (recheck_after_trace) - return 0; + return true; /* ENOSYS these calls if there is no tracer attached. */ if (!ptrace_event_enabled(current, PTRACE_EVENT_SECCOMP)) { @@ -1329,20 +1330,17 @@ static int __seccomp_filter(int this_syscall, const bool recheck_after_trace) * a reload of all registers. This does not goto skip since * a skip would have already been reported. */ - if (__seccomp_filter(this_syscall, true)) - return -1; - - return 0; + return __seccomp_filter(this_syscall, true); case SECCOMP_RET_USER_NOTIF: if (seccomp_do_user_notification(this_syscall, match, &sd)) goto skip; - return 0; + return true; case SECCOMP_RET_LOG: seccomp_log(this_syscall, 0, action, true); - return 0; + return true; case SECCOMP_RET_ALLOW: /* @@ -1350,7 +1348,7 @@ static int __seccomp_filter(int this_syscall, const bool recheck_after_trace) * this action since SECCOMP_RET_ALLOW is the starting * state in seccomp_run_filters(). */ - return 0; + return true; case SECCOMP_RET_KILL_THREAD: case SECCOMP_RET_KILL_PROCESS: @@ -1367,46 +1365,46 @@ static int __seccomp_filter(int this_syscall, const bool recheck_after_trace) } else { do_exit(SIGSYS); } - return -1; /* skip the syscall go directly to signal handling */ + return false; /* skip the syscall go directly to signal handling */ } unreachable(); skip: seccomp_log(this_syscall, 0, action, match ? match->log : false); - return -1; + return false; } #else -static int __seccomp_filter(int this_syscall, const bool recheck_after_trace) +static bool __seccomp_filter(int this_syscall, const bool recheck_after_trace) { BUG(); - return -1; + return false; } #endif -int __secure_computing(void) +bool __seccomp_permit_syscall(void) { int mode = current->seccomp.mode; int this_syscall; if (IS_ENABLED(CONFIG_CHECKPOINT_RESTORE) && unlikely(current->ptrace & PT_SUSPEND_SECCOMP)) - return 0; + return true; this_syscall = syscall_get_nr(current, current_pt_regs()); switch (mode) { case SECCOMP_MODE_STRICT: __secure_computing_strict(this_syscall); /* may call do_exit */ - return 0; + return true; case SECCOMP_MODE_FILTER: return __seccomp_filter(this_syscall, false); /* Surviving SECCOMP_RET_KILL_* must be proactively impossible. */ case SECCOMP_MODE_DEAD: WARN_ON_ONCE(1); do_exit(SIGKILL); - return -1; + return false; default: BUG(); } From 622f04e97415e62bd2ab61f7a27e7a296e8467b7 Mon Sep 17 00:00:00 2001 From: Thomas Gleixner Date: Tue, 7 Jul 2026 21:06:44 +0200 Subject: [PATCH 14/23] ptrace, treewide: Rename ptrace_report_syscall_entry() to ptrace_report_syscall_permit_entry() The return value of that function is boolean and tells the caller whether to permit the syscall processing or not. Rename the function so the purpose is clear and make the return type bool. Signed-off-by: Thomas Gleixner Tested-by: Mukesh Kumar Chaurasiya (IBM) Reviewed-by: Jinjie Ruan Reviewed-by: Radu Rendec Reviewed-by: Mukesh Kumar Chaurasiya (IBM) Acked-by: Oleg Nesterov Acked-by: Magnus Lindholm Link: https://patch.msgid.link/20260707190254.280015701@kernel.org --- arch/alpha/kernel/ptrace.c | 2 +- arch/arc/kernel/ptrace.c | 2 +- arch/arm/kernel/ptrace.c | 2 +- arch/arm64/kernel/ptrace.c | 2 +- arch/csky/kernel/ptrace.c | 2 +- arch/hexagon/kernel/traps.c | 2 +- arch/m68k/kernel/ptrace.c | 2 +- arch/microblaze/kernel/ptrace.c | 2 +- arch/mips/kernel/ptrace.c | 2 +- arch/nios2/kernel/ptrace.c | 2 +- arch/openrisc/kernel/ptrace.c | 2 +- arch/parisc/kernel/ptrace.c | 10 ++++------ arch/sh/kernel/ptrace_32.c | 2 +- arch/sparc/kernel/ptrace_32.c | 2 +- arch/sparc/kernel/ptrace_64.c | 2 +- arch/um/kernel/ptrace.c | 2 +- arch/xtensa/kernel/ptrace.c | 2 +- include/asm-generic/syscall.h | 4 ++-- include/linux/entry-common.h | 25 ++++++++++++------------- include/linux/ptrace.h | 13 ++++++------- 20 files changed, 40 insertions(+), 44 deletions(-) diff --git a/arch/alpha/kernel/ptrace.c b/arch/alpha/kernel/ptrace.c index 2870101dafc9..768b0920a9de 100644 --- a/arch/alpha/kernel/ptrace.c +++ b/arch/alpha/kernel/ptrace.c @@ -375,7 +375,7 @@ asmlinkage unsigned long syscall_trace_enter(void) struct pt_regs *regs = current_pt_regs(); if (test_thread_flag(TIF_SYSCALL_TRACE) && - ptrace_report_syscall_entry(regs)) { + !ptrace_report_syscall_permit_entry(regs)) { syscall_set_nr(current, regs, -1); if (regs->r19 == 0 && regs->r0 == (unsigned long)-1) syscall_set_return_value(current, regs, -ENOSYS, 0); diff --git a/arch/arc/kernel/ptrace.c b/arch/arc/kernel/ptrace.c index cad5367b7c37..fe2b6572e919 100644 --- a/arch/arc/kernel/ptrace.c +++ b/arch/arc/kernel/ptrace.c @@ -342,7 +342,7 @@ long arch_ptrace(struct task_struct *child, long request, asmlinkage int syscall_trace_enter(struct pt_regs *regs) { if (test_thread_flag(TIF_SYSCALL_TRACE)) - if (ptrace_report_syscall_entry(regs)) + if (!ptrace_report_syscall_permit_entry(regs)) return ULONG_MAX; #ifdef CONFIG_HAVE_SYSCALL_TRACEPOINTS diff --git a/arch/arm/kernel/ptrace.c b/arch/arm/kernel/ptrace.c index 45076fc92a06..ed7a2a87a670 100644 --- a/arch/arm/kernel/ptrace.c +++ b/arch/arm/kernel/ptrace.c @@ -840,7 +840,7 @@ static void report_syscall(struct pt_regs *regs, enum ptrace_syscall_dir dir) if (dir == PTRACE_SYSCALL_EXIT) ptrace_report_syscall_exit(regs, 0); - else if (ptrace_report_syscall_entry(regs)) + else if (!ptrace_report_syscall_permit_entry(regs)) current_thread_info()->abi_syscall = -1; regs->ARM_ip = ip; diff --git a/arch/arm64/kernel/ptrace.c b/arch/arm64/kernel/ptrace.c index 253ce5d78c5a..5709e9d3c321 100644 --- a/arch/arm64/kernel/ptrace.c +++ b/arch/arm64/kernel/ptrace.c @@ -2379,7 +2379,7 @@ static int report_syscall_entry(struct pt_regs *regs) int regno, ret; saved_reg = ptrace_save_reg(regs, PTRACE_SYSCALL_ENTER, ®no); - ret = ptrace_report_syscall_entry(regs); + ret = !ptrace_report_syscall_permit_entry(regs); if (ret) forget_syscall(regs); regs->regs[regno] = saved_reg; diff --git a/arch/csky/kernel/ptrace.c b/arch/csky/kernel/ptrace.c index b902358f599b..ee2867a1576e 100644 --- a/arch/csky/kernel/ptrace.c +++ b/arch/csky/kernel/ptrace.c @@ -320,7 +320,7 @@ long arch_ptrace(struct task_struct *child, long request, asmlinkage int syscall_trace_enter(struct pt_regs *regs) { if (test_thread_flag(TIF_SYSCALL_TRACE)) - if (ptrace_report_syscall_entry(regs)) + if (!ptrace_report_syscall_permit_entry(regs)) return -1; if (!seccomp_permit_syscall()) diff --git a/arch/hexagon/kernel/traps.c b/arch/hexagon/kernel/traps.c index e732aa01c2ff..6fe0c1b62c1b 100644 --- a/arch/hexagon/kernel/traps.c +++ b/arch/hexagon/kernel/traps.c @@ -345,7 +345,7 @@ void do_trap0(struct pt_regs *regs) /* allow strace to catch syscall args */ if (unlikely(test_thread_flag(TIF_SYSCALL_TRACE) && - ptrace_report_syscall_entry(regs))) + !ptrace_report_syscall_permit_entry(regs))) return; /* return -ENOSYS somewhere? */ /* Interrupts should be re-enabled for syscall processing */ diff --git a/arch/m68k/kernel/ptrace.c b/arch/m68k/kernel/ptrace.c index 28546d723c29..4575f6487a9a 100644 --- a/arch/m68k/kernel/ptrace.c +++ b/arch/m68k/kernel/ptrace.c @@ -279,7 +279,7 @@ asmlinkage int syscall_trace_enter(void) int ret = 0; if (test_thread_flag(TIF_SYSCALL_TRACE)) - ret = ptrace_report_syscall_entry(task_pt_regs(current)); + ret = !ptrace_report_syscall_permit_entry(task_pt_regs(current)); if (!seccomp_permit_syscall()) return -1; diff --git a/arch/microblaze/kernel/ptrace.c b/arch/microblaze/kernel/ptrace.c index 5234d0c1dcaa..236264e932d6 100644 --- a/arch/microblaze/kernel/ptrace.c +++ b/arch/microblaze/kernel/ptrace.c @@ -139,7 +139,7 @@ asmlinkage unsigned long do_syscall_trace_enter(struct pt_regs *regs) secure_computing_strict(regs->r12); if (test_thread_flag(TIF_SYSCALL_TRACE) && - ptrace_report_syscall_entry(regs)) + !ptrace_report_syscall_permit_entry(regs)) /* * Tracing decided this syscall should not happen. * We'll return a bogus call number to get an ENOSYS diff --git a/arch/mips/kernel/ptrace.c b/arch/mips/kernel/ptrace.c index af1e005b2689..5a0e298857b6 100644 --- a/arch/mips/kernel/ptrace.c +++ b/arch/mips/kernel/ptrace.c @@ -1324,7 +1324,7 @@ asmlinkage long syscall_trace_enter(struct pt_regs *regs) user_exit(); if (test_thread_flag(TIF_SYSCALL_TRACE)) { - if (ptrace_report_syscall_entry(regs)) + if (!ptrace_report_syscall_permit_entry(regs)) return -1; } diff --git a/arch/nios2/kernel/ptrace.c b/arch/nios2/kernel/ptrace.c index c88f5cabc0c1..2cd7e1ecbd74 100644 --- a/arch/nios2/kernel/ptrace.c +++ b/arch/nios2/kernel/ptrace.c @@ -133,7 +133,7 @@ asmlinkage int do_syscall_trace_enter(void) int ret = 0; if (test_thread_flag(TIF_SYSCALL_TRACE)) - ret = ptrace_report_syscall_entry(task_pt_regs(current)); + ret = !ptrace_report_syscall_permit_entry(task_pt_regs(current)); return ret; } diff --git a/arch/openrisc/kernel/ptrace.c b/arch/openrisc/kernel/ptrace.c index 552489b24855..287a9718f0c6 100644 --- a/arch/openrisc/kernel/ptrace.c +++ b/arch/openrisc/kernel/ptrace.c @@ -293,7 +293,7 @@ asmlinkage long do_syscall_trace_enter(struct pt_regs *regs) long ret = 0; if (test_thread_flag(TIF_SYSCALL_TRACE) && - ptrace_report_syscall_entry(regs)) + !ptrace_report_syscall_permit_entry(regs)) /* * Tracing decided this syscall should not happen. * We'll return a bogus call number to get an ENOSYS diff --git a/arch/parisc/kernel/ptrace.c b/arch/parisc/kernel/ptrace.c index ac5d077762bc..1d9e210702e1 100644 --- a/arch/parisc/kernel/ptrace.c +++ b/arch/parisc/kernel/ptrace.c @@ -326,7 +326,7 @@ long compat_arch_ptrace(struct task_struct *child, compat_long_t request, long do_syscall_trace_enter(struct pt_regs *regs) { if (test_thread_flag(TIF_SYSCALL_TRACE)) { - int rc = ptrace_report_syscall_entry(regs); + bool permit = ptrace_report_syscall_permit_entry(regs); /* * As tracesys_next does not set %r28 to -ENOSYS @@ -334,12 +334,10 @@ long do_syscall_trace_enter(struct pt_regs *regs) */ regs->gr[28] = -ENOSYS; - if (rc) { + if (!permit) { /* - * A nonzero return code from - * ptrace_report_syscall_entry() tells us - * to prevent the syscall execution. Skip - * the syscall call and the syscall restart handling. + * Skip the syscall call and the syscall restart + * handling. * * Note that the tracer may also just change * regs->gr[20] to an invalid syscall number, diff --git a/arch/sh/kernel/ptrace_32.c b/arch/sh/kernel/ptrace_32.c index 7594bd5cbb4a..8794081483fb 100644 --- a/arch/sh/kernel/ptrace_32.c +++ b/arch/sh/kernel/ptrace_32.c @@ -455,7 +455,7 @@ long arch_ptrace(struct task_struct *child, long request, asmlinkage long do_syscall_trace_enter(struct pt_regs *regs) { if (test_thread_flag(TIF_SYSCALL_TRACE) && - ptrace_report_syscall_entry(regs)) { + !ptrace_report_syscall_permit_entry(regs)) { regs->regs[0] = -ENOSYS; return -1; } diff --git a/arch/sparc/kernel/ptrace_32.c b/arch/sparc/kernel/ptrace_32.c index c56333975fb1..5991b1731fee 100644 --- a/arch/sparc/kernel/ptrace_32.c +++ b/arch/sparc/kernel/ptrace_32.c @@ -441,7 +441,7 @@ asmlinkage int syscall_trace(struct pt_regs *regs, int syscall_exit_p) if (syscall_exit_p) ptrace_report_syscall_exit(regs, 0); else - ret = ptrace_report_syscall_entry(regs); + ret = !ptrace_report_syscall_permit_entry(regs); } return ret; diff --git a/arch/sparc/kernel/ptrace_64.c b/arch/sparc/kernel/ptrace_64.c index 9fc67fa9336f..825ddf55fece 100644 --- a/arch/sparc/kernel/ptrace_64.c +++ b/arch/sparc/kernel/ptrace_64.c @@ -1093,7 +1093,7 @@ asmlinkage int syscall_trace_enter(struct pt_regs *regs) user_exit(); if (test_thread_flag(TIF_SYSCALL_TRACE)) - ret = ptrace_report_syscall_entry(regs); + ret = !ptrace_report_syscall_permit_entry(regs); if (unlikely(test_thread_flag(TIF_SYSCALL_TRACEPOINT))) trace_sys_enter(regs, regs->u_regs[UREG_G1]); diff --git a/arch/um/kernel/ptrace.c b/arch/um/kernel/ptrace.c index fdbb37b5c399..7da0a5223aa6 100644 --- a/arch/um/kernel/ptrace.c +++ b/arch/um/kernel/ptrace.c @@ -135,7 +135,7 @@ int syscall_trace_enter(struct pt_regs *regs) if (!test_thread_flag(TIF_SYSCALL_TRACE)) return 0; - return ptrace_report_syscall_entry(regs); + return !ptrace_report_syscall_permit_entry(regs); } void syscall_trace_leave(struct pt_regs *regs) diff --git a/arch/xtensa/kernel/ptrace.c b/arch/xtensa/kernel/ptrace.c index 5c3ebb03513b..364e4fdabb00 100644 --- a/arch/xtensa/kernel/ptrace.c +++ b/arch/xtensa/kernel/ptrace.c @@ -547,7 +547,7 @@ int do_syscall_trace_enter(struct pt_regs *regs) regs->areg[2] = -ENOSYS; if (test_thread_flag(TIF_SYSCALL_TRACE) && - ptrace_report_syscall_entry(regs)) { + !ptrace_report_syscall_permit_entry(regs)) { regs->areg[2] = -ENOSYS; regs->syscall = NO_SYSCALL; return 0; diff --git a/include/asm-generic/syscall.h b/include/asm-generic/syscall.h index c5a3ad53beec..b53bdd389948 100644 --- a/include/asm-generic/syscall.h +++ b/include/asm-generic/syscall.h @@ -58,8 +58,8 @@ void syscall_set_nr(struct task_struct *task, struct pt_regs *regs, int nr); * * It's only valid to call this when @task is stopped for system * call exit tracing (due to %SYSCALL_WORK_SYSCALL_TRACE or - * %SYSCALL_WORK_SYSCALL_AUDIT), after ptrace_report_syscall_entry() - * returned nonzero to prevent the system call from taking place. + * %SYSCALL_WORK_SYSCALL_AUDIT), after ptrace_report_syscall_permit_entry() + * returned False to prevent the system call from taking place. * * This rolls back the register state in @regs so it's as if the * system call instruction was a no-op. The registers containing diff --git a/include/linux/entry-common.h b/include/linux/entry-common.h index 99c49a402a9a..07d97def7dcb 100644 --- a/include/linux/entry-common.h +++ b/include/linux/entry-common.h @@ -39,21 +39,22 @@ SYSCALL_WORK_SYSCALL_EXIT_TRAP) /** - * arch_ptrace_report_syscall_entry - Architecture specific ptrace_report_syscall_entry() wrapper + * arch_ptrace_report_syscall_permit_entry - Architecture specific wrapper for + * ptrace_report_syscall_permit_entry() * @regs: Pointer to the register state at syscall entry * - * Invoked from syscall_trace_enter() to wrap ptrace_report_syscall_entry(). + * Invoked from syscall_trace_enter() to wrap ptrace_report_syscall_permit_entry(). * - * This allows architecture specific ptrace_report_syscall_entry() + * This allows architecture specific ptrace_report_syscall_permit_entry() * implementations. If not defined by the architecture this falls back to - * to ptrace_report_syscall_entry(). + * to ptrace_report_syscall_permit_entry(). */ -static __always_inline int arch_ptrace_report_syscall_entry(struct pt_regs *regs); +static __always_inline bool arch_ptrace_report_syscall_permit_entry(struct pt_regs *regs); -#ifndef arch_ptrace_report_syscall_entry -static __always_inline int arch_ptrace_report_syscall_entry(struct pt_regs *regs) +#ifndef arch_ptrace_report_syscall_permit_entry +static __always_inline bool arch_ptrace_report_syscall_permit_entry(struct pt_regs *regs) { - return ptrace_report_syscall_entry(regs); + return ptrace_report_syscall_permit_entry(regs); } #endif @@ -73,8 +74,6 @@ static inline void syscall_enter_audit(struct pt_regs *regs, long syscall) static __always_inline long syscall_trace_enter(struct pt_regs *regs, unsigned long work, long syscall) { - long ret = 0; - /* * Handle Syscall User Dispatch. This must comes first, since * the ABI here can be something that doesn't make sense for @@ -95,8 +94,8 @@ static __always_inline long syscall_trace_enter(struct pt_regs *regs, unsigned l /* Handle ptrace */ if (work & (SYSCALL_WORK_SYSCALL_TRACE | SYSCALL_WORK_SYSCALL_EMU)) { - ret = arch_ptrace_report_syscall_entry(regs); - if (ret || (work & SYSCALL_WORK_SYSCALL_EMU)) + if (!arch_ptrace_report_syscall_permit_entry(regs) || + (work & SYSCALL_WORK_SYSCALL_EMU)) return -1L; } @@ -137,7 +136,7 @@ static __always_inline long syscall_trace_enter(struct pt_regs *regs, unsigned l * It handles the following work items: * * 1) syscall_work flag dependent invocations of - * ptrace_report_syscall_entry(), __seccomp_permit_syscall(), trace_sys_enter() + * ptrace_report_syscall_permit_entry(), __seccomp_permit_syscall(), trace_sys_enter() * 2) Invocation of audit_syscall_entry() */ static __always_inline long syscall_enter_from_user_mode_work(struct pt_regs *regs, long syscall) diff --git a/include/linux/ptrace.h b/include/linux/ptrace.h index ef314f7a9ecc..2839d5c8eaa0 100644 --- a/include/linux/ptrace.h +++ b/include/linux/ptrace.h @@ -405,13 +405,13 @@ extern void sigaction_compat_abi(struct k_sigaction *act, struct k_sigaction *oa /* * ptrace report for syscall entry and exit looks identical. */ -static inline int ptrace_report_syscall(unsigned long message) +static inline bool ptrace_report_syscall(unsigned long message) { int ptrace = current->ptrace; int signr; if (!(ptrace & PT_PTRACED)) - return 0; + return true; signr = ptrace_notify(SIGTRAP | ((ptrace & PT_TRACESYSGOOD) ? 0x80 : 0), message); @@ -424,11 +424,11 @@ static inline int ptrace_report_syscall(unsigned long message) if (signr) send_sig(signr, current, 1); - return fatal_signal_pending(current); + return !fatal_signal_pending(current); } /** - * ptrace_report_syscall_entry - task is about to attempt a system call + * ptrace_report_syscall_permit_entry - task is about to attempt a system call * @regs: user register state of current task * * This will be called if %SYSCALL_WORK_SYSCALL_TRACE or @@ -438,7 +438,7 @@ static inline int ptrace_report_syscall(unsigned long message) * call number and arguments to be tried. It is safe to block here, * preventing the system call from beginning. * - * Returns zero normally, or nonzero if the calling arch code should abort + * Returns True normally, or False if the calling architecture code should abort * the system call. That must prevent normal entry so no system call is * made. If @task ever returns to user mode after this, its register state * is unspecified, but should be something harmless like an %ENOSYS error @@ -447,8 +447,7 @@ static inline int ptrace_report_syscall(unsigned long message) * * Called without locks, just after entering kernel mode. */ -static inline __must_check int ptrace_report_syscall_entry( - struct pt_regs *regs) +static inline __must_check bool ptrace_report_syscall_permit_entry(struct pt_regs *regs) { return ptrace_report_syscall(PTRACE_EVENTMSG_SYSCALL_ENTRY); } From 2b341c74dbf8864b6dd32119b761ceaab516249e Mon Sep 17 00:00:00 2001 From: Thomas Gleixner Date: Tue, 7 Jul 2026 21:06:57 +0200 Subject: [PATCH 15/23] x86/entry: Make syscall functions static They are only used in the respective source files. No point in exposing them. Signed-off-by: Thomas Gleixner Tested-by: Mukesh Kumar Chaurasiya (IBM) Reviewed-by: Jinjie Ruan Reviewed-by: Mukesh Kumar Chaurasiya (IBM) Link: https://patch.msgid.link/20260707190254.438361565@kernel.org --- arch/x86/entry/syscall_32.c | 2 +- arch/x86/entry/syscall_64.c | 10 ++++++---- arch/x86/include/asm/syscall.h | 8 -------- 3 files changed, 7 insertions(+), 13 deletions(-) diff --git a/arch/x86/entry/syscall_32.c b/arch/x86/entry/syscall_32.c index 2b13f1c842b8..6e00929b893c 100644 --- a/arch/x86/entry/syscall_32.c +++ b/arch/x86/entry/syscall_32.c @@ -41,7 +41,7 @@ const sys_call_ptr_t sys_call_table[] = { #endif #define __SYSCALL(nr, sym) case nr: return __ia32_##sym(regs); -long ia32_sys_call(const struct pt_regs *regs, unsigned int nr) +static noinline long ia32_sys_call(const struct pt_regs *regs, unsigned int nr) { switch (nr) { #include diff --git a/arch/x86/entry/syscall_64.c b/arch/x86/entry/syscall_64.c index 0acd7e99e8e0..837aee7db50d 100644 --- a/arch/x86/entry/syscall_64.c +++ b/arch/x86/entry/syscall_64.c @@ -32,7 +32,7 @@ const sys_call_ptr_t sys_call_table[] = { #undef __SYSCALL #define __SYSCALL(nr, sym) case nr: return __x64_##sym(regs); -long x64_sys_call(const struct pt_regs *regs, unsigned int nr) +static noinline long x64_sys_call(const struct pt_regs *regs, unsigned int nr) { switch (nr) { #include @@ -40,15 +40,17 @@ long x64_sys_call(const struct pt_regs *regs, unsigned int nr) } } -#ifdef CONFIG_X86_X32_ABI -long x32_sys_call(const struct pt_regs *regs, unsigned int nr) +static noinline long x32_sys_call(const struct pt_regs *regs, unsigned int nr) { +#ifdef CONFIG_X86_X32_ABI switch (nr) { #include default: return __x64_sys_ni_syscall(regs); } -} +#else + return -ENOSYS; #endif +} static __always_inline bool do_syscall_x64(struct pt_regs *regs, int nr) { diff --git a/arch/x86/include/asm/syscall.h b/arch/x86/include/asm/syscall.h index c10dbb74cd00..59a406074dc0 100644 --- a/arch/x86/include/asm/syscall.h +++ b/arch/x86/include/asm/syscall.h @@ -20,14 +20,6 @@ typedef long (*sys_call_ptr_t)(const struct pt_regs *); extern const sys_call_ptr_t sys_call_table[]; -/* - * These may not exist, but still put the prototypes in so we - * can use IS_ENABLED(). - */ -extern long ia32_sys_call(const struct pt_regs *, unsigned int nr); -extern long x32_sys_call(const struct pt_regs *, unsigned int nr); -extern long x64_sys_call(const struct pt_regs *, unsigned int nr); - /* * Only the low 32 bits of orig_ax are meaningful, so we return int. * This importantly ignores the high bits on 64-bit, so comparisons From 1b1f3b3e1b3945ae6e49081fd3586a6833d4a555 Mon Sep 17 00:00:00 2001 From: Thomas Gleixner Date: Tue, 7 Jul 2026 21:07:01 +0200 Subject: [PATCH 16/23] x86/entry: Get rid of the sys_ni_syscall() indirection Invoking sys_ni_syscall() from a code path, which already knows that the syscall number is invalid just to assign -ENOSYS to regs->ax is a pointless exercise. It's even redundant as the low level entry code already has set regs->ax to -ENOSYS on entry. Remove the extra conditionals and the function calls. Signed-off-by: Thomas Gleixner Tested-by: Mukesh Kumar Chaurasiya (IBM) Reviewed-by: Jinjie Ruan Link: https://patch.msgid.link/20260707190254.493733289@kernel.org --- arch/x86/entry/syscall_32.c | 2 -- arch/x86/entry/syscall_64.c | 10 +++------- 2 files changed, 3 insertions(+), 9 deletions(-) diff --git a/arch/x86/entry/syscall_32.c b/arch/x86/entry/syscall_32.c index 6e00929b893c..68e2f1323003 100644 --- a/arch/x86/entry/syscall_32.c +++ b/arch/x86/entry/syscall_32.c @@ -81,8 +81,6 @@ static __always_inline void do_syscall_32_irqs_on(struct pt_regs *regs, int nr) if (likely(unr < IA32_NR_syscalls)) { unr = array_index_nospec(unr, IA32_NR_syscalls); regs->ax = ia32_sys_call(regs, unr); - } else if (nr != -1) { - regs->ax = __ia32_sys_ni_syscall(regs); } } diff --git a/arch/x86/entry/syscall_64.c b/arch/x86/entry/syscall_64.c index 837aee7db50d..c716e0f546c6 100644 --- a/arch/x86/entry/syscall_64.c +++ b/arch/x86/entry/syscall_64.c @@ -68,7 +68,7 @@ static __always_inline bool do_syscall_x64(struct pt_regs *regs, int nr) return false; } -static __always_inline bool do_syscall_x32(struct pt_regs *regs, int nr) +static __always_inline void do_syscall_x32(struct pt_regs *regs, int nr) { /* * Adjust the starting offset of the table, and convert numbers @@ -80,9 +80,7 @@ static __always_inline bool do_syscall_x32(struct pt_regs *regs, int nr) if (IS_ENABLED(CONFIG_X86_X32_ABI) && likely(xnr < X32_NR_syscalls)) { xnr = array_index_nospec(xnr, X32_NR_syscalls); regs->ax = x32_sys_call(regs, xnr); - return true; } - return false; } /* Returns true to return using SYSRET, or false to use IRET */ @@ -92,10 +90,8 @@ __visible noinstr bool do_syscall_64(struct pt_regs *regs, int nr) instrumentation_begin(); - if (!do_syscall_x64(regs, nr) && !do_syscall_x32(regs, nr) && nr != -1) { - /* Invalid system call, but still a system call. */ - regs->ax = __x64_sys_ni_syscall(regs); - } + if (!do_syscall_x64(regs, nr)) + do_syscall_x32(regs, nr); instrumentation_end(); syscall_exit_to_user_mode(regs); From fb419d53f2619e29bf4f96613d9a614e1c263736 Mon Sep 17 00:00:00 2001 From: Thomas Gleixner Date: Tue, 7 Jul 2026 21:07:05 +0200 Subject: [PATCH 17/23] x86/entry: Simplify the syscall number logic Converting from int to long, back to int and then to unsigned int is confusing at best. None of this voodoo is required. Negative syscall numbers including -1 don't need any of this treatment and the low level ASM code already does the sign extension to 64-bit on a 64-bit kernel. The only point where signedness matters is the comparison against the maximum syscall number, but that can be simplified by just using a unsigned argument for the various syscall invocation functions. Signed-off-by: Thomas Gleixner Tested-by: Mukesh Kumar Chaurasiya (IBM) Link: https://patch.msgid.link/20260707190254.545214398@kernel.org --- arch/x86/entry/syscall_32.c | 26 +++++++++++--------------- arch/x86/entry/syscall_64.c | 34 +++++++++++++--------------------- arch/x86/include/asm/syscall.h | 2 +- 3 files changed, 25 insertions(+), 37 deletions(-) diff --git a/arch/x86/entry/syscall_32.c b/arch/x86/entry/syscall_32.c index 68e2f1323003..92369d84c0c8 100644 --- a/arch/x86/entry/syscall_32.c +++ b/arch/x86/entry/syscall_32.c @@ -41,6 +41,8 @@ const sys_call_ptr_t sys_call_table[] = { #endif #define __SYSCALL(nr, sym) case nr: return __ia32_##sym(regs); + +/* The unsigned int @nr argument is intentional as it creates denser code in a 64-bit build */ static noinline long ia32_sys_call(const struct pt_regs *regs, unsigned int nr) { switch (nr) { @@ -49,7 +51,7 @@ static noinline long ia32_sys_call(const struct pt_regs *regs, unsigned int nr) } } -static __always_inline int syscall_32_enter(struct pt_regs *regs) +static __always_inline long syscall_32_enter(struct pt_regs *regs) { if (IS_ENABLED(CONFIG_IA32_EMULATION)) current_thread_info()->status |= TS_COMPAT; @@ -70,17 +72,11 @@ early_param("ia32_emulation", ia32_emulation_override_cmdline); /* * Invoke a 32-bit syscall. Called with IRQs on in CT_STATE_KERNEL. */ -static __always_inline void do_syscall_32_irqs_on(struct pt_regs *regs, int nr) +static __always_inline void do_syscall_32_irqs_on(struct pt_regs *regs, unsigned long nr) { - /* - * Convert negative numbers to very high and thus out of range - * numbers for comparisons. - */ - unsigned int unr = nr; - - if (likely(unr < IA32_NR_syscalls)) { - unr = array_index_nospec(unr, IA32_NR_syscalls); - regs->ax = ia32_sys_call(regs, unr); + if (likely(nr < IA32_NR_syscalls)) { + nr = array_index_nospec(nr, IA32_NR_syscalls); + regs->ax = ia32_sys_call(regs, (unsigned int)nr); } } @@ -126,7 +122,7 @@ static __always_inline bool int80_is_external(void) */ __visible noinstr void do_int80_emulation(struct pt_regs *regs) { - int nr; + long nr; /* Kernel does not use INT $0x80! */ if (unlikely(!user_mode(regs))) { @@ -205,7 +201,7 @@ __visible noinstr void do_int80_emulation(struct pt_regs *regs) */ DEFINE_FREDENTRY_RAW(int80_emulation) { - int nr; + long nr; enter_from_user_mode_randomize_stack(regs); @@ -240,7 +236,7 @@ DEFINE_FREDENTRY_RAW(int80_emulation) /* Handles int $0x80 on a 32bit kernel */ __visible noinstr void do_int80_syscall_32(struct pt_regs *regs) { - int nr = syscall_32_enter(regs); + long nr = syscall_32_enter(regs); /* * Subtlety here: if ptrace pokes something larger than 2^31-1 into @@ -260,7 +256,7 @@ __visible noinstr void do_int80_syscall_32(struct pt_regs *regs) static noinstr bool __do_fast_syscall_32(struct pt_regs *regs) { - int nr = syscall_32_enter(regs); + long nr = syscall_32_enter(regs); int res; enter_from_user_mode_randomize_stack(regs); diff --git a/arch/x86/entry/syscall_64.c b/arch/x86/entry/syscall_64.c index c716e0f546c6..9a2762e32ae8 100644 --- a/arch/x86/entry/syscall_64.c +++ b/arch/x86/entry/syscall_64.c @@ -32,6 +32,8 @@ const sys_call_ptr_t sys_call_table[] = { #undef __SYSCALL #define __SYSCALL(nr, sym) case nr: return __x64_##sym(regs); + +/* The unsigned int @nr argument is intentional as it creates denser code */ static noinline long x64_sys_call(const struct pt_regs *regs, unsigned int nr) { switch (nr) { @@ -52,39 +54,29 @@ static noinline long x32_sys_call(const struct pt_regs *regs, unsigned int nr) #endif } -static __always_inline bool do_syscall_x64(struct pt_regs *regs, int nr) +static __always_inline bool do_syscall_x64(struct pt_regs *regs, unsigned long nr) { - /* - * Convert negative numbers to very high and thus out of range - * numbers for comparisons. - */ - unsigned int unr = nr; - - if (likely(unr < NR_syscalls)) { - unr = array_index_nospec(unr, NR_syscalls); - regs->ax = x64_sys_call(regs, unr); + if (likely(nr < NR_syscalls)) { + nr = array_index_nospec(nr, NR_syscalls); + regs->ax = x64_sys_call(regs, (unsigned int)nr); return true; } return false; } -static __always_inline void do_syscall_x32(struct pt_regs *regs, int nr) +static __always_inline void do_syscall_x32(struct pt_regs *regs, unsigned long nr) { - /* - * Adjust the starting offset of the table, and convert numbers - * < __X32_SYSCALL_BIT to very high and thus out of range - * numbers for comparisons. - */ - unsigned int xnr = nr - __X32_SYSCALL_BIT; + /* Adjust the starting offset of the table */ + nr -= __X32_SYSCALL_BIT; - if (IS_ENABLED(CONFIG_X86_X32_ABI) && likely(xnr < X32_NR_syscalls)) { - xnr = array_index_nospec(xnr, X32_NR_syscalls); - regs->ax = x32_sys_call(regs, xnr); + if (IS_ENABLED(CONFIG_X86_X32_ABI) && likely(nr < X32_NR_syscalls)) { + nr = array_index_nospec(nr, X32_NR_syscalls); + regs->ax = x32_sys_call(regs, (unsigned int)nr); } } /* Returns true to return using SYSRET, or false to use IRET */ -__visible noinstr bool do_syscall_64(struct pt_regs *regs, int nr) +__visible noinstr bool do_syscall_64(struct pt_regs *regs, long nr) { nr = syscall_enter_from_user_mode_randomize_stack(regs, nr); diff --git a/arch/x86/include/asm/syscall.h b/arch/x86/include/asm/syscall.h index 59a406074dc0..cfe4251fc1fe 100644 --- a/arch/x86/include/asm/syscall.h +++ b/arch/x86/include/asm/syscall.h @@ -164,7 +164,7 @@ static inline int syscall_get_arch(struct task_struct *task) ? AUDIT_ARCH_I386 : AUDIT_ARCH_X86_64; } -bool do_syscall_64(struct pt_regs *regs, int nr); +bool do_syscall_64(struct pt_regs *regs, long nr); void do_int80_emulation(struct pt_regs *regs); #endif /* CONFIG_X86_32 */ From 4a3591287fb7f808e209b4974ed337f609a2006b Mon Sep 17 00:00:00 2001 From: Jinjie Ruan Date: Mon, 13 Jul 2026 10:57:12 +0800 Subject: [PATCH 18/23] entry: Fix seccomp bypass after ptrace with TSYNC Sashiko review pointed out the following issue. If a thread is stopped in syscall_trace_enter() for ptrace, another thread can install a seccomp filter with SECCOMP_FILTER_FLAG_TSYNC (e.g., via seccomp_attach_filter()). This will successfully set SYSCALL_WORK_SECCOMP on the stopped thread, but syscall_trace_enter() evaluates a cached 'work' variable sampled on entry. Consequently, the subsequent check for SYSCALL_WORK_SECCOMP misses the newly assigned flag, and the filter is silently bypassed. This race condition could allow an unprivileged process to execute a prohibited system call (e.g., execve) that the newly installed filter was intended to block, especially since the tracer might have modified the system call number during the ptrace stop. Fix this by re-reading the syscall_work flags after ptrace handling, so that any new SYSCALL_WORK_SECCOMP flag set by another thread via TSYNC during the ptrace stop is observed before the subsequent seccomp check. Fixes: 142781e108b1 ("entry: Provide generic syscall entry functionality") Signed-off-by: Jinjie Ruan Signed-off-by: Thomas Gleixner Cc: stable@vger.kernel.org Link: https://lore.kernel.org/all/20260629132914.1135C1F000E9@smtp.kernel.org/ Link: https://patch.msgid.link/20260713025712.416366-1-ruanjinjie@huawei.com --- include/linux/entry-common.h | 3 +++ 1 file changed, 3 insertions(+) diff --git a/include/linux/entry-common.h b/include/linux/entry-common.h index 07d97def7dcb..299f13c78a6f 100644 --- a/include/linux/entry-common.h +++ b/include/linux/entry-common.h @@ -97,6 +97,9 @@ static __always_inline long syscall_trace_enter(struct pt_regs *regs, unsigned l if (!arch_ptrace_report_syscall_permit_entry(regs) || (work & SYSCALL_WORK_SYSCALL_EMU)) return -1L; + + /* ptrace might have changed work flags */ + work = READ_ONCE(current_thread_info()->syscall_work); } /* Do seccomp after ptrace, to catch any tracer changes. */ From 8383e05af734355ba5cdd348991eaa71f6003e71 Mon Sep 17 00:00:00 2001 From: Jinjie Ruan Date: Mon, 13 Jul 2026 11:54:22 +0800 Subject: [PATCH 19/23] syscall_user_dispatch: Introduce ARCH_SUPPORTS_SYSCALL_USER_DISPATCH MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Currently, only x86 genuinely implements and supports Syscall User Dispatch (SUD). Multiple architectures provide a stub arch_syscall_is_vdso_sigreturn() returning 'false' simply to satisfy GENERIC_ENTRY compilation, which creates a false impression of feature support. Introduce ARCH_SUPPORTS_SYSCALL_USER_DISPATCH to decouple this mechanism from GENERIC_ENTRY. Select it exclusively on x86 and remove the redundant stub functions from other architectures. Suggested-by: Mark Rutland Signed-off-by: Jinjie Ruan Signed-off-by: Thomas Gleixner Reviewed-by: Thomas Weißschuh Link: https://patch.msgid.link/20260713035422.582771-1-ruanjinjie@huawei.com --- arch/Kconfig | 4 ++++ arch/loongarch/include/asm/syscall.h | 6 ------ arch/powerpc/include/asm/syscall.h | 5 ----- arch/riscv/include/asm/syscall.h | 5 ----- arch/s390/include/asm/syscall.h | 5 ----- arch/x86/Kconfig | 1 + 6 files changed, 5 insertions(+), 21 deletions(-) diff --git a/arch/Kconfig b/arch/Kconfig index d2dbed524f15..066263cc44fe 100644 --- a/arch/Kconfig +++ b/arch/Kconfig @@ -114,8 +114,12 @@ config GENERIC_ENTRY select GENERIC_IRQ_ENTRY select GENERIC_SYSCALL +config ARCH_SUPPORTS_SYSCALL_USER_DISPATCH + bool + config SYSCALL_USER_DISPATCH bool "Syscall User Dispatch" + depends on ARCH_SUPPORTS_SYSCALL_USER_DISPATCH depends on GENERIC_ENTRY default y help diff --git a/arch/loongarch/include/asm/syscall.h b/arch/loongarch/include/asm/syscall.h index df8ea223c77b..d9275010f548 100644 --- a/arch/loongarch/include/asm/syscall.h +++ b/arch/loongarch/include/asm/syscall.h @@ -84,10 +84,4 @@ static inline int syscall_get_arch(struct task_struct *task) return AUDIT_ARCH_LOONGARCH64; #endif } - -static inline bool arch_syscall_is_vdso_sigreturn(struct pt_regs *regs) -{ - return false; -} - #endif /* __ASM_LOONGARCH_SYSCALL_H */ diff --git a/arch/powerpc/include/asm/syscall.h b/arch/powerpc/include/asm/syscall.h index 834fcc4f7b54..4b3c52ed6e9d 100644 --- a/arch/powerpc/include/asm/syscall.h +++ b/arch/powerpc/include/asm/syscall.h @@ -139,9 +139,4 @@ static inline int syscall_get_arch(struct task_struct *task) else return AUDIT_ARCH_PPC64; } - -static inline bool arch_syscall_is_vdso_sigreturn(struct pt_regs *regs) -{ - return false; -} #endif /* _ASM_SYSCALL_H */ diff --git a/arch/riscv/include/asm/syscall.h b/arch/riscv/include/asm/syscall.h index 8067e666a4ca..987c9a78806f 100644 --- a/arch/riscv/include/asm/syscall.h +++ b/arch/riscv/include/asm/syscall.h @@ -112,11 +112,6 @@ static inline void syscall_handler(struct pt_regs *regs, ulong syscall) regs->a0 = fn(regs); } -static inline bool arch_syscall_is_vdso_sigreturn(struct pt_regs *regs) -{ - return false; -} - asmlinkage long sys_riscv_flush_icache(uintptr_t, uintptr_t, uintptr_t); asmlinkage long sys_riscv_hwprobe(struct riscv_hwprobe *, size_t, size_t, diff --git a/arch/s390/include/asm/syscall.h b/arch/s390/include/asm/syscall.h index 4271e4169f45..5f310caad1fc 100644 --- a/arch/s390/include/asm/syscall.h +++ b/arch/s390/include/asm/syscall.h @@ -89,11 +89,6 @@ static inline int syscall_get_arch(struct task_struct *task) return AUDIT_ARCH_S390X; } -static inline bool arch_syscall_is_vdso_sigreturn(struct pt_regs *regs) -{ - return false; -} - #define SYSCALL_FMT_0 #define SYSCALL_FMT_1 , "0" (r2) #define SYSCALL_FMT_2 , "d" (r3) SYSCALL_FMT_1 diff --git a/arch/x86/Kconfig b/arch/x86/Kconfig index bdad90f210e4..f3f8ad107eeb 100644 --- a/arch/x86/Kconfig +++ b/arch/x86/Kconfig @@ -335,6 +335,7 @@ config X86 select SCHED_SMT if SMP select ARCH_SUPPORTS_SCHED_CLUSTER if SMP select ARCH_SUPPORTS_SCHED_MC if SMP + select ARCH_SUPPORTS_SYSCALL_USER_DISPATCH select HAVE_SINGLE_FTRACE_DIRECT_OPS if X86_64 && DYNAMIC_FTRACE_WITH_DIRECT_CALLS config INSTRUCTION_DECODER From 6f25517010ddd3f8080d7e06b9b1cb1b64b73772 Mon Sep 17 00:00:00 2001 From: Thomas Gleixner Date: Sun, 12 Jul 2026 23:25:17 +0200 Subject: [PATCH 20/23] entry: Rework syscall_audit_enter() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Move it out of line and let it reread the syscall number on it's own. That makes the low level entry code denser and allows to move the reread to the call site of syscall_trace_enter() once the tracer is fixed up. To prevent the compiler from putting audit_context() out of line and thereby breaking dead code elimination, mark audit_context() __always_inline. Signed-off-by: Thomas Gleixner Tested-by: Michal Suchánek Reviewed-by: Jinjie Ruan Link: https://patch.msgid.link/20260712141346.576865340@kernel.org --- include/linux/entry-common.h | 14 +++----------- kernel/entry/syscall-common.c | 12 ++++++++++++ 2 files changed, 15 insertions(+), 11 deletions(-) diff --git a/include/linux/entry-common.h b/include/linux/entry-common.h index 299f13c78a6f..78cfeeb7a5f0 100644 --- a/include/linux/entry-common.h +++ b/include/linux/entry-common.h @@ -60,16 +60,7 @@ static __always_inline bool arch_ptrace_report_syscall_permit_entry(struct pt_re long trace_syscall_enter(struct pt_regs *regs, long syscall); void trace_syscall_exit(struct pt_regs *regs, long ret); - -static inline void syscall_enter_audit(struct pt_regs *regs, long syscall) -{ - if (unlikely(audit_context())) { - unsigned long args[6]; - - syscall_get_arguments(current, regs, args); - audit_syscall_entry(syscall, args[0], args[1], args[2], args[3]); - } -} +void syscall_enter_audit(struct pt_regs *regs); static __always_inline long syscall_trace_enter(struct pt_regs *regs, unsigned long work, long syscall) @@ -114,7 +105,8 @@ static __always_inline long syscall_trace_enter(struct pt_regs *regs, unsigned l if (unlikely(work & SYSCALL_WORK_SYSCALL_TRACEPOINT)) syscall = trace_syscall_enter(regs, syscall); - syscall_enter_audit(regs, syscall); + if (unlikely(audit_context())) + syscall_enter_audit(regs); return syscall; } diff --git a/kernel/entry/syscall-common.c b/kernel/entry/syscall-common.c index cd4967a9c53e..b3cde6fcee3e 100644 --- a/kernel/entry/syscall-common.c +++ b/kernel/entry/syscall-common.c @@ -1,5 +1,6 @@ // SPDX-License-Identifier: GPL-2.0 +#include #include #define CREATE_TRACE_POINTS @@ -21,3 +22,14 @@ void trace_syscall_exit(struct pt_regs *regs, long ret) { trace_sys_exit(regs, ret); } + +#ifdef CONFIG_AUDITSYSCALL +void syscall_enter_audit(struct pt_regs *regs) +{ + long syscall = syscall_get_nr(current, regs); + unsigned long args[6]; + + syscall_get_arguments(current, regs, args); + __audit_syscall_entry(syscall, args[0], args[1], args[2], args[3]); +} +#endif From dfc98c7a46424683326c8d8ffc70e81548c59fdb Mon Sep 17 00:00:00 2001 From: Thomas Gleixner Date: Sun, 12 Jul 2026 23:25:22 +0200 Subject: [PATCH 21/23] entry: Rework trace_syscall_enter() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Reread the syscall number from pt_regs and stop returning the eventually modified syscall number. That moves the reread to the end of syscall_trace_enter() and prepares for moving it to the call site. No functional change. Signed-off-by: Thomas Gleixner Tested-by: Michal Suchánek Reviewed-by: Jinjie Ruan Link: https://patch.msgid.link/20260712141346.639115923@kernel.org --- include/linux/entry-common.h | 10 ++++------ kernel/entry/syscall-common.c | 9 ++------- 2 files changed, 6 insertions(+), 13 deletions(-) diff --git a/include/linux/entry-common.h b/include/linux/entry-common.h index 78cfeeb7a5f0..38d148477729 100644 --- a/include/linux/entry-common.h +++ b/include/linux/entry-common.h @@ -58,7 +58,7 @@ static __always_inline bool arch_ptrace_report_syscall_permit_entry(struct pt_re } #endif -long trace_syscall_enter(struct pt_regs *regs, long syscall); +void trace_syscall_enter(struct pt_regs *regs); void trace_syscall_exit(struct pt_regs *regs, long ret); void syscall_enter_audit(struct pt_regs *regs); @@ -99,16 +99,14 @@ static __always_inline long syscall_trace_enter(struct pt_regs *regs, unsigned l return -1L; } - /* Either of the above might have changed the syscall number */ - syscall = syscall_get_nr(current, regs); - if (unlikely(work & SYSCALL_WORK_SYSCALL_TRACEPOINT)) - syscall = trace_syscall_enter(regs, syscall); + trace_syscall_enter(regs); if (unlikely(audit_context())) syscall_enter_audit(regs); - return syscall; + /* Either of the above might have changed the syscall number */ + return syscall_get_nr(current, regs); } /** diff --git a/kernel/entry/syscall-common.c b/kernel/entry/syscall-common.c index b3cde6fcee3e..b8eac9efb6fd 100644 --- a/kernel/entry/syscall-common.c +++ b/kernel/entry/syscall-common.c @@ -8,14 +8,9 @@ /* Out of line to prevent tracepoint code duplication */ -long trace_syscall_enter(struct pt_regs *regs, long syscall) +void trace_syscall_enter(struct pt_regs *regs) { - trace_sys_enter(regs, syscall); - /* - * Probes or BPF hooks in the tracepoint may have changed the - * system call number. Reread it. - */ - return syscall_get_nr(current, regs); + trace_sys_enter(regs, syscall_get_nr(current, regs)); } void trace_syscall_exit(struct pt_regs *regs, long ret) From 71ff30013f19ca46c2c72c25731c32b6cc7b5620 Mon Sep 17 00:00:00 2001 From: Thomas Gleixner Date: Sun, 12 Jul 2026 23:25:27 +0200 Subject: [PATCH 22/23] entry: Make return type of syscall_trace_enter() bool MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This prepares for changing the return types of syscall_enter_from_user_mode[_work]() to bool, which in turn separates the decision of invoking the syscall from the syscall number, which might have been changed in the call by ptrace, seccomp, tracing. Signed-off-by: Thomas Gleixner Tested-by: Michal Suchánek Reviewed-by: Jinjie Ruan Link: https://patch.msgid.link/20260712141346.699072205@kernel.org --- include/linux/entry-common.h | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/include/linux/entry-common.h b/include/linux/entry-common.h index 38d148477729..df221d919e76 100644 --- a/include/linux/entry-common.h +++ b/include/linux/entry-common.h @@ -72,7 +72,7 @@ static __always_inline long syscall_trace_enter(struct pt_regs *regs, unsigned l */ if (work & SYSCALL_WORK_SYSCALL_USER_DISPATCH) { if (syscall_user_dispatch(regs)) - return -1L; + return false; } /* @@ -87,7 +87,7 @@ static __always_inline long syscall_trace_enter(struct pt_regs *regs, unsigned l if (work & (SYSCALL_WORK_SYSCALL_TRACE | SYSCALL_WORK_SYSCALL_EMU)) { if (!arch_ptrace_report_syscall_permit_entry(regs) || (work & SYSCALL_WORK_SYSCALL_EMU)) - return -1L; + return false; /* ptrace might have changed work flags */ work = READ_ONCE(current_thread_info()->syscall_work); @@ -96,7 +96,7 @@ static __always_inline long syscall_trace_enter(struct pt_regs *regs, unsigned l /* Do seccomp after ptrace, to catch any tracer changes. */ if (work & SYSCALL_WORK_SECCOMP) { if (!__seccomp_permit_syscall()) - return -1L; + return false; } if (unlikely(work & SYSCALL_WORK_SYSCALL_TRACEPOINT)) @@ -105,8 +105,7 @@ static __always_inline long syscall_trace_enter(struct pt_regs *regs, unsigned l if (unlikely(audit_context())) syscall_enter_audit(regs); - /* Either of the above might have changed the syscall number */ - return syscall_get_nr(current, regs); + return true; } /** @@ -136,8 +135,13 @@ static __always_inline long syscall_enter_from_user_mode_work(struct pt_regs *re { unsigned long work = READ_ONCE(current_thread_info()->syscall_work); - if (work & SYSCALL_WORK_ENTER) - syscall = syscall_trace_enter(regs, work, syscall); + if (work & SYSCALL_WORK_ENTER) { + if (!syscall_trace_enter(regs, work, syscall)) + return -1L; + + /* Reread the syscall number as it might have been modified */ + syscall = syscall_get_nr(current, regs); + } return syscall; } From 05c033db7e9ad3c34f6968ec568cb6ee01051c57 Mon Sep 17 00:00:00 2001 From: Thomas Gleixner Date: Sun, 12 Jul 2026 23:25:32 +0200 Subject: [PATCH 23/23] entry, treewide: Make syscall_enter_from_user_mode[_work]() indicate syscall execution MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The return values of syscall_enter_from_user_mode[_work]() are non-intuitive. Both functions return the syscall number which should be invoked by the architecture specific syscall entry code. The returned number can be: - the unmodified syscall number which was handed in by the caller - a modified syscall number (ptrace, seccomp, trace/probe/bpf) That has an additional twist. If the return value is -1L then the caller is not allowed to modify the return value as that indicates that the modifying entity requests to abort the syscall and set the return value already. That can obviously not be differentiated from a syscall which handed in -1 as syscall number. The most trivial way to deal with that is: set_return_value(regs, -ENOSYS); nr = syscall_enter_from_user_mode(regs, nr); if (valid(nr)) handle_syscall(regs, nr); That's what LOONGARCH, RISCV, and X86 do. But PowerPC and S390 do not preset the return value, so when user space hands in -1 and there is nothing setting the return value in the entry work code, then the syscall is skipped but the return value is whatever random data has been in the return value register. Change the return values of syscall_enter_from_user_mode[_work]() to boolean and return false, when either ptrace or seccomp request to skip the syscall. If they return true, update the syscall number as it might have been changed. That results in slightly different behaviour of the architectures versus tracing. If the syscall tracepoint has probe/BPF attached, those might set the syscall number to -1 and also set the return value. PowerPC and S390 will then overwrite that value with -ENOSYS. The other architectures will just ignore it like any other invalid syscall and use the modified one. Originally-by: Michal Suchánek Signed-off-by: Thomas Gleixner Tested-by: Michal Suchánek Link: https://patch.msgid.link/20260712141346.772209074@kernel.org --- Documentation/core-api/entry.rst | 49 ++++++++++++++++++++++++-------- arch/loongarch/kernel/syscall.c | 14 ++++----- arch/powerpc/kernel/syscall.c | 3 +- arch/riscv/kernel/traps.c | 11 ++++--- arch/s390/kernel/syscall.c | 7 +++-- arch/x86/entry/syscall_32.c | 25 ++++++++-------- arch/x86/entry/syscall_64.c | 12 ++++---- include/linux/entry-common.h | 32 ++++++++++----------- 8 files changed, 90 insertions(+), 63 deletions(-) diff --git a/Documentation/core-api/entry.rst b/Documentation/core-api/entry.rst index 44e9608038ea..79fdaed954d9 100644 --- a/Documentation/core-api/entry.rst +++ b/Documentation/core-api/entry.rst @@ -58,26 +58,51 @@ state transitions must run with interrupts disabled. Syscalls -------- -Syscall-entry code starts in assembly code and calls out into low-level C code -after establishing low-level architecture-specific state and stack frames. This -low-level C code must not be instrumented. A typical syscall handling function -invoked from low-level assembly code looks like this: +Syscall-entry code starts in assembly code and calls out into low-level C +code after establishing low-level architecture-specific state and stack +frames. This low-level C code must not be instrumented. The recommended +syscall handling function invoked from low-level assembly code looks like +this: .. code-block:: c - noinstr void syscall(struct pt_regs *regs, int nr) + noinstr void syscall(struct pt_regs *regs, long nr) { arch_syscall_enter(regs); - nr = syscall_enter_from_user_mode_randomize_stack(regs, nr); - - instrumentation_begin(); - if (!invoke_syscall(regs, nr) && nr != -1) - result_reg(regs) = __sys_ni_syscall(regs); - instrumentation_end(); - + result_reg(regs) = -ENOSYS; + if (syscall_enter_from_user_mode_randomize_stack(regs, &nr)) { + instrumentation_begin(); + if (valid(nr) + result_reg(regs) = invoke_syscall(regs, nr); + instrumentation_end(); + } syscall_exit_to_user_mode(regs); } +This is the most resilent variant as it has always a guaranteed valid +return code. The alternative variant is: + +.. code-block:: c + + noinstr void syscall(struct pt_regs *regs, long nr) + { + arch_syscall_enter(regs); + if (syscall_enter_from_user_mode_randomize_stack(regs, &nr)) { + instrumentation_begin(); + if (valid(nr) + result_reg(regs) = invoke_syscall(regs, nr); + else + result_reg(regs) = -ENOSYS; + instrumentation_end(); + } + syscall_exit_to_user_mode(regs); + } + +That works for most situations except when a probe/BPF attached to the +syscall tracepoint sets an invalid syscall number e.g. -1 and also modifies +the result register. So this variant will obviously overwrite the modified +result with -ENOSYS. + syscall_enter_from_user_mode_randomize_stack() first invokes enter_from_user_mode_randomize_stack() which establishes state in the following order: diff --git a/arch/loongarch/kernel/syscall.c b/arch/loongarch/kernel/syscall.c index 142a9c20292c..62264e422467 100644 --- a/arch/loongarch/kernel/syscall.c +++ b/arch/loongarch/kernel/syscall.c @@ -57,8 +57,8 @@ typedef long (*sys_call_fn)(unsigned long, unsigned long, void noinstr __no_stack_protector do_syscall(struct pt_regs *regs) { - unsigned long nr; sys_call_fn syscall_fn; + unsigned long nr; nr = regs->regs[11]; /* Set for syscall restarting */ @@ -69,12 +69,12 @@ void noinstr __no_stack_protector do_syscall(struct pt_regs *regs) regs->orig_a0 = regs->regs[4]; regs->regs[4] = -ENOSYS; - nr = syscall_enter_from_user_mode_randomize_stack(regs, nr); - - if (nr < NR_syscalls) { - 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]); + if (likely(syscall_enter_from_user_mode_randomize_stack(regs, &nr))) { + if (nr < NR_syscalls) { + 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]); + } } syscall_exit_to_user_mode(regs); diff --git a/arch/powerpc/kernel/syscall.c b/arch/powerpc/kernel/syscall.c index b4d0159e29f8..1440dcabe052 100644 --- a/arch/powerpc/kernel/syscall.c +++ b/arch/powerpc/kernel/syscall.c @@ -18,7 +18,8 @@ notrace long system_call_exception(struct pt_regs *regs, unsigned long r0) long ret; syscall_fn f; - r0 = syscall_enter_from_user_mode_randomize_stack(regs, r0); + if (unlikely(!syscall_enter_from_user_mode_randomize_stack(regs, &r0))) + return syscall_get_error(current, regs); if (unlikely(r0 >= NR_syscalls)) { if (unlikely(trap_is_unsupported_scv(regs))) { diff --git a/arch/riscv/kernel/traps.c b/arch/riscv/kernel/traps.c index bb180116ef0a..2f57fd48ec28 100644 --- a/arch/riscv/kernel/traps.c +++ b/arch/riscv/kernel/traps.c @@ -332,13 +332,12 @@ void do_trap_ecall_u(struct pt_regs *regs) riscv_v_vstate_discard(regs); - syscall = syscall_enter_from_user_mode_randomize_stack(regs, syscall); - - if (syscall >= 0 && syscall < NR_syscalls) { - syscall = array_index_nospec(syscall, NR_syscalls); - syscall_handler(regs, syscall); + if (likely(syscall_enter_from_user_mode_randomize_stack(regs, &syscall))) { + if (syscall >= 0 && syscall < NR_syscalls) { + syscall = array_index_nospec(syscall, NR_syscalls); + syscall_handler(regs, syscall); + } } - syscall_exit_to_user_mode(regs); } else { irqentry_state_t state = irqentry_nmi_enter(regs); diff --git a/arch/s390/kernel/syscall.c b/arch/s390/kernel/syscall.c index 73abda2a9080..4ac80e2478d7 100644 --- a/arch/s390/kernel/syscall.c +++ b/arch/s390/kernel/syscall.c @@ -96,6 +96,7 @@ SYSCALL_DEFINE0(ni_syscall) void noinstr __do_syscall(struct pt_regs *regs, int per_trap) { unsigned long nr; + bool permit; enter_from_user_mode_randomize_stack(regs); @@ -121,7 +122,9 @@ void noinstr __do_syscall(struct pt_regs *regs, int per_trap) regs->psw.addr = current->restart_block.arch_data; current->restart_block.arch_data = 1; } - nr = syscall_enter_from_user_mode_work(regs, nr); + + permit = syscall_enter_from_user_mode_work(regs, &nr); + /* * In the s390 ptrace ABI, both the syscall number and the return value * use gpr2. However, userspace puts the syscall number either in the @@ -129,7 +132,7 @@ void noinstr __do_syscall(struct pt_regs *regs, int per_trap) * work, the ptrace code sets PIF_SYSCALL_RET_SET, which is checked here * and if set, the syscall will be skipped. */ - if (unlikely(test_and_clear_pt_regs_flag(regs, PIF_SYSCALL_RET_SET))) + if (unlikely(test_and_clear_pt_regs_flag(regs, PIF_SYSCALL_RET_SET) || !permit)) goto out; regs->gprs[2] = -ENOSYS; if (likely(nr < NR_syscalls)) { diff --git a/arch/x86/entry/syscall_32.c b/arch/x86/entry/syscall_32.c index 92369d84c0c8..91123a90710c 100644 --- a/arch/x86/entry/syscall_32.c +++ b/arch/x86/entry/syscall_32.c @@ -161,8 +161,9 @@ __visible noinstr void do_int80_emulation(struct pt_regs *regs) nr = syscall_32_enter(regs); local_irq_enable(); - nr = syscall_enter_from_user_mode_work(regs, nr); - do_syscall_32_irqs_on(regs, nr); + + if (likely(syscall_enter_from_user_mode_work(regs, &nr))) + do_syscall_32_irqs_on(regs, nr); instrumentation_end(); syscall_exit_to_user_mode(regs); @@ -223,8 +224,8 @@ DEFINE_FREDENTRY_RAW(int80_emulation) nr = syscall_32_enter(regs); local_irq_enable(); - nr = syscall_enter_from_user_mode_work(regs, nr); - do_syscall_32_irqs_on(regs, nr); + if (likely(syscall_enter_from_user_mode_work(regs, &nr))) + do_syscall_32_irqs_on(regs, nr); instrumentation_end(); syscall_exit_to_user_mode(regs); @@ -243,13 +244,13 @@ __visible noinstr void do_int80_syscall_32(struct pt_regs *regs) * orig_ax, the int return value truncates it. This matches * the semantics of syscall_get_nr(). */ - nr = syscall_enter_from_user_mode_randomize_stack(regs, nr); + if (likely(syscall_enter_from_user_mode_randomize_stack(regs, &nr))) { + instrumentation_begin(); - instrumentation_begin(); + do_syscall_32_irqs_on(regs, nr); - do_syscall_32_irqs_on(regs, nr); - - instrumentation_end(); + instrumentation_end(); + } syscall_exit_to_user_mode(regs); } #endif /* !CONFIG_IA32_EMULATION */ @@ -286,10 +287,8 @@ static noinstr bool __do_fast_syscall_32(struct pt_regs *regs) return false; } - nr = syscall_enter_from_user_mode_work(regs, nr); - - /* Now this is just like a normal syscall. */ - do_syscall_32_irqs_on(regs, nr); + if (likely(syscall_enter_from_user_mode_work(regs, &nr))) + do_syscall_32_irqs_on(regs, nr); instrumentation_end(); syscall_exit_to_user_mode(regs); diff --git a/arch/x86/entry/syscall_64.c b/arch/x86/entry/syscall_64.c index 9a2762e32ae8..966d3d1b7586 100644 --- a/arch/x86/entry/syscall_64.c +++ b/arch/x86/entry/syscall_64.c @@ -78,14 +78,14 @@ static __always_inline void do_syscall_x32(struct pt_regs *regs, unsigned long n /* Returns true to return using SYSRET, or false to use IRET */ __visible noinstr bool do_syscall_64(struct pt_regs *regs, long nr) { - nr = syscall_enter_from_user_mode_randomize_stack(regs, nr); + if (likely(syscall_enter_from_user_mode_randomize_stack(regs, &nr))) { + instrumentation_begin(); - instrumentation_begin(); + if (!do_syscall_x64(regs, nr)) + do_syscall_x32(regs, nr); - if (!do_syscall_x64(regs, nr)) - do_syscall_x32(regs, nr); - - instrumentation_end(); + instrumentation_end(); + } syscall_exit_to_user_mode(regs); /* diff --git a/include/linux/entry-common.h b/include/linux/entry-common.h index df221d919e76..6574b7183c01 100644 --- a/include/linux/entry-common.h +++ b/include/linux/entry-common.h @@ -114,16 +114,15 @@ static __always_inline long syscall_trace_enter(struct pt_regs *regs, unsigned l * @regs: Pointer to currents pt_regs * @syscall: The syscall number * - * Invoked from architecture specific syscall entry code with interrupts - * enabled after invoking enter_from_user_mode(), enabling interrupts and - * extra architecture specific work. + * Invoked from architecture specific syscall entry code with interrupts enabled + * after invoking enter_from_user_mode(), enabling interrupts and extra + * architecture specific work with the syscall return value preset to -ENOSYS. * - * Returns: The original or a modified syscall number + * Returns: True if the syscall should be invoked, False otherwise. * - * If the returned syscall number is -1 then the syscall should be - * skipped. In this case the caller may invoke syscall_set_error() or - * syscall_set_return_value() first. If neither of those are called and -1 - * is returned, then the syscall will fail with ENOSYS. + * If the return value is false, the caller must skip the syscall and leave the + * syscall return value unmodified as it might have been set by one of the entry + * work functions. * * It handles the following work items: * @@ -131,19 +130,20 @@ static __always_inline long syscall_trace_enter(struct pt_regs *regs, unsigned l * ptrace_report_syscall_permit_entry(), __seccomp_permit_syscall(), trace_sys_enter() * 2) Invocation of audit_syscall_entry() */ -static __always_inline long syscall_enter_from_user_mode_work(struct pt_regs *regs, long syscall) +static __always_inline bool syscall_enter_from_user_mode_work(struct pt_regs *regs, long *syscall) { unsigned long work = READ_ONCE(current_thread_info()->syscall_work); - if (work & SYSCALL_WORK_ENTER) { - if (!syscall_trace_enter(regs, work, syscall)) - return -1L; + if (!(work & SYSCALL_WORK_ENTER)) + return true; - /* Reread the syscall number as it might have been modified */ - syscall = syscall_get_nr(current, regs); - } + if (unlikely(!syscall_trace_enter(regs, work, *syscall))) + return false; - return syscall; + /* Reread the syscall number as it might have been modified */ + *syscall = syscall_get_nr(current, regs); + + return true; } /**