[PATCH v21 01/15] arm64: ptrace: Fix spurious pseudo-single-step for PTRACE_SYSEMU_SINGLESTEP
From: Jinjie Ruan
Date: Wed Nov 11 2026 - 06:43:21 EST
PTRACE_SYSEMU_SINGLESTEP sets both _TIF_SYSCALL_EMU and _TIF_SINGLESTEP.
Since commit ac2081cdc4d99 ("arm64: ptrace: Consistently use
pseudo-singlestep exceptions"), syscall_trace_exit() injects a
pseudo-single-step SIGTRAP (si_code SI_USER) whenever _TIF_SINGLESTEP is
set, including for an emulated system call.
That pseudo-step was added for PTRACE_SINGLESTEP: the system call runs,
and the tracer must get a stop at the return address to inspect the
result. An emulated system call does not run, and its syscall-enter-stop
already reports the SVC, as the x86 implementation and generic entry
(report_single_step()) both say. The extra stop is wrong:
1) Stepping armed before the SVC (the normal case). The SVC exception
leaves the step pending (SPSR_EL1.SS is 0, Arm ARM D2.11.5.1), so the
hardware step is taken at the return address anyway, and the tracer
now stops there twice without the tracee executing anything:
syscall-enter-stop pc=L
pseudo-step pc=L si_code=SI_USER <- added by that commit
hardware step pc=L si_code=TRAP_TRACE
hardware step pc=L+4
2) Stepping armed after the SVC, e.g. a tracer resuming a
PTRACE_EVENT_SECCOMP stop with PTRACE_SYSEMU_SINGLESTEP. The first
stop is the pseudo-step at L, so the instruction at L is not
executed before the step is reported. Before that commit, and on
x86, the first stop follows the instruction at L.
Skip the pseudo-step when _TIF_SYSCALL_EMU is set, as generic entry
does. The behaviour of PTRACE_SYSEMU_SINGLESTEP goes back to what it was
before that commit. PTRACE_SINGLESTEP is unaffected and still gets the
pseudo-step, so both problems fixed by that commit stay fixed.
Only tracers using PTRACE_SYSEMU_SINGLESTEP see the difference. Measured
in QEMU, L is the instruction after the SVC:
case before that commit v7.4-rc1 this patch
normal L, L+4 L, L, L+4 L, L+4
after seccomp L+4 L L+4
Fixes: ac2081cdc4d99 ("arm64: ptrace: Consistently use pseudo-singlestep exceptions")
Cc: Mark Rutland <mark.rutland@xxxxxxx>
Cc: Will Deacon <will@xxxxxxxxxx>
Cc: Catalin Marinas <catalin.marinas@xxxxxxx>
Cc: Oleg Nesterov <oleg@xxxxxxxxxx>
Signed-off-by: Jinjie Ruan <ruanjinjie@xxxxxxxxxx>
---
arch/arm64/kernel/ptrace.c | 12 +++++++++++-
1 file changed, 11 insertions(+), 1 deletion(-)
diff --git a/arch/arm64/kernel/ptrace.c b/arch/arm64/kernel/ptrace.c
index 7405c76724bf..4baf04f20e67 100644
--- a/arch/arm64/kernel/ptrace.c
+++ b/arch/arm64/kernel/ptrace.c
@@ -2482,16 +2482,26 @@ int syscall_trace_enter(struct pt_regs *regs)
return regs->syscallno;
}
+static inline bool report_single_step(unsigned long flags)
+{
+ if (flags & _TIF_SYSCALL_EMU)
+ return false;
+
+ return flags & _TIF_SINGLESTEP;
+}
+
void syscall_trace_exit(struct pt_regs *regs)
{
unsigned long flags = read_thread_flags();
+ bool step;
audit_syscall_exit(regs);
if (flags & _TIF_SYSCALL_TRACEPOINT)
trace_sys_exit(regs, syscall_get_return_value(current, regs));
- if (flags & (_TIF_SYSCALL_TRACE | _TIF_SINGLESTEP))
+ step = report_single_step(flags);
+ if (step || flags & _TIF_SYSCALL_TRACE)
report_syscall_exit(regs);
rseq_syscall(regs);
--
2.34.1