From: Kees Cook <kees@kernel.org>
To: Jinjie Ruan <ruanjinjie@huawei.com>
Cc: Will Deacon <will@kernel.org>,
linux-arm-kernel@lists.infradead.org,
linux-kernel@vger.kernel.org, Mark Rutland <mark.rutland@arm.com>,
Yiqi Sun <sunyiqixm@gmail.com>,
Catalin Marinas <catalin.marinas@arm.com>
Subject: Re: [PATCH] arm64: syscall: Ensure saved x0 is kept in-sync with tracer updates
Date: Wed, 15 Jul 2026 20:05:34 -0700 [thread overview]
Message-ID: <202607152004.DEA95D63@keescook> (raw)
In-Reply-To: <ac39d21e-8800-4c4e-885e-4baf7af2a106@huawei.com>
On Thu, Jul 16, 2026 at 10:57:34AM +0800, Jinjie Ruan wrote:
>
>
> On 7/14/2026 10:35 PM, Will Deacon wrote:
> > When seccomp support was originally added to arm64 in a1ae65b21941
> > ("arm64: add seccomp support"), seccomp was erroneously called _before_
> > the ptrace syscall-enter-stop and therefore the tracer could trivially
> > manipulate the syscall register state after the seccomp check had
> > passed. This was subsequently fixed in a5cd110cb836 ("arm64/ptrace: run
> > seccomp after ptrace") by moving the seccomp check after the tracer has
> > run. Unfortunately, a decade later, that fix has been reported to be
> > incomplete.
> >
> > On arm64, both the first argument to a syscall and its eventual return
> > value are allocated to register x0. In order to facilitate syscall
> > restarting and querying of syscall arguments on the syscall exit path,
> > the original value of x0 is stashed in 'struct pt_regs::orig_x0' early
> > during the syscall entry path and is returned for the first argument by
> > syscall_get_arguments(). Unlike 32-bit Arm, this stashed value is not
> > directly exposed via ptrace() and so changes to register x0 made by the
> > tracer on a syscall-enter-stop are not reflected in 'orig_x0'. This
> > means that seccomp and audit can observe a stale value for the register
> > compared to the argument that will be observed by the actual syscall.
> >
> > Re-sync 'orig_x0' from x0 on the syscall entry path following a
> > potential ptrace stop (i.e. PTRACE_EVENTMSG_SYSCALL_ENTRY or
> > SECCOMP_RET_TRACE). This behaviour is limited to native tasks (because
> > compat tasks expose 'orig_r0' to ptrace) where the syscall is not being
> > skipped (because x0 is updated to hold the return value of -ENOSYS in
> > that case).
> >
> > Cc: Kees Cook <kees@kernel.org>
> > Cc: Jinjie Ruan <ruanjinjie@huawei.com>
> > Cc: Mark Rutland <mark.rutland@arm.com>
> > Reported-by: Yiqi Sun <sunyiqixm@gmail.com>
> > Link: https://lore.kernel.org/all/20260529065444.1336608-1-sunyiqixm@gmail.com/
> > Suggested-by: Catalin Marinas <catalin.marinas@arm.com>
> > Fixes: a5cd110cb836 ("arm64/ptrace: run seccomp after ptrace")
> > Signed-off-by: Will Deacon <will@kernel.org>
> > ---
> > arch/arm64/kernel/ptrace.c | 24 ++++++++++++++++++++++++
> > 1 file changed, 24 insertions(+)
> >
> > diff --git a/arch/arm64/kernel/ptrace.c b/arch/arm64/kernel/ptrace.c
> > index 4d08598e2891..57e8c6714d44 100644
> > --- a/arch/arm64/kernel/ptrace.c
> > +++ b/arch/arm64/kernel/ptrace.c
> > @@ -2408,6 +2408,21 @@ static void report_syscall_exit(struct pt_regs *regs)
> > }
> > }
> >
> > +static void update_syscall_orig_x0_after_ptrace(struct pt_regs *regs)
> > +{
> > + /*
> > + * 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. Skip this 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().
> > + * For compat tasks, orig_r0 is provided directly through GPR index 17.
> > + */
> > + if (!is_compat_task() && regs->syscallno != NO_SYSCALL)
> > + regs->orig_x0 = regs->regs[0];
> > +}
> > +
> > int syscall_trace_enter(struct pt_regs *regs)
> > {
> > unsigned long flags = read_thread_flags();
> > @@ -2417,12 +2432,21 @@ int syscall_trace_enter(struct pt_regs *regs)
> > ret = report_syscall_entry(regs);
> > if (ret || (flags & _TIF_SYSCALL_EMU))
> > return NO_SYSCALL;
> > +
> > + /*
> > + * Ensure ptrace changes to x0 are visible to seccomp
> > + * ptrace exits (SECCOMP_RET_TRACE).
> > + */
> > + update_syscall_orig_x0_after_ptrace(regs);
> > }
> >
> > /* Do the secure computing after ptrace; failures should be fast. */
> > if (secure_computing() == -1)
> > return NO_SYSCALL;
> >
> > + /* Ensure seccomp updates to x0 are visible to audit. */
> > + update_syscall_orig_x0_after_ptrace(regs);
>
>
> Hi, will
>
> I think unconditionally updating orig_x0 here is unnecessary, we could
> Expand seccomp check in place as below the same as generic entry.
>
> In this way, in most cases where seccomp is not used, the overhead of
> updating orig_x0 is eliminated. Moreover, we only need to define an
> architecture-specific version of the seccomp function, thus avoiding the
> pain of switching from arm64 to the generic entry.
>
> So this patch can be like below.
>
> int syscall_trace_enter(struct pt_regs *regs)
> {
> unsigned long flags = read_thread_flags();
> @@ -2420,12 +2435,24 @@ int syscall_trace_enter(struct pt_regs *regs)
>
> /* ptrace might have changed work flags */
> flags = read_thread_flags();
> + /*
> + * Ensure ptrace changes to x0 during a regular
> syscall-enter-stop
> + * (PTRACE_SYSCALL) are visible to subsequent seccomp trace
> + * and audit checking.
> + */
> + update_syscall_orig_x0_after_ptrace(regs);
> }
>
> /* Do the secure computing after ptrace; failures should be fast. */
> if (unlikely(flags & _TIF_SECCOMP)) {
> if (!__seccomp_permit_syscall())
> return NO_SYSCALL;
> +
> + /*
> + * Ensure tracer changes to x0 during seccomp ptrace
> exit processing
> + * (SECCOMP_RET_TRACE) are visible to audit.
> + */
> + update_syscall_orig_x0_after_ptrace(regs);
> }
>
>
> Author: Jinjie Ruan <ruanjinjie@huawei.com>
> Date: Tue Oct 29 19:08:03 2024 +0800
>
> arm64: ptrace: Expand seccomp check in place
>
> Refactor syscall_trace_enter() by open-coding the seccomp check
> to align with the generic entry framework. While the original call to
> seccomp_permit_syscall() internally re-reads the thread flags and is
> therefore safe against flag changes during ptrace stops, the new
> open-coded version must explicitly re-read the flags after ptrace
> handling to preserve that safety.
>
> [Background]
> The generic entry implementation expands the seccomp check in-place
> instead of using the seccomp_permit_syscall() wrapper. It directly
> tests SYSCALL_WORK_SECCOMP and calls the underlying
> __seccomp_permit_syscall() function to handle syscall filtering.
>
> [Changes]
> 1. After ptrace handling, re-read thread flags:
> This ensures that any _TIF_SECCOMP set during the ptrace stop is
> observed before the seccomp check.
>
> 2. Open-code seccomp check:
> - Instead of calling the seccomp_permit_syscall() wrapper, explicitly
> check the updated 'flags' parameter for _TIF_SECCOMP.
> - Call __seccomp_permit_syscall() directly if the flag is set.
>
> [Why this matters]
> - Aligns the arm64 syscall path with the generic entry implementation,
> simplifying future migration to the generic entry framework.
>
> - No functional changes are intended; seccomp behavior remains
> identical.
> The explicit re-read ensures the open-coded version retains the same
> safety as the original wrapper, preventing the race condition
> described
> in the generic entry fix.
>
> - Performance: Non-ptrace fast path avoids atomic test_bit overhead via
> cached flags.
>
> Cc: Mark Rutland <mark.rutland@arm.com>
> Cc: Will Deacon <will@kernel.org>
> Cc: Catalin Marinas <catalin.marinas@arm.com>
> Link:
> https://lore.kernel.org/all/20260713025712.416366-1-ruanjinjie@huawei.com/
> Reviewed-by: Ada Couprie Diaz <ada.coupriediaz@arm.com>
> Reviewed-by: Linus Walleij <linusw@kernel.org>
> Reviewed-by: Yeoreum Yun <yeoreum.yun@arm.com>
> Reviewed-by: Kevin Brodsky <kevin.brodsky@arm.com>
> Signed-off-by: Jinjie Ruan <ruanjinjie@huawei.com>
>
> diff --git a/arch/arm64/kernel/ptrace.c b/arch/arm64/kernel/ptrace.c
> index 5709e9d3c321..941752656ea6 100644
> --- a/arch/arm64/kernel/ptrace.c
> +++ b/arch/arm64/kernel/ptrace.c
> @@ -2417,11 +2417,16 @@ int syscall_trace_enter(struct pt_regs *regs)
> ret = report_syscall_entry(regs);
> if (ret || (flags & _TIF_SYSCALL_EMU))
> return NO_SYSCALL;
> +
> + /* ptrace might have changed the flags */
> + flags = read_thread_flags();
> }
>
> /* Do the secure computing after ptrace; failures should be fast. */
> - if (!seccomp_permit_syscall())
> - return NO_SYSCALL;
> + if (unlikely(flags & _TIF_SECCOMP)) {
> + if (!__seccomp_permit_syscall())
> + return NO_SYSCALL;
> + }
>
> if (test_thread_flag(TIF_SYSCALL_TRACEPOINT))
> trace_sys_enter(regs, regs->syscallno);
Do we have a corresponding seccomp_bpf.c selftest we can add for this? I
would really like to have a regression test that would catch this
issue...
-Kees
--
Kees Cook
next prev parent reply other threads:[~2026-07-16 3:05 UTC|newest]
Thread overview: 7+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-07-14 14:35 [PATCH] arm64: syscall: Ensure saved x0 is kept in-sync with tracer updates Will Deacon
2026-07-15 11:39 ` Jinjie Ruan
2026-07-15 13:16 ` Will Deacon
2026-07-16 2:09 ` Jinjie Ruan
2026-07-16 2:57 ` Jinjie Ruan
2026-07-16 3:05 ` Kees Cook [this message]
2026-07-16 3:25 ` Jinjie Ruan
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=202607152004.DEA95D63@keescook \
--to=kees@kernel.org \
--cc=catalin.marinas@arm.com \
--cc=linux-arm-kernel@lists.infradead.org \
--cc=linux-kernel@vger.kernel.org \
--cc=mark.rutland@arm.com \
--cc=ruanjinjie@huawei.com \
--cc=sunyiqixm@gmail.com \
--cc=will@kernel.org \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox