* [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 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
* 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
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®