Re: [PATCH v2 1/2] powerpc: Restore all registers after a traced syscall again
flat view
From: Mukesh Kumar Chaurasiya <hidden>
Date: 2026-10-15 06:33:16
Also in:
lkml, stable
On Wed, Oct 14, 2026 at 01:06:51PM +0200, Michal Suchanek wrote:
quoted hunk ↗ jump to hunk
From: Bernhard M. Wiedemann <redacted> syscall_exit_prepare() asks the exit assembly to reload all GPRs, CTR and XER from pt_regs only if the thread flags contain any of the flags in _TIF_SYSCALL_DOTRACE. With the new entry code based GENERIC_ENTRY the flags are no longer set. This breaks tracers that modify a stopped syscall, for example ones that re-execute the syscall instruction by moving the NIP back by 4 at the syscall-exit stop with the syscall number put back into r0: the sc then runs with r0 = 0, which is __NR_restart_syscall. dettrace fails like this with "No filter rule for system call: restart_syscall". A tracer that writes r4 at the syscall-exit stop of an sc sees the tracee continue with r4 = 0 instead; with a 7.1 kernel both work. Check the equivalent syscall_work bits instead, and drop the now unused _TIF_SYSCALL_DOTRACE. Fixes: bee25f97ad24 ("powerpc: Enable GENERIC_ENTRY feature") Cc: stable@vger.kernel.org # 7.2.x: c7585b8e99ad: powerpc: Don't drop _TIF_RESTOREALL on syscall restart Cc: stable@vger.kernel.org # 7.2.x Assisted-by: LLM Signed-off-by: Bernhard M. Wiedemann <redacted> [ms: reword commit message] Signed-off-by: Michal Suchánek <redacted> --- A backport also needs commit c7585b8e99ad ("powerpc: Don't drop _TIF_RESTOREALL on syscall restart"), without which the flag can still be lost. v2: rename PPC_SYSCALL_WORK_RESTOREALL to SYSCALL_WORK_RESTOREALL and move it from interrupt.c to thread_info.h v1: https://lore.kernel.org/linuxppc-dev/4c1da7f31a6603455ec452960af529e4d77f5334.1791826411.git.msuchanek@suse.de/ (local) Testing: a small tracer (pokes r4 at the syscall-exit stop of an sc, and replays a syscall by moving NIP back with a new r0) shows both writes lost on a ppc64le and both working on 7.1.8. ------------[ cut here ]------------ // SPDX-License-Identifier: GPL-2.0 /* * Does a register a tracer writes at the syscall-exit stop of an "sc" * reach the tracee? Checks r4, and a replay: NIP moved back onto the sc * with r0 set to another syscall number. * Build: gcc -O2 -o ppc-exitregs ppc-exitregs.c (powerpc64 only) */ /* word offsets in struct pt_regs, arch/powerpc/include/uapi/asm/ptrace.h */ static long do_sc(long nr, long *r4io) { register long r0 __asm__("r0") = nr; register long r3 __asm__("r3") = 0; register long r4 __asm__("r4") = *r4io; __asm__ volatile("sc" : "+r"(r0), "+r"(r3), "+r"(r4) : : "r5", "r6", "r7", "r8", "r9", "r10", "r11", "r12", "cr0", "ctr", "xer", "memory"); *r4io = r4; return r3; } static long peek(pid_t pid, int w) { errno = 0; long v = ptrace(PTRACE_PEEKUSER, pid, (void *)(w * sizeof(long)), 0); if (errno) { perror("PEEKUSER"); exit(2); } return v; } static void poke(pid_t pid, int w, long v) { if (ptrace(PTRACE_POKEUSER, pid, (void *)(w * sizeof(long)), (void *)v)) { perror("POKEUSER"); exit(2); } } /* Resume to the next syscall stop, which must be one. */ static void next_stop(pid_t pid) { int st; ptrace(PTRACE_SYSCALL, pid, 0, 0); if (waitpid(pid, &st, 0) != pid || !WIFSTOPPED(st) || WSTOPSIG(st) != (SIGTRAP | 0x80)) { fprintf(stderr, "unexpected wait status %#x\n", st); exit(2); } } int main(void) { pid_t pid = fork(); int st, fail = 0; if (pid == 0) { long r4 = 0x1111, r4a, ret; ptrace(PTRACE_TRACEME, 0, 0, 0); raise(SIGSTOP); do_sc(__NR_getpid, &r4); r4a = r4; ret = do_sc(__NR_getpid, &r4); /* bit 0: the tracer's r4 was lost; bit 1: the replay of * the second getpid as getppid returned something else */ _exit((r4a != 0x2222) | (ret != getppid()) << 1); } waitpid(pid, &st, 0); ptrace(PTRACE_SETOPTIONS, pid, 0, (void *)PTRACE_O_TRACESYSGOOD); /* 1: write r4 at the exit stop of getpid */ next_stop(pid); /* entry */ next_stop(pid); /* exit */ poke(pid, W_R4, 0x2222); next_stop(pid); /* entry of the second getpid */ /* 2: replay the second getpid as getppid */ next_stop(pid); /* exit */ poke(pid, W_NIP, peek(pid, W_NIP) - 4); poke(pid, W_R0, __NR_getppid); next_stop(pid); /* entry of the replay */ long nr = peek(pid, W_R0); printf("replayed syscall arrives as nr %ld (%s)\n", nr, nr == __NR_getppid ? "getppid, OK" : nr == __NR_restart_syscall ? "restart_syscall, BUG" : "?"); if (nr != __NR_getppid) { fail = 1; poke(pid, W_R0, __NR_getppid); /* entry writes do work */ } next_stop(pid); /* exit */ ptrace(PTRACE_CONT, pid, 0, 0); waitpid(pid, &st, 0); if (!WIFEXITED(st)) { printf("child did not exit normally: %#x\n", st); return 2; } printf("r4 written at the exit stop: %s\n", WEXITSTATUS(st) & 1 ? "lost, BUG" : "kept, OK"); printf("replay result: %s\n", WEXITSTATUS(st) & 2 ? "wrong" : "getppid(), OK"); return fail || WEXITSTATUS(st); } --- arch/powerpc/include/asm/thread_info.h | 15 ++++++++++++--- arch/powerpc/kernel/interrupt.c | 3 +-- 2 files changed, 13 insertions(+), 5 deletions(-)diff --git a/arch/powerpc/include/asm/thread_info.h b/arch/powerpc/include/asm/thread_info.h index 1e069a2e7ce87..31a7a157bc902 100644 --- a/arch/powerpc/include/asm/thread_info.h +++ b/arch/powerpc/include/asm/thread_info.h@@ -142,9 +142,6 @@ void arch_setup_new_exec(void); #define _TIF_SYSCALL_TRACEPOINT (1<<TIF_SYSCALL_TRACEPOINT) #define _TIF_EMULATE_STACK_STORE (1<<TIF_EMULATE_STACK_STORE) #define _TIF_SYSCALL_EMU (1<<TIF_SYSCALL_EMU) -#define _TIF_SYSCALL_DOTRACE (_TIF_SYSCALL_TRACE | _TIF_SYSCALL_AUDIT | \ - _TIF_SECCOMP | _TIF_SYSCALL_TRACEPOINT | \ - _TIF_SYSCALL_EMU) #define _TIF_USER_WORK_MASK (_TIF_SIGPENDING | _TIF_NEED_RESCHED | \ _TIF_NEED_RESCHED_LAZY | _TIF_NOTIFY_RESUME | \@@ -153,6 +150,18 @@ void arch_setup_new_exec(void); #define _TIF_PERSYSCALL_MASK (_TIF_RESTOREALL|_TIF_NOERROR) +/* + * Syscall work after which the exit must reload all registers from + * pt_regs: a tracer may have changed any of them at a syscall or seccomp + * stop, or under PTRACE_SYSEMU. Audit and the tracepoint are included to + * keep the pre-GENERIC_ENTRY behaviour. + */ +#define SYSCALL_WORK_RESTOREALL (SYSCALL_WORK_SECCOMP | \ + SYSCALL_WORK_SYSCALL_TRACEPOINT | \ + SYSCALL_WORK_SYSCALL_TRACE | \ + SYSCALL_WORK_SYSCALL_EMU | \ + SYSCALL_WORK_SYSCALL_AUDIT) + /* Bits in local_flags */ /* Don't move TLF_NAPPING without adjusting the code in entry_32.S */ #define TLF_NAPPING 0 /* idle thread enabled NAP mode */diff --git a/arch/powerpc/kernel/interrupt.c b/arch/powerpc/kernel/interrupt.c index 55f9c0c9922ac..d6a7b6d2b4bd2 100644 --- a/arch/powerpc/kernel/interrupt.c +++ b/arch/powerpc/kernel/interrupt.c@@ -117,9 +117,8 @@ notrace unsigned long syscall_exit_prepare(unsigned long r3, regs->gpr[3] = r3; } - if (unlikely(ti_flags & _TIF_SYSCALL_DOTRACE)) { + if (unlikely(READ_ONCE(current_thread_info()->syscall_work) & SYSCALL_WORK_RESTOREALL)) ret |= _TIF_RESTOREALL; - } syscall_exit_to_user_mode(regs);-- 2.51.0
Reviewed-by: Mukesh Kumar Chaurasiya (IBM) <redacted>