From: Renzo Davoli <renzo@cs.unibo.it>
To: linux-kernel@vger.kernel.org
Cc: "Renzo Davoli" <renzo@cs.unibo.it>,
"Oleg Nesterov" <oleg@redhat.com>,
"Michal Suchánek" <msuchanek@suse.de>,
"Kees Cook" <kees@kernel.org>,
"Andy Lutomirski" <luto@amacapital.net>,
"Will Drewry" <wad@chromium.org>,
"Thomas Gleixner" <tglx@kernel.org>,
"Andrew Morton" <akpm@linux-foundation.org>,
"Shuah Khan" <shuah@kernel.org>,
"Dmitry V . Levin" <ldv@strace.io>,
"Alexey Gladkov" <legion@kernel.org>,
"Eugene Syromyatnikov" <evgsyr@gmail.com>,
"Davide Berardi" <berardi.dav@gmail.com>,
"Thomas Bogendoerfer" <tsbogend@alpha.franken.de>,
"Heiko Carstens" <hca@linux.ibm.com>,
"Vasily Gorbik" <gor@linux.ibm.com>,
"Madhavan Srinivasan" <maddy@linux.ibm.com>,
linuxppc-dev@lists.ozlabs.org, linux-s390@vger.kernel.org,
linux-kselftest@vger.kernel.org, strace-devel@lists.strace.io
Subject: [PATCH v9 3/3] selftests/ptrace: add a test case for PTRACE_SET_SYSCALL_INFO syscall skipping
Date: Tue, 20 Oct 2026 17:29:43 +0200 [thread overview]
Message-ID: <20261020152945.575664-4-renzo@cs.unibo.it> (raw)
In-Reply-To: <20261020152945.575664-1-renzo@cs.unibo.it>
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>
[v8: also check an error return value, and that the syscall cannot be
changed anymore once skipped while its return value still can;
checkpatch fixes]
Assisted-by: claude-opus-5-5
---
.../selftests/ptrace/set_syscall_info.c | 230 +++++++++++++++++-
1 file changed, 229 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..e4fc2779ee5a 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,206 @@ TEST(set_syscall_info)
ASSERT_EQ(ptrace_stop, ARRAY_SIZE(si) * 2);
}
+TEST(set_syscall_info_seccomp)
+{
+ /*
+ * Check for seccomp filter support without installing a filter:
+ * a NULL filter fails with EFAULT if SECCOMP_SET_MODE_FILTER is
+ * supported, and with EINVAL or ENOSYS otherwise.
+ */
+ ASSERT_EQ(-1, sys_seccomp(SECCOMP_SET_MODE_FILTER, 0, NULL));
+ if (errno != EFAULT)
+ SKIP(return, "SECCOMP_SET_MODE_FILTER: %m");
+
+ 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");
+ }
+
+ /*
+ * From now on every syscall stops the tracee, so do not use
+ * ASSERT_*() here: it would write() its message before
+ * _exit(1) and the tracer would report a syscall nr mismatch
+ * instead of the test case that failed.
+ */
+
+ /* run getpid unmodified */
+ if (getpid() != tracee_pid)
+ _exit(1);
+
+ /* run getppid instead of getpid */
+ if (getpid() != tracer_pid)
+ _exit(1);
+
+ /* skip getpid and return 42 */
+ if (getpid() != 42)
+ _exit(1);
+
+ /* skip getpid and fail with ESRCH */
+ errno = 0;
+ if (syscall(__NR_getpid) != -1 || errno != ESRCH)
+ _exit(1);
+
+ _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");
+ }
+
+ static const char * const testname[] = {
+ "unknown error",
+ "getpid seccomp unchanged",
+ "getpid seccomp nr change: getppid",
+ "getpid seccomp skip+set retvalue",
+ "getpid seccomp skip+set error"
+ };
+
+ static const unsigned int expected_nr[] = {
+ __NR_getpid,
+ __NR_getpid,
+ __NR_getpid,
+ __NR_getpid,
+ __NR_exit_group
+ };
+
+ 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");
+ }
+
+ ASSERT_LT(ptrace_stop, ARRAY_SIZE(expected_nr)) {
+ LOG_KILL_TRACEE("ptrace stop overflow");
+ }
+ ASSERT_FALSE(info.seccomp.nr == __NR_exit_group && info.seccomp.args[0] == 1) {
+ LOG_KILL_TRACEE("tracee error: %s",
+ testname[ptrace_stop]);
+ }
+ ASSERT_EQ(info.seccomp.nr, expected_nr[ptrace_stop]) {
+ LOG_KILL_TRACEE("syscall nr mismatch");
+ }
+ const struct ptrace_syscall_info seccomp_info = info;
+
+ switch (ptrace_stop) {
+ case 0:
+ case 4:
+ break;
+ case 1:
+ info.seccomp.nr = __NR_getppid;
+ break;
+ case 2:
+ info.op = PTRACE_SYSCALL_INFO_EXIT;
+ info.exit.rval = 41;
+ info.exit.is_error = 0;
+ ASSERT_EQ(0, sys_ptrace(PTRACE_SET_SYSCALL_INFO,
+ tracee_pid, info_size,
+ (uintptr_t) &info)) {
+ LOG_KILL_TRACEE("PTRACE_SET_SYSCALL_INFO: %m");
+ }
+ /* the syscall cannot be changed anymore once skipped */
+ ASSERT_EQ(-1, sys_ptrace(PTRACE_SET_SYSCALL_INFO,
+ tracee_pid, info_size,
+ (uintptr_t) &seccomp_info)) {
+ LOG_KILL_TRACEE("PTRACE_SET_SYSCALL_INFO after skip succeeded");
+ }
+ ASSERT_EQ(EINVAL, errno) {
+ LOG_KILL_TRACEE("PTRACE_SET_SYSCALL_INFO after skip: %m");
+ }
+ /* but its return value can */
+ info.exit.rval = 42;
+ break;
+ case 3:
+ info.op = PTRACE_SYSCALL_INFO_EXIT;
+ info.exit.rval = -ESRCH;
+ info.exit.is_error = 1;
+ 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.55.0
next prev parent reply other threads:[~2026-10-20 15:30 UTC|newest]
Thread overview: 5+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-10-20 15:29 [PATCH v9 0/3] PTRACE_SET_SYSCALL_INFO: add support for seccomp " Renzo Davoli
2026-10-20 15:29 ` [PATCH v9 1/3] seccomp: let the tracer skip a syscall independently of its number Renzo Davoli
2026-10-20 15:29 ` [PATCH v9 2/3] ptrace: add PTRACE_SET_SYSCALL_INFO syscall skipping support Renzo Davoli
2026-10-20 15:29 ` Renzo Davoli [this message]
2026-10-24 17:05 ` [PATCH v9 0/3] PTRACE_SET_SYSCALL_INFO: add support for seccomp syscall skipping Renzo Davoli
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20261020152945.575664-4-renzo@cs.unibo.it \
--to=renzo@cs.unibo.it \
--cc=akpm@linux-foundation.org \
--cc=berardi.dav@gmail.com \
--cc=evgsyr@gmail.com \
--cc=gor@linux.ibm.com \
--cc=hca@linux.ibm.com \
--cc=kees@kernel.org \
--cc=ldv@strace.io \
--cc=legion@kernel.org \
--cc=linux-kernel@vger.kernel.org \
--cc=linux-kselftest@vger.kernel.org \
--cc=linux-s390@vger.kernel.org \
--cc=linuxppc-dev@lists.ozlabs.org \
--cc=luto@amacapital.net \
--cc=maddy@linux.ibm.com \
--cc=msuchanek@suse.de \
--cc=oleg@redhat.com \
--cc=shuah@kernel.org \
--cc=strace-devel@lists.strace.io \
--cc=tglx@kernel.org \
--cc=tsbogend@alpha.franken.de \
--cc=wad@chromium.org \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
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®