diff options
Diffstat (limited to 'include/linux/entry-common.h')
| -rw-r--r-- | include/linux/entry-common.h | 164 |
1 files changed, 91 insertions, 73 deletions
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 <linux/irq-entry-common.h> #include <linux/livepatch.h> #include <linux/ptrace.h> +#include <linux/randomize_kstack.h> #include <linux/resume_user_mode.h> #include <linux/seccomp.h> #include <linux/sched.h> +#include <linux/syscall_user_dispatch.h> #include <asm/entry-common.h> #include <asm/syscall.h> @@ -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; + + 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; } /** - * 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); |
