On Wed, Oct 14, 2026 at 01:06:51PM +0200, Michal Suchanek wrote:
> From: Bernhard M. Wiedemann <[email protected]>
> 
> 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: [email protected] # 7.2.x: c7585b8e99ad: powerpc: Don't drop 
> _TIF_RESTOREALL on syscall restart
> Cc: [email protected] # 7.2.x
> Assisted-by: LLM
> Signed-off-by: Bernhard M. Wiedemann <[email protected]>
> [ms: reword commit message]
> Signed-off-by: Michal Suchánek <[email protected]>
> ---
> 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.msucha...@suse.de/
> 
> 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) <[email protected]>

Reply via email to