From c3f83d021162571bcd87b062ec9e587828d2b7f3 Mon Sep 17 00:00:00 2001 From: Karl Mehltretter Date: Wed, 29 Jul 2026 02:42:54 +0200 Subject: [PATCH 1/3] arm64/fpsimd: ptrace: Fix inactive SVE and SSVE regsets sve_init_header_from_task() takes header as a pointer, so for the inactive mode header->size = sizeof(header); stores 8 rather than sizeof(struct user_sve_header), which is 16. Userspace sees an impossible size smaller than the header it describes. The inactive-mode check in sve_get_common() compares header.size against sizeof(header) as well, but there header is a struct, so the check can never fire. Reads of NT_ARM_SVE and NT_ARM_SSVE for the inactive mode therefore still return the other mode's FPSIMD data, exactly the situation the check was added to prevent. Fix the size, and make the check return the remaining membuf space instead of 0, which regset_get() would interpret as the entire (zero-filled) buffer having been populated. Fixes: b93e685ecff7 ("arm64/fpsimd: ptrace: Do not present register data for inactive mode") Assisted-by: Claude:claude-opus-5 Signed-off-by: Karl Mehltretter Signed-off-by: Will Deacon --- arch/arm64/kernel/ptrace.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/arch/arm64/kernel/ptrace.c b/arch/arm64/kernel/ptrace.c index 4d08598e2891..2a72c61a8af9 100644 --- a/arch/arm64/kernel/ptrace.c +++ b/arch/arm64/kernel/ptrace.c @@ -801,7 +801,7 @@ static void sve_init_header_from_task(struct user_sve_header *header, if (active) header->size = SVE_PT_SIZE(vq, header->flags); else - header->size = sizeof(header); + header->size = sizeof(*header); header->max_size = SVE_PT_SIZE(sve_vq_from_vl(header->max_vl), SVE_PT_REGS_SVE); } @@ -837,7 +837,7 @@ static int sve_get_common(struct task_struct *target, * from the other mode to userspace. */ if (header.size == sizeof(header)) - return 0; + return to.left; switch ((header.flags & SVE_PT_REGS_MASK)) { case SVE_PT_REGS_FPSIMD: From 88b839ce497ccb1ff92f7ae742c78dd2937ba572 Mon Sep 17 00:00:00 2001 From: Will Deacon Date: Thu, 30 Jul 2026 14:26:50 +0100 Subject: [PATCH 2/3] arm64: ptrace: Keep 'orig_x0' in-sync with x0 on syscall entry Commit e057b9477232 ("arm64: syscall: Ensure saved x0 is kept in-sync with tracer updates") attempted to resolve a long-standing issue with syscall entry tracing, where a tracer is able to manipulate the first syscall argument without being subjected to seccomp or audit checking. Unfortunately, that fix was incomplete [1], as it failed to update 'orig_x0' between a tracer updating x0 during a seccomp ptrace exit (SECCOMP_RET_TRACE) and the seccomp filter being re-evaluated. Rather than add hooks to the core seccomp code, instead move the synchronisation code into the ptrace GPR and syscall setting code so that 'orig_x0' is kept up to date with x0 whenever we're stopped on the syscall entry path. Cc: Kees Cook Cc: Jinjie Ruan Cc: Mark Rutland Link: https://sashiko.dev/#/patchset/20260716120640.6590-1-will@kernel.org [1] Reported-by: Yiqi Sun Link: https://lore.kernel.org/all/20260529065444.1336608-1-sunyiqixm@gmail.com/ Fixes: e057b9477232 ("arm64: syscall: Ensure saved x0 is kept in-sync with tracer updates") Fixes: a5cd110cb836 ("arm64/ptrace: run seccomp after ptrace") Tested-by: Jinjie Ruan Signed-off-by: Will Deacon --- arch/arm64/kernel/ptrace.c | 50 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 50 insertions(+) diff --git a/arch/arm64/kernel/ptrace.c b/arch/arm64/kernel/ptrace.c index 2a72c61a8af9..4955166723b8 100644 --- a/arch/arm64/kernel/ptrace.c +++ b/arch/arm64/kernel/ptrace.c @@ -560,6 +560,42 @@ static int gpr_get(struct task_struct *target, return membuf_write(&to, uregs, sizeof(*uregs)); } +static void update_syscall_orig_x0_after_ptrace(struct task_struct *target) +{ + struct pt_regs *regs = task_pt_regs(target); + struct kernel_siginfo *info = target->last_siginfo; + + /* + * Skip the update for NO_SYSCALL (set either by the user or the + * tracer), as regs[0] holds the return value (see the comment in + * el0_svc_common()) and can be unwound using syscall_rollback(). + */ + if (regs->syscallno == NO_SYSCALL) + return; + + /* We should only be called when target is in a ptrace stop */ + if (WARN_ON_ONCE(!info)) + return; + + /* + * For compat tasks, orig_r0 is provided directly through GPR index + * 17. + */ + if (is_compat_thread(task_thread_info(target))) + return; + + /* + * Don't update orig_x0 for a syscall-exit-stop, as x0 now contains the + * return value of the system call. + */ + if ((info->si_code & ~0x80) == SIGTRAP && + target->ptrace_message == PTRACE_EVENTMSG_SYSCALL_EXIT) { + return; + } + + regs->orig_x0 = regs->regs[0]; +} + static int gpr_set(struct task_struct *target, const struct user_regset *regset, unsigned int pos, unsigned int count, const void *kbuf, const void __user *ubuf) @@ -575,6 +611,14 @@ static int gpr_set(struct task_struct *target, const struct user_regset *regset, return -EINVAL; task_pt_regs(target)->user_regs = newregs; + + /* + * Keep orig_x0 authoritative so that seccomp (via + * syscall_get_arguments()), audit and the restart path all see the same + * first argument the syscall is dispatched with, even if it has been + * updated by a tracer. + */ + update_syscall_orig_x0_after_ptrace(target); return 0; } @@ -753,6 +797,12 @@ static int system_call_set(struct task_struct *target, return ret; task_pt_regs(target)->syscallno = syscallno; + + /* + * Re-sync orig_x0 in case the syscall number has been changed + * from NO_SYSCALL. + */ + update_syscall_orig_x0_after_ptrace(target); return ret; } From 5a87e8c7f3702eba8a1ed17431a807e01da3ae51 Mon Sep 17 00:00:00 2001 From: Will Deacon Date: Thu, 30 Jul 2026 14:26:51 +0100 Subject: [PATCH 3/3] arm64: syscall: Pass 'orig_x0' as first argument to native system call syscall_get_arguments() returns 'regs->orig_x0' for the first system call argument so as to avoid aliasing with the syscall return value in 'regs->regs[0]' on the return path, however the actual syscall invocation passes 'regs->regs[0]' as the first parameter. Although the two registers should be kept in sync during syscall entry for native tasks, pass 'regs->orig_x0' as the first syscall parameter for consistency with the syscall argument APIs. Compat tasks continue to use 'regs->regs[0]' for compatibility with the behaviour of the 32-bit kernel. Suggested-by: Jinjie Ruan Signed-off-by: Will Deacon --- arch/arm64/include/asm/syscall_wrapper.h | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/arch/arm64/include/asm/syscall_wrapper.h b/arch/arm64/include/asm/syscall_wrapper.h index abb57bc54305..395152ef5372 100644 --- a/arch/arm64/include/asm/syscall_wrapper.h +++ b/arch/arm64/include/asm/syscall_wrapper.h @@ -10,13 +10,13 @@ #include -#define SC_ARM64_REGS_TO_ARGS(x, ...) \ +#ifdef CONFIG_COMPAT + +#define COMPAT_SC_ARM64_REGS_TO_ARGS(x, ...) \ __MAP(x,__SC_ARGS \ ,,regs->regs[0],,regs->regs[1],,regs->regs[2] \ ,,regs->regs[3],,regs->regs[4],,regs->regs[5]) -#ifdef CONFIG_COMPAT - #define COMPAT_SYSCALL_DEFINEx(x, name, ...) \ asmlinkage long __arm64_compat_sys##name(const struct pt_regs *regs); \ ALLOW_ERROR_INJECTION(__arm64_compat_sys##name, ERRNO); \ @@ -24,7 +24,7 @@ static inline long __do_compat_sys##name(__MAP(x,__SC_DECL,__VA_ARGS__)); \ asmlinkage long __arm64_compat_sys##name(const struct pt_regs *regs) \ { \ - return __se_compat_sys##name(SC_ARM64_REGS_TO_ARGS(x,__VA_ARGS__)); \ + return __se_compat_sys##name(COMPAT_SC_ARM64_REGS_TO_ARGS(x,__VA_ARGS__)); \ } \ static long __se_compat_sys##name(__MAP(x,__SC_LONG,__VA_ARGS__)) \ { \ @@ -46,6 +46,11 @@ #endif /* CONFIG_COMPAT */ +#define SC_ARM64_REGS_TO_ARGS(x, ...) \ + __MAP(x,__SC_ARGS \ + ,,regs->orig_x0,,regs->regs[1],,regs->regs[2] \ + ,,regs->regs[3],,regs->regs[4],,regs->regs[5]) + #define __SYSCALL_DEFINEx(x, name, ...) \ asmlinkage long __arm64_sys##name(const struct pt_regs *regs); \ ALLOW_ERROR_INJECTION(__arm64_sys##name, ERRNO); \