Implement the generic entry framework for arm64 to handle system call entry and exit. This follows the migration of x86, RISC-V, and LoongArch, consolidating architecture-specific syscall tracing and auditing into the common kernel entry infrastructure, making it easier to enable advanced features on arm64, such as "Syscall User Dispatch" and "rseq time slice extension" optimization. [Background] Arm64 has already adopted generic IRQ entry. Completing the conversion to the generic syscall entry framework reduces architectural divergence, simplifies maintenance, and allows arm64 to automatically benefit from improvements in the common entry code. [Changes] 1. Kconfig and Infrastructure: - Select GENERIC_ENTRY and remove GENERIC_IRQ_ENTRY (now implied). - Migrate struct thread_info to use the syscall_work field instead of TIF flags for syscall-related tasks. 2. Thread Info and Flags: - Remove definitions for TIF_SYSCALL_TRACE, TIF_SYSCALL_AUDIT, TIF_SYSCALL_TRACEPOINT, TIF_SECCOMP, and TIF_SYSCALL_EMU. - Replace _TIF_SYSCALL_WORK and _TIF_SYSCALL_EXIT_WORK with the generic SYSCALL_WORK bitmask. - Map single-step state to SYSCALL_EXIT_TRAP in debug-monitors.c. 3. Architecture-Specific Hooks (asm/entry-common.h): Implement following arch function by porting the existing arm64 logic to the generic interface: - arch_ptrace_report_syscall_permit_entry() - arch_ptrace_report_syscall_exit() 4. Cleanup and Refactoring: Remove redundant arm64-specific syscall tracing functions from ptrace.c, including following key functions and related audit/step helpers: - arm64_syscall_exit_to_user_mode_work() - arm64_syscall_trace_enter() - arm64_syscall_exit_work(). - Update el0_svc_common() in syscall.c to use the generic syscall_work checks and entry/exit call sites. [Why this matters] - Unified Interface: Aligns arm64 with the modern kernel entry standard. - Improved Maintainability: Bug fixes in kernel/entry/common.c now apply to arm64 automatically. - Feature Readiness: Simplifies the implementation of future cross-architecture syscall features. [Compatibility] This conversion maintains full ABI compatibility with existing userspace. The ptrace register-saving behavior, seccomp filtering, and syscall tracing semantics remain identical to the previous implementation. Additionally, since arm64 now does not select HAVE_GENERIC_TIF_BITS, there is no functional change regarding rseq management, as those generic entry pathways remain a no-op for this architecture." Cc: Mark Rutland Cc: Will Deacon Cc: Catalin Marinas Cc: Thomas Gleixner Cc: Peter Zijlstra Tested-by: Ada Couprie Diaz Reviewed-by: Ada Couprie Diaz Reviewed-by: Linus Walleij Acked-by: Peter Zijlstra (Intel) Reviewed-by: Yeoreum Yun Reviewed-by: Kevin Brodsky Suggested-by: Kevin Brodsky Suggested-by: Mark Rutland Signed-off-by: Jinjie Ruan --- arch/arm64/Kconfig | 2 +- arch/arm64/include/asm/entry-common.h | 77 +++++++++++++++ arch/arm64/include/asm/syscall.h | 14 --- arch/arm64/include/asm/thread_info.h | 19 +--- arch/arm64/kernel/debug-monitors.c | 8 ++ arch/arm64/kernel/entry-common.c | 2 +- arch/arm64/kernel/ptrace.c | 136 -------------------------- arch/arm64/kernel/signal.c | 2 +- arch/arm64/kernel/syscall.c | 9 +- 9 files changed, 95 insertions(+), 174 deletions(-) diff --git a/arch/arm64/Kconfig b/arch/arm64/Kconfig index b3afe0688919..aea686dce3d5 100644 --- a/arch/arm64/Kconfig +++ b/arch/arm64/Kconfig @@ -128,9 +128,9 @@ config ARM64 select GENERIC_CPU_DEVICES select GENERIC_CPU_VULNERABILITIES select GENERIC_EARLY_IOREMAP + select GENERIC_ENTRY select GENERIC_IDLE_POLL_SETUP select GENERIC_IOREMAP - select GENERIC_IRQ_ENTRY select GENERIC_IRQ_IPI select GENERIC_IRQ_KEXEC_CLEAR_VM_FORWARD select GENERIC_IRQ_PROBE diff --git a/arch/arm64/include/asm/entry-common.h b/arch/arm64/include/asm/entry-common.h index cab8cd78f693..2b053acbe10f 100644 --- a/arch/arm64/include/asm/entry-common.h +++ b/arch/arm64/include/asm/entry-common.h @@ -3,14 +3,21 @@ #ifndef _ASM_ARM64_ENTRY_COMMON_H #define _ASM_ARM64_ENTRY_COMMON_H +#include #include +#include #include #include #include #include #include +enum ptrace_syscall_dir { + PTRACE_SYSCALL_ENTER = 0, + PTRACE_SYSCALL_EXIT, +}; + #define ARCH_EXIT_TO_USER_MODE_WORK (_TIF_MTE_ASYNC_FAULT | _TIF_FOREIGN_FPSTATE) static __always_inline void arch_exit_to_user_mode_work(struct pt_regs *regs, @@ -54,4 +61,74 @@ static inline bool arch_irqentry_exit_need_resched(void) #define arch_irqentry_exit_need_resched arch_irqentry_exit_need_resched +static __always_inline unsigned long ptrace_save_reg(struct pt_regs *regs, + enum ptrace_syscall_dir dir, + int *regno) +{ + unsigned long saved_reg; + + /* + * We have some ABI weirdness here in the way that we handle syscall + * exit stops because we indicate whether or not the stop has been + * signalled from syscall entry or syscall exit by clobbering a general + * purpose register (ip/r12 for AArch32, x7 for AArch64) in the tracee + * and restoring its old value after the stop. This means that: + * + * - Any writes by the tracer to this register during the stop are + * ignored/discarded. + * + * - The actual value of the register is not available during the stop, + * so the tracer cannot save it and restore it later. + * + * - Syscall stops behave differently to seccomp and pseudo-step traps + * (the latter do not nobble any registers). + */ + *regno = (is_compat_task() ? 12 : 7); + saved_reg = regs->regs[*regno]; + regs->regs[*regno] = dir; + + return saved_reg; +} + +static __always_inline bool arch_ptrace_report_syscall_permit_entry(struct pt_regs *regs) +{ + unsigned long saved_reg; + bool permit; + int regno; + + saved_reg = ptrace_save_reg(regs, PTRACE_SYSCALL_ENTER, ®no); + permit = ptrace_report_syscall_permit_entry(regs); + if (!permit) + forget_syscall(regs); + regs->regs[regno] = saved_reg; + + return permit; +} + +#define arch_ptrace_report_syscall_permit_entry arch_ptrace_report_syscall_permit_entry + +static __always_inline void arch_ptrace_report_syscall_exit(struct pt_regs *regs, + int step) +{ + unsigned long saved_reg; + int regno; + + saved_reg = ptrace_save_reg(regs, PTRACE_SYSCALL_EXIT, ®no); + if (!step) { + ptrace_report_syscall_exit(regs, 0); + regs->regs[regno] = saved_reg; + } else { + regs->regs[regno] = saved_reg; + + /* + * Signal a pseudo-step exception since we are stepping but + * tracer modifications to the registers may have rewound the + * state machine. + */ + ptrace_report_syscall_exit(regs, 1); + } +} + +#define arch_ptrace_report_syscall_exit arch_ptrace_report_syscall_exit + #endif /* _ASM_ARM64_ENTRY_COMMON_H */ diff --git a/arch/arm64/include/asm/syscall.h b/arch/arm64/include/asm/syscall.h index f19d4a8824ba..e3e716575846 100644 --- a/arch/arm64/include/asm/syscall.h +++ b/arch/arm64/include/asm/syscall.h @@ -120,18 +120,4 @@ static inline int syscall_get_arch(struct task_struct *task) return AUDIT_ARCH_AARCH64; } - -bool arm64_syscall_trace_enter(struct pt_regs *regs, unsigned long flags); -void arm64_syscall_exit_work(struct pt_regs *regs, unsigned long flags); - -static __always_inline void arm64_syscall_exit_to_user_mode_work(struct pt_regs *regs) -{ - unsigned long flags = read_thread_flags(); - - rseq_syscall(regs); - - if (unlikely(flags & _TIF_SYSCALL_EXIT_WORK) || flags & _TIF_SINGLESTEP) - arm64_syscall_exit_work(regs, flags); -} - #endif /* __ASM_SYSCALL_H */ diff --git a/arch/arm64/include/asm/thread_info.h b/arch/arm64/include/asm/thread_info.h index 56a2c9426a32..3f621ba0f961 100644 --- a/arch/arm64/include/asm/thread_info.h +++ b/arch/arm64/include/asm/thread_info.h @@ -46,6 +46,7 @@ struct thread_info { u64 mpam_partid_pmg; #endif u32 cpu; + unsigned long syscall_work; /* SYSCALL_WORK_ flags */ }; #define thread_saved_pc(tsk) \ @@ -68,11 +69,6 @@ void arch_setup_new_exec(void); #define TIF_UPROBE 5 /* uprobe breakpoint or singlestep */ #define TIF_MTE_ASYNC_FAULT 6 /* MTE Asynchronous Tag Check Fault */ #define TIF_NOTIFY_SIGNAL 7 /* signal notifications exist */ -#define TIF_SYSCALL_TRACE 8 /* syscall trace active */ -#define TIF_SYSCALL_AUDIT 9 /* syscall auditing */ -#define TIF_SYSCALL_TRACEPOINT 10 /* syscall tracepoint for ftrace */ -#define TIF_SECCOMP 11 /* syscall secure computing */ -#define TIF_SYSCALL_EMU 12 /* syscall emulation active */ #define TIF_PATCH_PENDING 13 /* pending live patching update */ #define TIF_MEMDIE 18 /* is terminating due to OOM killer */ #define TIF_FREEZE 19 @@ -94,27 +90,14 @@ void arch_setup_new_exec(void); #define _TIF_NEED_RESCHED_LAZY (1 << TIF_NEED_RESCHED_LAZY) #define _TIF_NOTIFY_RESUME (1 << TIF_NOTIFY_RESUME) #define _TIF_FOREIGN_FPSTATE (1 << TIF_FOREIGN_FPSTATE) -#define _TIF_SYSCALL_TRACE (1 << TIF_SYSCALL_TRACE) -#define _TIF_SYSCALL_AUDIT (1 << TIF_SYSCALL_AUDIT) -#define _TIF_SYSCALL_TRACEPOINT (1 << TIF_SYSCALL_TRACEPOINT) -#define _TIF_SECCOMP (1 << TIF_SECCOMP) -#define _TIF_SYSCALL_EMU (1 << TIF_SYSCALL_EMU) #define _TIF_PATCH_PENDING (1 << TIF_PATCH_PENDING) #define _TIF_UPROBE (1 << TIF_UPROBE) -#define _TIF_SINGLESTEP (1 << TIF_SINGLESTEP) #define _TIF_32BIT (1 << TIF_32BIT) #define _TIF_SVE (1 << TIF_SVE) #define _TIF_MTE_ASYNC_FAULT (1 << TIF_MTE_ASYNC_FAULT) #define _TIF_NOTIFY_SIGNAL (1 << TIF_NOTIFY_SIGNAL) #define _TIF_TSC_SIGSEGV (1 << TIF_TSC_SIGSEGV) -#define _TIF_SYSCALL_WORK (_TIF_SYSCALL_TRACE | _TIF_SYSCALL_AUDIT | \ - _TIF_SYSCALL_TRACEPOINT | _TIF_SECCOMP | \ - _TIF_SYSCALL_EMU) - -#define _TIF_SYSCALL_EXIT_WORK (_TIF_SYSCALL_TRACE | _TIF_SYSCALL_AUDIT | \ - _TIF_SYSCALL_TRACEPOINT) - #ifdef CONFIG_SHADOW_CALL_STACK #define INIT_SCS \ .scs_base = init_shadow_call_stack, \ diff --git a/arch/arm64/kernel/debug-monitors.c b/arch/arm64/kernel/debug-monitors.c index 29307642f4c9..c53683839155 100644 --- a/arch/arm64/kernel/debug-monitors.c +++ b/arch/arm64/kernel/debug-monitors.c @@ -385,11 +385,19 @@ void user_enable_single_step(struct task_struct *task) if (!test_and_set_ti_thread_flag(ti, TIF_SINGLESTEP)) set_regs_spsr_ss(task_pt_regs(task)); + + /* + * Ensure that the generic entry code triggers a trap once stepping + * out of a system call prior to executing any user instruction, + * as the generic entry code does not natively check for TIF_SINGLESTEP. + */ + set_task_syscall_work(task, SYSCALL_EXIT_TRAP); } NOKPROBE_SYMBOL(user_enable_single_step); void user_disable_single_step(struct task_struct *task) { clear_ti_thread_flag(task_thread_info(task), TIF_SINGLESTEP); + clear_task_syscall_work(task, SYSCALL_EXIT_TRAP); } NOKPROBE_SYMBOL(user_disable_single_step); diff --git a/arch/arm64/kernel/entry-common.c b/arch/arm64/kernel/entry-common.c index ceb4eb11232a..9eac76804bd9 100644 --- a/arch/arm64/kernel/entry-common.c +++ b/arch/arm64/kernel/entry-common.c @@ -6,7 +6,7 @@ */ #include -#include +#include #include #include #include diff --git a/arch/arm64/kernel/ptrace.c b/arch/arm64/kernel/ptrace.c index b2b28dcb2826..8bdee1805c93 100644 --- a/arch/arm64/kernel/ptrace.c +++ b/arch/arm64/kernel/ptrace.c @@ -8,7 +8,6 @@ * Copyright (C) 2012 ARM Ltd. */ -#include #include #include #include @@ -18,7 +17,6 @@ #include #include #include -#include #include #include #include @@ -37,13 +35,9 @@ #include #include #include -#include #include #include -#define CREATE_TRACE_POINTS -#include - struct pt_regs_offset { const char *name; int offset; @@ -2388,136 +2382,6 @@ long arch_ptrace(struct task_struct *child, long request, return ptrace_request(child, request, addr, data); } -enum ptrace_syscall_dir { - PTRACE_SYSCALL_ENTER = 0, - PTRACE_SYSCALL_EXIT, -}; - -static __always_inline unsigned long ptrace_save_reg(struct pt_regs *regs, - enum ptrace_syscall_dir dir, - int *regno) -{ - unsigned long saved_reg; - - /* - * We have some ABI weirdness here in the way that we handle syscall - * exit stops because we indicate whether or not the stop has been - * signalled from syscall entry or syscall exit by clobbering a general - * purpose register (ip/r12 for AArch32, x7 for AArch64) in the tracee - * and restoring its old value after the stop. This means that: - * - * - Any writes by the tracer to this register during the stop are - * ignored/discarded. - * - * - The actual value of the register is not available during the stop, - * so the tracer cannot save it and restore it later. - * - * - Syscall stops behave differently to seccomp and pseudo-step traps - * (the latter do not nobble any registers). - */ - *regno = (is_compat_task() ? 12 : 7); - saved_reg = regs->regs[*regno]; - regs->regs[*regno] = dir; - - return saved_reg; -} - -static int report_syscall_entry(struct pt_regs *regs) -{ - unsigned long saved_reg; - int regno, ret; - - saved_reg = ptrace_save_reg(regs, PTRACE_SYSCALL_ENTER, ®no); - ret = !ptrace_report_syscall_permit_entry(regs); - if (ret) - forget_syscall(regs); - regs->regs[regno] = saved_reg; - - return ret; -} - -static void report_syscall_exit(struct pt_regs *regs) -{ - unsigned long saved_reg; - int regno; - - saved_reg = ptrace_save_reg(regs, PTRACE_SYSCALL_EXIT, ®no); - if (!test_thread_flag(TIF_SINGLESTEP)) { - ptrace_report_syscall_exit(regs, 0); - regs->regs[regno] = saved_reg; - } else { - regs->regs[regno] = saved_reg; - - /* - * Signal a pseudo-step exception since we are stepping but - * tracer modifications to the registers may have rewound the - * state machine. - */ - ptrace_report_syscall_exit(regs, 1); - } -} - -#ifdef CONFIG_AUDITSYSCALL -static inline 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 - -bool arm64_syscall_trace_enter(struct pt_regs *regs, unsigned long flags) -{ - int ret; - - if (flags & (_TIF_SYSCALL_EMU | _TIF_SYSCALL_TRACE)) { - ret = report_syscall_entry(regs); - if (ret || (flags & _TIF_SYSCALL_EMU)) - return false; - - /* ptrace might have changed thread flags */ - flags = read_thread_flags(); - } - - /* Do the secure computing after ptrace; failures should be fast. */ - if (unlikely(flags & _TIF_SECCOMP)) { - if (!__seccomp_permit_syscall()) - return false; - } - - if (test_thread_flag(TIF_SYSCALL_TRACEPOINT)) - trace_sys_enter(regs, syscall_get_nr(current, regs)); - - if (unlikely(audit_context())) - syscall_enter_audit(regs); - - return true; -} - -static inline bool report_single_step(unsigned long flags) -{ - if (flags & _TIF_SYSCALL_EMU) - return false; - - return flags & _TIF_SINGLESTEP; -} - -void arm64_syscall_exit_work(struct pt_regs *regs, unsigned long flags) -{ - bool step; - - audit_syscall_exit(regs); - - if (flags & _TIF_SYSCALL_TRACEPOINT) - trace_sys_exit(regs, syscall_get_return_value(current, regs)); - - step = report_single_step(flags); - if (step || flags & _TIF_SYSCALL_TRACE) - report_syscall_exit(regs); -} - /* * SPSR_ELx bits which are always architecturally RES0 per ARM DDI 0487D.a. * We permit userspace to set SSBS (AArch64 bit 12, AArch32 bit 23) which is diff --git a/arch/arm64/kernel/signal.c b/arch/arm64/kernel/signal.c index 38e6fa204c17..093eebb9d764 100644 --- a/arch/arm64/kernel/signal.c +++ b/arch/arm64/kernel/signal.c @@ -8,8 +8,8 @@ #include #include +#include #include -#include #include #include #include diff --git a/arch/arm64/kernel/syscall.c b/arch/arm64/kernel/syscall.c index 72c6e8b7ab21..4a13108defb0 100644 --- a/arch/arm64/kernel/syscall.c +++ b/arch/arm64/kernel/syscall.c @@ -2,6 +2,7 @@ #include #include +#include #include #include #include @@ -58,6 +59,7 @@ static void el0_svc_common(struct pt_regs *regs, int scno, int sc_nr, const syscall_fn_t syscall_table[]) { unsigned long flags = read_thread_flags(); + unsigned long work; regs->orig_x0 = regs->regs[0]; regs->syscallno = scno; @@ -90,7 +92,8 @@ static void el0_svc_common(struct pt_regs *regs, int scno, int sc_nr, return; } - if (unlikely(flags & _TIF_SYSCALL_WORK)) { + work = READ_ONCE(current_thread_info()->syscall_work); + if (unlikely(work & SYSCALL_WORK_ENTER)) { /* * The de-facto standard way to skip a system call using ptrace * is to set the system call to -1 (NO_SYSCALL) and set x0 to a @@ -108,7 +111,7 @@ static void el0_svc_common(struct pt_regs *regs, int scno, int sc_nr, */ if (scno == NO_SYSCALL) syscall_set_return_value(current, regs, -ENOSYS, 0); - if (!arm64_syscall_trace_enter(regs, read_thread_flags())) + if (!syscall_trace_enter(regs, work, scno)) goto trace_exit; /* Reread the syscall number as it might have been modified */ @@ -119,7 +122,7 @@ static void el0_svc_common(struct pt_regs *regs, int scno, int sc_nr, invoke_syscall(regs, scno, sc_nr, syscall_table); trace_exit: - arm64_syscall_exit_to_user_mode_work(regs); + syscall_exit_to_user_mode_work(regs); } void do_el0_svc(struct pt_regs *regs) -- 2.34.1