* [PATCH v5 0/2] PTRACE_SET_SYSCALL_INFO: add support for seccomp syscall skipping
@ 2026-07-09 10:09 Renzo Davoli
2026-07-09 10:09 ` [PATCH v5 1/2] ptrace: add PTRACE_SET_SYSCALL_INFO syscall skipping support Renzo Davoli
` (2 more replies)
0 siblings, 3 replies; 8+ messages in thread
From: Renzo Davoli @ 2026-07-09 10:09 UTC (permalink / raw)
To: linux-kernel
Cc: Renzo Davoli, Andrew Morton, Oleg Nesterov, Shuah Khan,
Alexey Gladkov, Eugene Syromyatnikov, Davide Berardi,
strace-devel, Dmitry V . Levin
PTRACE_SET_SYSCALL_INFO is a generic ptrace API that complements
PTRACE_GET_SYSCALL_INFO by allowing a tracer to modify details of a
system call in which the tracee is currently blocked.
The API is designed to let tracers inspect and modify system call
information in a simple, architecture-agnostic manner.
The current implementation only supports modifying the subset of
system call information needed by strace: the system call number,
arguments, and return value.
This patch set extends PTRACE_SET_SYSCALL_INFO with support for
skipping a system call.
When a seccomp filter returns SECCOMP_RET_TRACE, the tracer receives,
via PTRACE_GET_SYSCALL_INFO, a struct ptrace_syscall_info with
op == PTRACE_SYSCALL_INFO_SECCOMP.
The tracer can skip the system call by setting the system call number
to -1. However, the current PTRACE_SET_SYSCALL_INFO interface does not
provide a way to specify the return value or error code that should be
reported to the tracee after skipping the call.
This patchset extends PTRACE_SET_SYSCALL_INFO to support skipping a system call
triggered via seccomp.
When a tracer retrieves a ptrace_syscall_info structure with 'op' set to
PTRACE_SYSCALL_INFO_SECCOMP, it can now choose to skip the system call. To do
this, the tracer changes 'op' to PTRACE_SYSCALL_INFO_EXIT and populates the
exit union fields (rval and is_error) to define the return value and error
status for the tracee.
System call suppression via PTRACE_SYSCALL_INFO_ENTRY is currently not
implemented. On some architectures (e.g. MIPS), when a system call is
skipped by setting the syscall number to -1 at the entry stop, the
architecture entry path unconditionally overwrites the return value
register with -ENOSYS, clobbering any custom return value set by the
tracer at the entry stop.
This patchset is a new version of the proposed patchset entitled:
ptrace_set_syscall_info: add support for seccomp syscall skipping and
instruction pointer modification
The patchset has been split in two:
syscall skipping(this)
instruction pointer modification (it will be updated soon)
Changes in v5:
* reworded the explanation for not supporting PTRACE_SYSCALL_INFO_ENTRY yet.
* selftests/ptrace: removed a redundant check.
Changes in v4:
* Reworded the commit messages for clarity.
* Renamed the local variable child_op to op in ptrace_set_syscall_info()
* Clean up and improve the coding style of selftests/ptrace/set_syscall_info.c
(suggested by Dmitry V. Levin)
Changes in v3:
* restrict the syscall skipping feature to PTRACE_SYSCALL_INFO_SECCOMP
Changes in v2:
bugfix: _NONE -> _EXIT transition was erroneously permitted
Changes since the previous patchset v2:
* bugfix: skip_syscall init value
* fix comments
Changes in (previous patchset) v2:
* use PTRACE_SYSCALL_INFO_EXIT instead of a new tag
* fixed most of the comments from sashiko.dev
Renzo Davoli (2):
ptrace: add PTRACE_SET_SYSCALL_INFO syscall skipping support
selftests/ptrace: add a test case for PTRACE_SET_SYSCALL_INFO syscall
skipping
kernel/ptrace.c | 27 ++-
.../selftests/ptrace/set_syscall_info.c | 172 +++++++++++++++++-
2 files changed, 193 insertions(+), 6 deletions(-)
--
2.53.0
^ permalink raw reply [flat|nested] 8+ messages in thread* [PATCH v5 1/2] ptrace: add PTRACE_SET_SYSCALL_INFO syscall skipping support 2026-07-09 10:09 [PATCH v5 0/2] PTRACE_SET_SYSCALL_INFO: add support for seccomp syscall skipping Renzo Davoli @ 2026-07-09 10:09 ` Renzo Davoli 2026-07-09 10:09 ` [PATCH v5 2/2] selftests/ptrace: add a test case for PTRACE_SET_SYSCALL_INFO syscall skipping Renzo Davoli 2026-07-10 15:38 ` [PATCH v5 0/2] PTRACE_SET_SYSCALL_INFO: add support for seccomp " Michal Suchánek 2 siblings, 0 replies; 8+ messages in thread From: Renzo Davoli @ 2026-07-09 10:09 UTC (permalink / raw) To: linux-kernel Cc: Renzo Davoli, Andrew Morton, Oleg Nesterov, Shuah Khan, Alexey Gladkov, Eugene Syromyatnikov, Davide Berardi, strace-devel, Dmitry V . Levin Extend PTRACE_SET_SYSCALL_INFO to support skipping a system call triggered via seccomp. When a tracer retrieves a ptrace_syscall_info structure with 'op' set to PTRACE_SYSCALL_INFO_SECCOMP, it can now choose to skip the system call. To do this, the tracer changes 'op' to PTRACE_SYSCALL_INFO_EXIT and populates the exit union fields (rval and is_error) to define the return value and error status for the tracee. System call suppression via PTRACE_SYSCALL_INFO_ENTRY is currently not implemented. On some architectures (e.g. MIPS), when a system call is skipped by setting the syscall number to -1 at the entry stop, the architecture entry path unconditionally overwrites the return value register with -ENOSYS, clobbering any custom return value set by the tracer at the entry stop. Signed-off-by: Renzo Davoli <renzo@cs.unibo.it> Reviewed-by: Oleg Nesterov <oleg@redhat.com> Reviewed-by: Dmitry V. Levin <ldv@strace.io> --- kernel/ptrace.c | 27 ++++++++++++++++++++++----- 1 file changed, 22 insertions(+), 5 deletions(-) diff --git a/kernel/ptrace.c b/kernel/ptrace.c index d041645d9d17..64fd1b455297 100644 --- a/kernel/ptrace.c +++ b/kernel/ptrace.c @@ -1099,7 +1099,7 @@ ptrace_set_syscall_info_seccomp(struct task_struct *child, struct pt_regs *regs, static int ptrace_set_syscall_info_exit(struct task_struct *child, struct pt_regs *regs, - struct ptrace_syscall_info *info) + struct ptrace_syscall_info *info, bool skip_syscall) { long rval = info->exit.rval; @@ -1111,6 +1111,9 @@ ptrace_set_syscall_info_exit(struct task_struct *child, struct pt_regs *regs, if (rval != info->exit.rval) return -ERANGE; + if (skip_syscall) + syscall_set_nr(child, regs, -1); + if (info->exit.is_error) syscall_set_return_value(child, regs, rval, 0); else @@ -1125,6 +1128,8 @@ ptrace_set_syscall_info(struct task_struct *child, unsigned long user_size, { struct pt_regs *regs = task_pt_regs(child); struct ptrace_syscall_info info; + int op; + bool skip_syscall = false; if (user_size < sizeof(info)) return -EINVAL; @@ -1141,15 +1146,27 @@ ptrace_set_syscall_info(struct task_struct *child, unsigned long user_size, if (info.flags || info.reserved) return -EINVAL; - /* Changing the type of the system call stop is not supported yet. */ - if (ptrace_get_syscall_info_op(child) != info.op) - return -EINVAL; + /* + * Changing the type of the system call stop is not allowed, with the + * following exception: + * PTRACE_SYSCALL_INFO_SECCOMP can be changed to PTRACE_SYSCALL_INFO_EXIT + * to skip the system call + */ + + op = ptrace_get_syscall_info_op(child); + if (op != info.op) { + if (info.op == PTRACE_SYSCALL_INFO_EXIT && + op == PTRACE_SYSCALL_INFO_SECCOMP) + skip_syscall = true; + else + return -EINVAL; + } switch (info.op) { case PTRACE_SYSCALL_INFO_ENTRY: return ptrace_set_syscall_info_entry(child, regs, &info); case PTRACE_SYSCALL_INFO_EXIT: - return ptrace_set_syscall_info_exit(child, regs, &info); + return ptrace_set_syscall_info_exit(child, regs, &info, skip_syscall); case PTRACE_SYSCALL_INFO_SECCOMP: return ptrace_set_syscall_info_seccomp(child, regs, &info); default: -- 2.53.0 ^ permalink raw reply [flat|nested] 8+ messages in thread
* [PATCH v5 2/2] selftests/ptrace: add a test case for PTRACE_SET_SYSCALL_INFO syscall skipping 2026-07-09 10:09 [PATCH v5 0/2] PTRACE_SET_SYSCALL_INFO: add support for seccomp syscall skipping Renzo Davoli 2026-07-09 10:09 ` [PATCH v5 1/2] ptrace: add PTRACE_SET_SYSCALL_INFO syscall skipping support Renzo Davoli @ 2026-07-09 10:09 ` Renzo Davoli 2026-07-10 15:38 ` [PATCH v5 0/2] PTRACE_SET_SYSCALL_INFO: add support for seccomp " Michal Suchánek 2 siblings, 0 replies; 8+ messages in thread From: Renzo Davoli @ 2026-07-09 10:09 UTC (permalink / raw) To: linux-kernel Cc: Renzo Davoli, Andrew Morton, Oleg Nesterov, Shuah Khan, Alexey Gladkov, Eugene Syromyatnikov, Davide Berardi, strace-devel, Dmitry V . Levin Check whether PTRACE_SET_SYSCALL_INFO syscall skipping semantics implemented in the kernel matches userspace expectations. Signed-off-by: Renzo Davoli <renzo@cs.unibo.it> Reviewed-by: Dmitry V. Levin <ldv@strace.io> --- .../selftests/ptrace/set_syscall_info.c | 172 +++++++++++++++++- 1 file changed, 171 insertions(+), 1 deletion(-) diff --git a/tools/testing/selftests/ptrace/set_syscall_info.c b/tools/testing/selftests/ptrace/set_syscall_info.c index 1cc411a41cd6..30bb00c7e6f2 100644 --- a/tools/testing/selftests/ptrace/set_syscall_info.c +++ b/tools/testing/selftests/ptrace/set_syscall_info.c @@ -11,9 +11,15 @@ #include <err.h> #include <fcntl.h> #include <signal.h> +#include <stdlib.h> +#include <stddef.h> #include <asm/unistd.h> +#include <sys/prctl.h> #include <linux/types.h> #include <linux/ptrace.h> +#include <linux/filter.h> +#include <linux/seccomp.h> +#include <linux/prctl.h> #if defined(_MIPS_SIM) && _MIPS_SIM == _MIPS_SIM_NABI32 /* @@ -36,6 +42,7 @@ struct si_exit { static unsigned int ptrace_stop; static pid_t tracee_pid; +static pid_t tracer_pid; static int kill_tracee(pid_t pid) @@ -64,6 +71,25 @@ sys_ptrace(int request, pid_t pid, unsigned long addr, unsigned long data) ptrace_stop, ##__VA_ARGS__); \ } while (0) +static int sys_seccomp(unsigned int operation, unsigned int flags, void *args) +{ + return syscall(__NR_seccomp, operation, flags, args); +} + +static struct sock_filter seccomp_filter[] = { + BPF_STMT(BPF_LD+BPF_W+BPF_ABS, offsetof(struct seccomp_data, nr)), + + BPF_JUMP(BPF_JMP+BPF_JEQ+BPF_K, __NR_restart_syscall, 0, 1), + BPF_STMT(BPF_RET+BPF_K, SECCOMP_RET_ALLOW), + + BPF_STMT(BPF_RET+BPF_K, SECCOMP_RET_TRACE), +}; + +static struct sock_fprog seccomp_prog = { + .filter = seccomp_filter, + .len = ARRAY_SIZE(seccomp_filter) +}; + static void check_psi_entry(struct __test_metadata *_metadata, const struct ptrace_syscall_info *info, @@ -128,7 +154,6 @@ check_psi_exit(struct __test_metadata *_metadata, TEST(set_syscall_info) { - const pid_t tracer_pid = getpid(); const kernel_ulong_t dummy[] = { (kernel_ulong_t) 0xdad0bef0bad0fed0ULL, (kernel_ulong_t) 0xdad1bef1bad1fed1ULL, @@ -138,6 +163,7 @@ TEST(set_syscall_info) (kernel_ulong_t) 0xdad5bef5bad5fed5ULL, }; int splice_in[2], splice_out[2]; + tracer_pid = getpid(); ASSERT_EQ(0, pipe(splice_in)); ASSERT_EQ(0, pipe(splice_out)); @@ -516,4 +542,148 @@ TEST(set_syscall_info) ASSERT_EQ(ptrace_stop, ARRAY_SIZE(si) * 2); } +TEST(set_syscall_info_seccomp) +{ + tracer_pid = getpid(); + tracee_pid = fork(); + + ASSERT_LE(0, tracee_pid) { + TH_LOG("fork: %m"); + } + + /* tracee */ + if (tracee_pid == 0) { + tracee_pid = getpid(); + ASSERT_EQ(0, sys_ptrace(PTRACE_TRACEME, 0, 0, 0)) { + TH_LOG("PTRACE_TRACEME: %m"); + } + ASSERT_EQ(0, kill(tracee_pid, SIGSTOP)) { + /* cannot happen */ + TH_LOG("kill SIGSTOP: %m"); + } + + ASSERT_EQ(0, prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0)) { + TH_LOG("prctl: %m"); + } + ASSERT_EQ(0, sys_seccomp(SECCOMP_SET_MODE_FILTER, 0, + (void *) &seccomp_prog)) { + TH_LOG("seccomp: %m"); + } + + /* run getpid unmodified */ + ASSERT_EQ(tracee_pid, getpid()) { + TH_LOG("getpid seccomp unchanged: %m"); + } + + /* run getppid instead of getpid */ + ASSERT_EQ(tracer_pid, getpid()) { + TH_LOG("getpid seccomp nr changes: %m"); + } + + /* skip getpid and return 42 */ + ASSERT_EQ(42, getpid()) { + TH_LOG("getpid skip set return value changes: %m"); + } + _exit(0); + } + + int status; + + /* tracer */ + ASSERT_LE(0, waitpid(-1, &status, 0)) { + LOG_KILL_TRACEE("waitpid: %m"); + } + + ASSERT_EQ(0, sys_ptrace(PTRACE_SETOPTIONS, tracee_pid, 0, + (PTRACE_O_TRACESECCOMP | PTRACE_O_TRACESYSGOOD))) + LOG_KILL_TRACEE("PTRACE_SETOPTIONS: %m"); + + ASSERT_EQ(0, sys_ptrace(PTRACE_CONT, tracee_pid, 0, 0)) { + LOG_KILL_TRACEE("PTRACE_CONT: %m"); + } + + for (ptrace_stop = 0; ; ++ptrace_stop) { + ASSERT_EQ(tracee_pid, wait(&status)) { + /* cannot happen */ + LOG_KILL_TRACEE("wait: %m"); + } + if (WIFEXITED(status)) { + tracee_pid = 0; /* the tracee is no more */ + ASSERT_EQ(0, WEXITSTATUS(status)) { + LOG_KILL_TRACEE("unexpected exit status %u", + WEXITSTATUS(status)); + } + break; + } + ASSERT_FALSE(WIFSIGNALED(status)) { + tracee_pid = 0; /* the tracee is no more */ + LOG_KILL_TRACEE("unexpected signal %u", + WTERMSIG(status)); + } + ASSERT_TRUE(WIFSTOPPED(status)) { + LOG_KILL_TRACEE("unexpected wait status %#x", status); + } + + ASSERT_EQ(status >> 8, SIGTRAP | (PTRACE_EVENT_SECCOMP << 8)) { + LOG_KILL_TRACEE("unexpected stop, wait status %#x", status); + } + + struct ptrace_syscall_info info = { + .op = 0xff /* invalid PTRACE_SYSCALL_INFO_* op */ + }; + size_t info_size = sizeof(info); + + ASSERT_LT(0, sys_ptrace(PTRACE_GET_SYSCALL_INFO, tracee_pid, info_size, (uintptr_t) &info)) { + LOG_KILL_TRACEE("PTRACE_GET_SYSCALL_INFO: %m"); + } + ASSERT_EQ(PTRACE_SYSCALL_INFO_SECCOMP, info.op) { + LOG_KILL_TRACEE("entry op mismatch: %m"); + } + ASSERT_TRUE(info.arch) { + LOG_KILL_TRACEE("entry arch mismatch: %m"); + } + ASSERT_TRUE(info.instruction_pointer) { + LOG_KILL_TRACEE("entry instruction_pointer mismatch: %m"); + } + ASSERT_TRUE(info.stack_pointer) { + LOG_KILL_TRACEE("entry stack_pointer mismatch: %m"); + } + + const unsigned int expected_nr[] = { + __NR_getpid, + __NR_getpid, + __NR_getpid, + __NR_exit_group + }; + + ASSERT_LT(ptrace_stop, ARRAY_SIZE(expected_nr)) { + LOG_KILL_TRACEE("ptrace stop overflow"); + } + ASSERT_EQ(info.seccomp.nr, expected_nr[ptrace_stop]) { + LOG_KILL_TRACEE("syscall nr mismatch"); + } + switch (ptrace_stop) { + case 0: + case 3: + break; + case 1: + info.seccomp.nr = __NR_getppid; + break; + case 2: + info.op = PTRACE_SYSCALL_INFO_EXIT; + info.exit.rval = 42; + info.exit.is_error = 0; + break; + } + + ASSERT_EQ(0, sys_ptrace(PTRACE_SET_SYSCALL_INFO, tracee_pid, info_size, (uintptr_t) &info)) { + LOG_KILL_TRACEE("PTRACE_SET_SYSCALL_INFO: %m"); + } + + ASSERT_EQ(0, sys_ptrace(PTRACE_CONT, tracee_pid, 0, 0)) { + LOG_KILL_TRACEE("PTRACE_CONT: %m"); + } + } +} + TEST_HARNESS_MAIN -- 2.53.0 ^ permalink raw reply [flat|nested] 8+ messages in thread
* Re: [PATCH v5 0/2] PTRACE_SET_SYSCALL_INFO: add support for seccomp syscall skipping 2026-07-09 10:09 [PATCH v5 0/2] PTRACE_SET_SYSCALL_INFO: add support for seccomp syscall skipping Renzo Davoli 2026-07-09 10:09 ` [PATCH v5 1/2] ptrace: add PTRACE_SET_SYSCALL_INFO syscall skipping support Renzo Davoli 2026-07-09 10:09 ` [PATCH v5 2/2] selftests/ptrace: add a test case for PTRACE_SET_SYSCALL_INFO syscall skipping Renzo Davoli @ 2026-07-10 15:38 ` Michal Suchánek 2026-07-10 16:49 ` Renzo Davoli 2026-07-13 20:45 ` Oleg Nesterov 2 siblings, 2 replies; 8+ messages in thread From: Michal Suchánek @ 2026-07-10 15:38 UTC (permalink / raw) To: Renzo Davoli Cc: linux-kernel, Andrew Morton, Oleg Nesterov, Shuah Khan, Alexey Gladkov, Eugene Syromyatnikov, Davide Berardi, strace-devel, Dmitry V . Levin On Thu, Jul 09, 2026 at 12:09:47PM +0200, Renzo Davoli wrote: > PTRACE_SET_SYSCALL_INFO is a generic ptrace API that complements > PTRACE_GET_SYSCALL_INFO by allowing a tracer to modify details of a > system call in which the tracee is currently blocked. > > The API is designed to let tracers inspect and modify system call > information in a simple, architecture-agnostic manner. > > The current implementation only supports modifying the subset of > system call information needed by strace: the system call number, > arguments, and return value. > > This patch set extends PTRACE_SET_SYSCALL_INFO with support for > skipping a system call. > > When a seccomp filter returns SECCOMP_RET_TRACE, the tracer receives, > via PTRACE_GET_SYSCALL_INFO, a struct ptrace_syscall_info with > op == PTRACE_SYSCALL_INFO_SECCOMP. > > The tracer can skip the system call by setting the system call number > to -1. However, the current PTRACE_SET_SYSCALL_INFO interface does not > provide a way to specify the return value or error code that should be > reported to the tracee after skipping the call. Hello, this will not work at least on powerpc, and possibly s390x. On these architectures the syscall number and the syscall return value share the same register. seccomp can skip a syscall by returning -1 from __secure_computing() which is then interpreted by the caller as a reason to skip further syscall processing, in particular interpretinfg the return value set in the registers as the syscall number. However, the tracing hook in seccomp does not return anything, in particular it does not change the return value of __secure_computing(). /* Allow the BPF to provide the event message */ ptrace_event(PTRACE_EVENT_SECCOMP, data); If the skip was propagated all the way to here so that the __secure_computing() could indicate to skip the signal it would work. Other ways to rework the setting of the return value are possible. eg. it was suggested to add a special field to pt_regs to hold the syscall return value, and only copy it to the register at syscall exit. Thanks Michal > > This patchset extends PTRACE_SET_SYSCALL_INFO to support skipping a system call > triggered via seccomp. > > When a tracer retrieves a ptrace_syscall_info structure with 'op' set to > PTRACE_SYSCALL_INFO_SECCOMP, it can now choose to skip the system call. To do > this, the tracer changes 'op' to PTRACE_SYSCALL_INFO_EXIT and populates the > exit union fields (rval and is_error) to define the return value and error > status for the tracee. > > System call suppression via PTRACE_SYSCALL_INFO_ENTRY is currently not > implemented. On some architectures (e.g. MIPS), when a system call is > skipped by setting the syscall number to -1 at the entry stop, the > architecture entry path unconditionally overwrites the return value > register with -ENOSYS, clobbering any custom return value set by the > tracer at the entry stop. > > This patchset is a new version of the proposed patchset entitled: > ptrace_set_syscall_info: add support for seccomp syscall skipping and > instruction pointer modification > The patchset has been split in two: > syscall skipping(this) > instruction pointer modification (it will be updated soon) > > Changes in v5: > * reworded the explanation for not supporting PTRACE_SYSCALL_INFO_ENTRY yet. > * selftests/ptrace: removed a redundant check. > > Changes in v4: > * Reworded the commit messages for clarity. > * Renamed the local variable child_op to op in ptrace_set_syscall_info() > * Clean up and improve the coding style of selftests/ptrace/set_syscall_info.c > (suggested by Dmitry V. Levin) > > Changes in v3: > * restrict the syscall skipping feature to PTRACE_SYSCALL_INFO_SECCOMP > > Changes in v2: > bugfix: _NONE -> _EXIT transition was erroneously permitted > > Changes since the previous patchset v2: > * bugfix: skip_syscall init value > * fix comments > > Changes in (previous patchset) v2: > * use PTRACE_SYSCALL_INFO_EXIT instead of a new tag > * fixed most of the comments from sashiko.dev > > Renzo Davoli (2): > ptrace: add PTRACE_SET_SYSCALL_INFO syscall skipping support > selftests/ptrace: add a test case for PTRACE_SET_SYSCALL_INFO syscall > skipping > > kernel/ptrace.c | 27 ++- > .../selftests/ptrace/set_syscall_info.c | 172 +++++++++++++++++- > 2 files changed, 193 insertions(+), 6 deletions(-) > > -- > 2.53.0 > ^ permalink raw reply [flat|nested] 8+ messages in thread
* Re: [PATCH v5 0/2] PTRACE_SET_SYSCALL_INFO: add support for seccomp syscall skipping 2026-07-10 15:38 ` [PATCH v5 0/2] PTRACE_SET_SYSCALL_INFO: add support for seccomp " Michal Suchánek @ 2026-07-10 16:49 ` Renzo Davoli 2026-07-21 10:10 ` Michal Suchánek 2026-07-13 20:45 ` Oleg Nesterov 1 sibling, 1 reply; 8+ messages in thread From: Renzo Davoli @ 2026-07-10 16:49 UTC (permalink / raw) To: Michal Suchánek Cc: linux-kernel, Andrew Morton, Oleg Nesterov, Shuah Khan, Alexey Gladkov, Eugene Syromyatnikov, Davide Berardi, strace-devel, Dmitry V . Levin Hi Michal, I am not an expert of the powerpc architecture. On Fri, Jul 10, 2026 at 05:38:32PM +0200, Michal Suchánek wrote: > On these architectures the syscall number and the syscall return value > share the same register. Reading the file arch/powerpc/include/asm/syscall.h the functions syscall_get_nr() syscall_set_nr() read and write regs->gpr[0] while syscall_get_return_value() syscall_set_return_value() read and write regs->gpr[3] (and one bit in regs->ccr as an error flag). Am I missing another part of the entry/exit path where the two overlap? Thank you. renzo ^ permalink raw reply [flat|nested] 8+ messages in thread
* Re: [PATCH v5 0/2] PTRACE_SET_SYSCALL_INFO: add support for seccomp syscall skipping 2026-07-10 16:49 ` Renzo Davoli @ 2026-07-21 10:10 ` Michal Suchánek 2026-07-30 7:39 ` Renzo Davoli 0 siblings, 1 reply; 8+ messages in thread From: Michal Suchánek @ 2026-07-21 10:10 UTC (permalink / raw) To: Renzo Davoli Cc: linux-kernel, Andrew Morton, Oleg Nesterov, Shuah Khan, Alexey Gladkov, Eugene Syromyatnikov, Davide Berardi, strace-devel, Dmitry V . Levin On Fri, Jul 10, 2026 at 06:49:11PM +0200, Renzo Davoli wrote: > Hi Michal, > I am not an expert of the powerpc architecture. > > On Fri, Jul 10, 2026 at 05:38:32PM +0200, Michal Suchánek wrote: > > On these architectures the syscall number and the syscall return value > > share the same register. > > Reading the file arch/powerpc/include/asm/syscall.h > the functions > syscall_get_nr() > syscall_set_nr() > read and write regs->gpr[0] > > while > syscall_get_return_value() > syscall_set_return_value() > read and write regs->gpr[3] (and one bit in regs->ccr as an error flag). > > Am I missing another part of the entry/exit path where the two overlap? Ok, so it has the other varian of the problem. The syscall return value overlaps a parameter. In any case it has this problem: On and other architectures the syscall number, the syscall arguments, and the syscall return value are disjunct. These architectures use this platform-specific to preset teh syscall return value at the very start of syscall processing, before entry ptrace and seccomp. On architectures where there is an overlap between the syscall number or arguments and the return value the return value cannot be preset before entry ptrace and seccomp because these need the syscall number abd arguments. On these platforms the tracer may change the registers in one way or another but there is no guarantee that when the syscall number is invalid, or -1 as is the value traditionally used to skip a syscall that there is a meaningful return value set. The -ENOSYS when handling an invalid syscal is set only after the entry ptrace and seccomp, overwriting any value that ptrace may have set. The SECCOMP_SET_MODE_FILTER SECCOMP_RET_ERRNO can accomplish setting areturn value and skipping the syscall because it changes the return value of __secure_computing() indicating that the return value has been set, and the syscall number or parameters in the registers must not be used anymore, and the syscall skipped. As ptrace does not have any way to return something it does not have this option. There are some workarounds possible. One is setting another flag in pt_regs to indicate the same as SECCOMP_RET_ERRNO: https://lore.kernel.org/all/20260714075935.1830145-1-mkchauras@gmail.com/ another is storing the return value otside of the registers allowing it to be set in advance: https://lore.kernel.org/all/20260715133830.2619853-1-svens@linux.ibm.com/ but as of now the architectures that have this overlap are not required to apply one of these workarounds, and not all do AFAIK. Note there is a piece of bogus code in seccomp that incorrectly skips syscalls when returning from ptrace: /* Allow the BPF to provide the event message */ ptrace_event(PTRACE_EVENT_SECCOMP, data); /* * The delivery of a fatal signal during event * notification may silently skip tracer notification, * which could leave us with a potentially unmodified * syscall that the tracer would have liked to have * changed. Since the process is about to die, we just * force the syscall to be skipped and let the signal * kill the process and correctly handle any tracer exit * notifications. */ if (fatal_signal_pending(current)) goto skip; /* Check if the tracer forced the syscall to be skipped. */ this_syscall = syscall_get_nr(current, current_pt_regs()); if (this_syscall < 0) goto skip; This does not work correctly when the return value of the syscal is not preset before calling __secure_computing() Thanks Michal ^ permalink raw reply [flat|nested] 8+ messages in thread
* Re: [PATCH v5 0/2] PTRACE_SET_SYSCALL_INFO: add support for seccomp syscall skipping 2026-07-21 10:10 ` Michal Suchánek @ 2026-07-30 7:39 ` Renzo Davoli 0 siblings, 0 replies; 8+ messages in thread From: Renzo Davoli @ 2026-07-30 7:39 UTC (permalink / raw) To: Michal Suchánek Cc: linux-kernel, Andrew Morton, Oleg Nesterov, Shuah Khan, Alexey Gladkov, Eugene Syromyatnikov, Davide Berardi, strace-devel, Dmitry V . Levin Hi Michal, Thank you for pointing out the PowerPC issue. I spent some time tracing the syscall entry/exit path on a current kernel (git 0e35b9b6ec0ff + my syscall_skip patch) to understand where the return value is lost. The path I observed is the following: ptrace_set_syscall_info() syscall_set_nr(..., -1); syscall_set_return_value(..., 42); system_call_exception() r0 = syscall_enter_from_user_mode(...); if (r0 >= NR_syscalls) return -ENOSYS; interrupt_64.S bl system_call_exception bl syscall_exit_prepare syscall_exit_prepare(r3, regs, ...) regs->gpr[3] = r3; Initially I thought the return value from system_call_exception() was ignored, but the assembly passes the PPC64 ABI return register directly as the first argument to syscall_exit_prepare(). Consequently, the -ENOSYS returned by system_call_exception() becomes the value written back into regs->gpr[3]. To verify this, I added a few temporary pr_info() statements. Immediately after syscall_set_return_value(): ptrace: regs->gpr[3] = 42 and at the beginning of syscall_exit_prepare(): syscall_exit_prepare: r3 = -38 regs->gpr[3] = 42 where -38 is -ENOSYS. syscall_exit_prepare() then executes regs->gpr[3] = r3; so the value installed by syscall_set_return_value() is overwritten before returning to userspace. This reproduces exactly the behavior you described: on PowerPC, a return value preset before the syscall is skipped is not preserved through the architecture exit path. This also seems inconsistent with the generic contract documented in include/linux/entry-common.h, namely that if a skipped syscall already has a return value installed via syscall_set_return_value(), that value should be returned; otherwise the default result is -ENOSYS. The current PowerPC exit path always installs the default value. Given these observations, it seems that the ptrace patch is exposing an existing architectural issue rather than introducing a new one. Regarding possible solutions, I currently see two possible directions. * Introduce a pt_regs flag indicating that the final userspace return value has already been installed (as you suggested), so that syscall_exit_prepare() does not overwrite it. From the PowerPC code path I investigated, this appears to be sufficient. The value installed by syscall_set_return_value() survives in regs->gpr[3] until syscall_exit_prepare(), where it is unconditionally replaced by the default -ENOSYS return value. In other words, the issue appears to be confined to the architecture-specific exit path. * Extend the generic syscall API (for example by introducing new helpers in asm/syscall.h or changing the semantics of the existing helpers) so that architectures can explicitly represent a final userspace return value. My impression is that Sven's proposal addresses a different problem, namely architectures where the live register state cannot safely hold the pending return value during syscall entry because it overlaps with the syscall number. On PowerPC, however, the value installed by syscall_set_return_value() is still present in regs->gpr[3] when syscall_exit_prepare() is entered, so the issue seems to be deciding whether the architecture should preserve that value or replace it with the default -ENOSYS. For this reason I am currently inclined towards the first approach for PowerPC, as it appears to be the minimal architecture-specific change required to restore the documented API contract. Does this match your understanding of the PowerPC exit path? If so, would you consider the flag approach the preferred direction for PowerPC? Thanks, renzo ^ permalink raw reply [flat|nested] 8+ messages in thread
* Re: [PATCH v5 0/2] PTRACE_SET_SYSCALL_INFO: add support for seccomp syscall skipping 2026-07-10 15:38 ` [PATCH v5 0/2] PTRACE_SET_SYSCALL_INFO: add support for seccomp " Michal Suchánek 2026-07-10 16:49 ` Renzo Davoli @ 2026-07-13 20:45 ` Oleg Nesterov 1 sibling, 0 replies; 8+ messages in thread From: Oleg Nesterov @ 2026-07-13 20:45 UTC (permalink / raw) To: Michal Suchánek Cc: Renzo Davoli, linux-kernel, Andrew Morton, Shuah Khan, Alexey Gladkov, Eugene Syromyatnikov, Davide Berardi, strace-devel, Dmitry V . Levin Michal, thanks for your comments. On 07/10, Michal Suchánek wrote: > > this will not work at least on powerpc, and possibly s390x. > > On these architectures the syscall number and the syscall return value > share the same register. You know, PTRACE_{GET,SET}_SYSCALL_INFO code is not is not trivial in that it tries to provide the arch-neutral API to user-space, and thus we need help from experts who understand the non-x86 low level details. So perhaps you can provide more details to explain whats wrong with ppc? I too can't understand the problem. > However, the tracing hook in seccomp does not return anything, in > particular it does not change the return value of __secure_computing(). > > /* Allow the BPF to provide the event message */ > ptrace_event(PTRACE_EVENT_SECCOMP, data); Sorry, I don't understand. __secure_computing(SECCOMP_RET_TRACE) checks syscall_get_nr() checks syscall_get_nr() and returns -1 if this_syscall < 0. Note also SECCOMP_RET_USER_NOTIF, this path can skip syscall and it does syscall_set_return_value(). But yes, this path doesn't use syscall_set_nr(). > Other ways to rework the setting of the return value are possible. eg. > it was suggested to add a special field to pt_regs to hold the syscall > return value, and only copy it to the register at syscall exit. Or we can add PTRACE_SKIP_SYSCALL set by ptrace_set_syscall_info() and checked/cleared by ptrace_report_syscall_entry(). Although I'd like to avoid this at least right now. In short. Thanks again, but I can't understand your email. Please add more details? Oleg. ^ permalink raw reply [flat|nested] 8+ messages in thread
end of thread, other threads:[~2026-07-30 7:48 UTC | newest] Thread overview: 8+ messages (download: mbox.gz / follow: Atom feed) -- links below jump to the message on this page -- 2026-07-09 10:09 [PATCH v5 0/2] PTRACE_SET_SYSCALL_INFO: add support for seccomp syscall skipping Renzo Davoli 2026-07-09 10:09 ` [PATCH v5 1/2] ptrace: add PTRACE_SET_SYSCALL_INFO syscall skipping support Renzo Davoli 2026-07-09 10:09 ` [PATCH v5 2/2] selftests/ptrace: add a test case for PTRACE_SET_SYSCALL_INFO syscall skipping Renzo Davoli 2026-07-10 15:38 ` [PATCH v5 0/2] PTRACE_SET_SYSCALL_INFO: add support for seccomp " Michal Suchánek 2026-07-10 16:49 ` Renzo Davoli 2026-07-21 10:10 ` Michal Suchánek 2026-07-30 7:39 ` Renzo Davoli 2026-07-13 20:45 ` Oleg Nesterov
This is a public inbox, see mirroring instructions for how to clone and mirror all data and code used for this inbox
all inboxes | Powered by JetHome®