| // SPDX-License-Identifier: GPL-2.0 |
| |
| #include <linux/audit.h> |
| #include <linux/entry-common.h> |
| #include "common.h" |
| |
| #define CREATE_TRACE_POINTS |
| #include <trace/events/syscalls.h> |
| |
| 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]); |
| } |
| } |
| |
| long syscall_trace_enter(struct pt_regs *regs, long syscall, |
| unsigned long work) |
| { |
| long ret = 0; |
| |
| /* |
| * Handle Syscall User Dispatch. This must comes first, since |
| * the ABI here can be something that doesn't make sense for |
| * other syscall_work features. |
| */ |
| if (work & SYSCALL_WORK_SYSCALL_USER_DISPATCH) { |
| if (syscall_user_dispatch(regs)) |
| return -1L; |
| } |
| |
| /* Handle ptrace */ |
| if (work & (SYSCALL_WORK_SYSCALL_TRACE | SYSCALL_WORK_SYSCALL_EMU)) { |
| ret = ptrace_report_syscall_entry(regs); |
| if (ret || (work & SYSCALL_WORK_SYSCALL_EMU)) |
| return -1L; |
| } |
| |
| /* Do seccomp after ptrace, to catch any tracer changes. */ |
| if (work & SYSCALL_WORK_SECCOMP) { |
| ret = __secure_computing(); |
| if (ret == -1L) |
| return ret; |
| } |
| |
| /* Either of the above might have changed the syscall number */ |
| syscall = syscall_get_nr(current, regs); |
| |
| if (unlikely(work & SYSCALL_WORK_SYSCALL_TRACEPOINT)) { |
| trace_sys_enter(regs, syscall); |
| /* |
| * Probes or BPF hooks in the tracepoint may have changed the |
| * system call number as well. |
| */ |
| syscall = syscall_get_nr(current, regs); |
| } |
| |
| syscall_enter_audit(regs, syscall); |
| |
| return ret ? : syscall; |
| } |
| |
| noinstr void syscall_enter_from_user_mode_prepare(struct pt_regs *regs) |
| { |
| enter_from_user_mode(regs); |
| instrumentation_begin(); |
| local_irq_enable(); |
| instrumentation_end(); |
| } |
| |
| /* |
| * 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(). |
| */ |
| static inline bool report_single_step(unsigned long work) |
| { |
| if (work & SYSCALL_WORK_SYSCALL_EMU) |
| return false; |
| |
| return work & SYSCALL_WORK_SYSCALL_EXIT_TRAP; |
| } |
| |
| void syscall_exit_work(struct pt_regs *regs, unsigned long work) |
| { |
| bool step; |
| |
| /* |
| * If the syscall was rolled back due to syscall user dispatching, |
| * then the tracers below are not invoked for the same reason as |
| * the entry side was not invoked in syscall_trace_enter(): The ABI |
| * 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; |
| return; |
| } |
| } |
| |
| audit_syscall_exit(regs); |
| |
| if (work & SYSCALL_WORK_SYSCALL_TRACEPOINT) |
| trace_sys_exit(regs, syscall_get_return_value(current, regs)); |
| |
| step = report_single_step(work); |
| if (step || work & SYSCALL_WORK_SYSCALL_TRACE) |
| ptrace_report_syscall_exit(regs, step); |
| } |