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/Documentation/core-api/entry.rst b/Documentation/core-api/entry.rst index 71d8eedc0549..79fdaed954d9 100644 --- a/Documentation/core-api/entry.rst +++ b/Documentation/core-api/entry.rst @@ -58,32 +58,59 @@ 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(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); } -syscall_enter_from_user_mode() first invokes enter_from_user_mode() which -establishes state in the following order: +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: * 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 +126,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/arch/Kconfig b/arch/Kconfig index fa7507ac8e13..066263cc44fe 100644 --- a/arch/Kconfig +++ b/arch/Kconfig @@ -114,6 +114,20 @@ 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 + 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 @@ -637,7 +651,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..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); @@ -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/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 7951b2c06fec..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; @@ -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..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; @@ -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..ee2867a1576e 100644 --- a/arch/csky/kernel/ptrace.c +++ b/arch/csky/kernel/ptrace.c @@ -320,10 +320,10 @@ 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 (secure_computing() == -1) + if (!seccomp_permit_syscall()) return -1; if (test_thread_flag(TIF_SYSCALL_TRACEPOINT)) 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/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/loongarch/kernel/syscall.c b/arch/loongarch/kernel/syscall.c index 94c1c3b5b0b5..62264e422467 100644 --- a/arch/loongarch/kernel/syscall.c +++ b/arch/loongarch/kernel/syscall.c @@ -11,7 +11,6 @@ #include #include #include -#include #include #include @@ -58,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 */ @@ -70,14 +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(regs, nr); - - add_random_kstack_offset(); - - 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/m68k/kernel/ptrace.c b/arch/m68k/kernel/ptrace.c index cfa2df24eced..4575f6487a9a 100644 --- a/arch/m68k/kernel/ptrace.c +++ b/arch/m68k/kernel/ptrace.c @@ -279,9 +279,9 @@ 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 (secure_computing() == -1) + if (!seccomp_permit_syscall()) return -1; return ret; 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 3f4c94c88124..5a0e298857b6 100644 --- a/arch/mips/kernel/ptrace.c +++ b/arch/mips/kernel/ptrace.c @@ -1324,11 +1324,11 @@ 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; } - if (secure_computing()) + if (!seccomp_permit_syscall()) return -1; if (unlikely(test_thread_flag(TIF_SYSCALL_TRACEPOINT))) 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 8a17ab7e6e0b..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, @@ -351,7 +349,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/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/powerpc/kernel/syscall.c b/arch/powerpc/kernel/syscall.c index a9da2af6efa8..1440dcabe052 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,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); + 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/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/riscv/kernel/traps.c b/arch/riscv/kernel/traps.c index 8c62c771a656..2f57fd48ec28 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,15 +332,12 @@ 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(); - - 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/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/s390/kernel/syscall.c b/arch/s390/kernel/syscall.c index 75d5a3cab14e..4ac80e2478d7 100644 --- a/arch/s390/kernel/syscall.c +++ b/arch/s390/kernel/syscall.c @@ -96,9 +96,10 @@ 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); - enter_from_user_mode(regs); - add_random_kstack_offset(); regs->psw = get_lowcore()->svc_old_psw; regs->int_code = get_lowcore()->svc_int_code; update_timer_sys(); @@ -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/sh/kernel/ptrace_32.c b/arch/sh/kernel/ptrace_32.c index 06f765d71a29..8794081483fb 100644 --- a/arch/sh/kernel/ptrace_32.c +++ b/arch/sh/kernel/ptrace_32.c @@ -455,12 +455,12 @@ 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; } - if (secure_computing() == -1) + if (!seccomp_permit_syscall()) return -1; if (unlikely(test_thread_flag(TIF_SYSCALL_TRACEPOINT))) 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/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/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 diff --git a/arch/x86/entry/syscall_32.c b/arch/x86/entry/syscall_32.c index 31b9492fe851..91123a90710c 100644 --- a/arch/x86/entry/syscall_32.c +++ b/arch/x86/entry/syscall_32.c @@ -41,7 +41,9 @@ 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) + +/* 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) { #include @@ -49,7 +51,7 @@ 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,19 +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); - } else if (nr != -1) { - regs->ax = __ia32_sys_ni_syscall(regs); + if (likely(nr < IA32_NR_syscalls)) { + nr = array_index_nospec(nr, IA32_NR_syscalls); + regs->ax = ia32_sys_call(regs, (unsigned int)nr); } } @@ -128,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))) { @@ -142,10 +136,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())) @@ -168,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); @@ -208,13 +202,11 @@ __visible noinstr void do_int80_emulation(struct pt_regs *regs) */ DEFINE_FREDENTRY_RAW(int80_emulation) { - int nr; + long 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. @@ -232,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); @@ -245,38 +237,32 @@ 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 * orig_ax, the int return value truncates it. This matches * the semantics of syscall_get_nr(). */ - nr = syscall_enter_from_user_mode(regs, nr); - instrumentation_begin(); + if (likely(syscall_enter_from_user_mode_randomize_stack(regs, &nr))) { + instrumentation_begin(); - add_random_kstack_offset(); - 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 */ 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; - /* - * 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)) { @@ -301,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 71f032504e73..966d3d1b7586 100644 --- a/arch/x86/entry/syscall_64.c +++ b/arch/x86/entry/syscall_64.c @@ -32,7 +32,9 @@ 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) + +/* 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) { #include @@ -40,63 +42,50 @@ 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) +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 bool 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); - return true; + 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); } - return false; } /* 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(regs, nr); + if (likely(syscall_enter_from_user_mode_randomize_stack(regs, &nr))) { + instrumentation_begin(); - instrumentation_begin(); - add_random_kstack_offset(); + if (!do_syscall_x64(regs, nr)) + do_syscall_x32(regs, nr); - 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); + instrumentation_end(); } - - instrumentation_end(); syscall_exit_to_user_mode(regs); /* 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/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 diff --git a/arch/x86/include/asm/syscall.h b/arch/x86/include/asm/syscall.h index c10dbb74cd00..cfe4251fc1fe 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 @@ -172,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 */ diff --git a/arch/xtensa/kernel/ptrace.c b/arch/xtensa/kernel/ptrace.c index b80d54b2ea34..364e4fdabb00 100644 --- a/arch/xtensa/kernel/ptrace.c +++ b/arch/xtensa/kernel/ptrace.c @@ -547,14 +547,13 @@ 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; } - 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/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 416a3352261f..6574b7183c01 100644 --- a/include/linux/entry-common.h +++ b/include/linux/entry-common.h @@ -6,9 +6,11 @@ #include #include #include +#include #include #include #include +#include #include #include @@ -18,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 | \ @@ -37,42 +39,32 @@ 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 -bool syscall_user_dispatch(struct pt_regs *regs); -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); -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) { - 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]); - } -} - -static __always_inline long syscall_trace_enter(struct pt_regs *regs, unsigned long work) -{ - long syscall, ret = 0; - /* * Handle Syscall User Dispatch. This must comes first, since * the ABI here can be something that doesn't make sense for @@ -80,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; } /* @@ -89,31 +81,31 @@ 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)) { - ret = arch_ptrace_report_syscall_entry(regs); - if (ret || (work & SYSCALL_WORK_SYSCALL_EMU)) - return -1L; + if (!arch_ptrace_report_syscall_permit_entry(regs) || + (work & SYSCALL_WORK_SYSCALL_EMU)) + return false; + + /* ptrace might have changed work flags */ + work = READ_ONCE(current_thread_info()->syscall_work); } /* 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 false; } - /* 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); - syscall_enter_audit(regs, syscall); + if (unlikely(audit_context())) + syscall_enter_audit(regs); - return ret ? : syscall; + return true; } /** @@ -122,36 +114,63 @@ 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: * * 1) syscall_work flag dependent invocations of - * ptrace_report_syscall_entry(), __secure_computing(), 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) +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) - syscall = syscall_trace_enter(regs, work); + if (!(work & SYSCALL_WORK_ENTER)) + return true; - return syscall; + if (unlikely(!syscall_trace_enter(regs, work, *syscall))) + return false; + + /* Reread the syscall number as it might have been modified */ + *syscall = syscall_get_nr(current, regs); + + return true; } /** - * syscall_enter_from_user_mode - Establish state and check and handle work - * before invoking a 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 * @@ -160,31 +179,32 @@ static __always_inline long syscall_enter_from_user_mode_work(struct pt_regs *re * 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 + * 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. */ -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; -} +#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; \ +}) /* - * 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) { @@ -232,10 +252,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/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); 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); } 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 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/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/kernel/entry/syscall-common.c b/kernel/entry/syscall-common.c index cd4967a9c53e..b8eac9efb6fd 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 @@ -7,17 +8,23 @@ /* 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) { 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 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 */ 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(); } 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