From nobody Tue Sep 29 08:26:18 2026 Received: from mail.virtlab.unibo.it (mail.virtlab.unibo.it [130.136.161.50]) by smtp.subspace.kernel.org (Postfix) with ESMTP id 8AECD369D76 for ; Mon, 10 Aug 2026 16:12:54 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=130.136.161.50 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1786378378; cv=none; b=EDPQuM/bpB7PEElC5IrXk9dxWYq0UykOUyg1uEp1l63YS7U0XoVxzFJ0M2wTGf5I1yhftMVlt/O3QupCo4NmzXbeaO8AKUNKhbbYitfPXixtjyNs1GERFBjkUmUig8UHEctwf81FpnQstXOGBY+taID8ZLd1ppt4mnFvnUqJk6I= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1786378378; c=relaxed/simple; bh=bOsOongfDRWRHM0lKmtkOm275q3SNfQ1Kb8ZCW3Pp7I=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=lPfWmXMkm/qbdUKBYrOYaNRXxhpejdT5uE7qHk9BOyGCAmRlOIRRn+piURl31FzNAgqjwLh3oElEhlpWJ0GEqlGWHwrv6krChUHBYeKIXfHtsxp7ATlDaTWqbnNuzuy6+qXUXPozzBEuFy5FLjI+bplmZ+kfDz55IEz1oJU3l9E= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=reject dis=none) header.from=cs.unibo.it; spf=pass smtp.mailfrom=cs.unibo.it; dkim=pass (1024-bit key) header.d=cs.unibo.it header.i=@cs.unibo.it header.b=wnNFabTc; arc=none smtp.client-ip=130.136.161.50 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=reject dis=none) header.from=cs.unibo.it Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=cs.unibo.it Authentication-Results: smtp.subspace.kernel.org; dkim=pass (1024-bit key) header.d=cs.unibo.it header.i=@cs.unibo.it header.b="wnNFabTc" Received: from eipi10.cs.unibo.it (unknown [185.33.59.40]) by mail.virtlab.unibo.it (Postfix) with ESMTPA id 11BA71C024E; Mon, 10 Aug 2026 18:04:37 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=simple/simple; d=cs.unibo.it; s=virtlab; t=1786377877; bh=bOsOongfDRWRHM0lKmtkOm275q3SNfQ1Kb8ZCW3Pp7I=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=wnNFabTck4xnGACVAj2+s8vRNXhWG8yy8anazUjh0ts7UfoiJh0X/2Gbd9RPKN/s/ IrcuVJjKMKGugxsIjJubwxOlrt/EiPEtiRCGBlp9I5Nag6tg5hLP4HtTX1oy8Di7L/ OIfuUPvLM8XbTIN5DS81rxO6T9kkKL8aRdn2M6Gw= From: Renzo Davoli To: linux-kernel@vger.kernel.org Cc: Renzo Davoli , Andrew Morton , Oleg Nesterov , Shuah Khan , Alexey Gladkov , Eugene Syromyatnikov , Davide Berardi , strace-devel@lists.strace.io, "Dmitry V . Levin" , Thomas Bogendoerfer Subject: [PATCH v6 1/2] ptrace: add PTRACE_SET_SYSCALL_INFO syscall skipping support Date: Mon, 10 Aug 2026 18:04:07 +0200 Message-ID: <20260810160408.609103-2-renzo@cs.unibo.it> X-Mailer: git-send-email 2.55.0 In-Reply-To: <20260810160408.609103-1-renzo@cs.unibo.it> References: <20260810160408.609103-1-renzo@cs.unibo.it> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable Content-Type: text/plain; charset="utf-8" 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 Reviewed-by: Oleg Nesterov Reviewed-by: Dmitry V. Levin --- 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 *c= hild, struct pt_regs *regs, =20 static int ptrace_set_syscall_info_exit(struct task_struct *child, struct pt_regs *re= gs, - struct ptrace_syscall_info *info) + struct ptrace_syscall_info *info, bool skip_syscall) { long rval =3D info->exit.rval; =20 @@ -1111,6 +1111,9 @@ ptrace_set_syscall_info_exit(struct task_struct *chil= d, struct pt_regs *regs, if (rval !=3D info->exit.rval) return -ERANGE; =20 + 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, un= signed long user_size, { struct pt_regs *regs =3D task_pt_regs(child); struct ptrace_syscall_info info; + int op; + bool skip_syscall =3D false; =20 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; =20 - /* Changing the type of the system call stop is not supported yet. */ - if (ptrace_get_syscall_info_op(child) !=3D 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 =3D ptrace_get_syscall_info_op(child); + if (op !=3D info.op) { + if (info.op =3D=3D PTRACE_SYSCALL_INFO_EXIT && + op =3D=3D PTRACE_SYSCALL_INFO_SECCOMP) + skip_syscall =3D true; + else + return -EINVAL; + } =20 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: --=20 2.55.0 From nobody Tue Sep 29 08:26:18 2026 Received: from mail.virtlab.unibo.it (mail.virtlab.unibo.it [130.136.161.50]) by smtp.subspace.kernel.org (Postfix) with ESMTP id 8ADD4368D40 for ; Mon, 10 Aug 2026 16:12:54 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=130.136.161.50 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1786378378; cv=none; b=U73yhrENbg/72poDjk+GHTi6m4qyRvfjEVb2yl0Qoi9KnNeuUn6SQppPA2ctoO85FRTLWtftQt0RwyyMJ4lhdSwJaCUpx5cAw77LNbtKdUlMuBzDigdu1SU1gSLeKHf2uPJipSNoBIbJ1zOvNVCbww24NIJNpzdvM71dSoIF8Fg= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1786378378; c=relaxed/simple; bh=vkbpiHSo7Ldis7/33JuTTgT3YhNT70Hf2hszIDmdh9I=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=s8QXuguKGgJaUvHAEGAke9hzxGYXcQJrhgxaZhwB5QIDVRaR0HoB40IwFPG0P9OGquLtTh/qm9HBd/vS0LFVx7UsWyw57hsUxAr1Kvyh1NqEDz3ksdDJxB+iIX5ORJXIVqcEC6Barc9r5OxqaIU2wNMXSlN+flcEo/FcvzGEkIY= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=reject dis=none) header.from=cs.unibo.it; spf=pass smtp.mailfrom=cs.unibo.it; dkim=pass (1024-bit key) header.d=cs.unibo.it header.i=@cs.unibo.it header.b=XZFSiOID; arc=none smtp.client-ip=130.136.161.50 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=reject dis=none) header.from=cs.unibo.it Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=cs.unibo.it Authentication-Results: smtp.subspace.kernel.org; dkim=pass (1024-bit key) header.d=cs.unibo.it header.i=@cs.unibo.it header.b="XZFSiOID" Received: from eipi10.cs.unibo.it (unknown [185.33.59.40]) by mail.virtlab.unibo.it (Postfix) with ESMTPA id 725061C0257; Mon, 10 Aug 2026 18:04:38 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=simple/simple; d=cs.unibo.it; s=virtlab; t=1786377878; bh=vkbpiHSo7Ldis7/33JuTTgT3YhNT70Hf2hszIDmdh9I=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=XZFSiOIDNy63vz6tPJJ8CVbRxSPTmAlYlFWzWLNgu1uz6/z0F0pu/vKS0mgxWr6+m d1WXBTFUWKTMmmdr+ReKpHxNRISJyRDGdLu2IqNyPhSngj+4lOhQ3gp0qd8CaDxCPv dD6NrADE8JDpKPkxH+rgfLDlbJrrGC82Vv/LmFJQ= From: Renzo Davoli To: linux-kernel@vger.kernel.org Cc: Renzo Davoli , Andrew Morton , Oleg Nesterov , Shuah Khan , Alexey Gladkov , Eugene Syromyatnikov , Davide Berardi , strace-devel@lists.strace.io, "Dmitry V . Levin" , Thomas Bogendoerfer Subject: [PATCH v6 2/2] selftests/ptrace: add a test case for PTRACE_SET_SYSCALL_INFO syscall skipping Date: Mon, 10 Aug 2026 18:04:08 +0200 Message-ID: <20260810160408.609103-3-renzo@cs.unibo.it> X-Mailer: git-send-email 2.55.0 In-Reply-To: <20260810160408.609103-1-renzo@cs.unibo.it> References: <20260810160408.609103-1-renzo@cs.unibo.it> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable Content-Type: text/plain; charset="utf-8" Check whether PTRACE_SET_SYSCALL_INFO syscall skipping semantics implemente= d in the kernel matches userspace expectations. Signed-off-by: Renzo Davoli Reviewed-by: Dmitry V. Levin --- .../selftests/ptrace/set_syscall_info.c | 184 +++++++++++++++++- 1 file changed, 183 insertions(+), 1 deletion(-) diff --git a/tools/testing/selftests/ptrace/set_syscall_info.c b/tools/test= ing/selftests/ptrace/set_syscall_info.c index 1cc411a41cd6..cb2fe5295ae3 100644 --- a/tools/testing/selftests/ptrace/set_syscall_info.c +++ b/tools/testing/selftests/ptrace/set_syscall_info.c @@ -11,9 +11,15 @@ #include #include #include +#include +#include #include +#include #include #include +#include +#include +#include =20 #if defined(_MIPS_SIM) && _MIPS_SIM =3D=3D _MIPS_SIM_NABI32 /* @@ -36,6 +42,7 @@ struct si_exit { =20 static unsigned int ptrace_stop; static pid_t tracee_pid; +static pid_t tracer_pid; =20 static int kill_tracee(pid_t pid) @@ -64,6 +71,25 @@ sys_ptrace(int request, pid_t pid, unsigned long addr, u= nsigned long data) ptrace_stop, ##__VA_ARGS__); \ } while (0) =20 +static int sys_seccomp(unsigned int operation, unsigned int flags, void *a= rgs) +{ + return syscall(__NR_seccomp, operation, flags, args); +} + +static struct sock_filter seccomp_filter[] =3D { + 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 =3D { + .filter =3D seccomp_filter, + .len =3D 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, =20 TEST(set_syscall_info) { - const pid_t tracer_pid =3D getpid(); const kernel_ulong_t dummy[] =3D { (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 =3D getpid(); =20 ASSERT_EQ(0, pipe(splice_in)); ASSERT_EQ(0, pipe(splice_out)); @@ -516,4 +542,160 @@ TEST(set_syscall_info) ASSERT_EQ(ptrace_stop, ARRAY_SIZE(si) * 2); } =20 +TEST(set_syscall_info_seccomp) +{ + tracer_pid =3D getpid(); + tracee_pid =3D fork(); + + ASSERT_LE(0, tracee_pid) { + TH_LOG("fork: %m"); + } + + /* tracee */ + if (tracee_pid =3D=3D 0) { + tracee_pid =3D 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()) { + _exit(1); + } + + /* run getppid instead of getpid */ + ASSERT_EQ(tracer_pid, getpid()) { + _exit(1); + } + + /* skip getpid and return 42 */ + ASSERT_EQ(42, getpid()) { + _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"); + } + + const char *testname[] =3D { + "unknown error", + "getpid seccomp unchanged", + "getpid seccomp nr change: getppid", + "getpid seccomp skip+set retvalue" + }; + + const unsigned int expected_nr[] =3D { + __NR_getpid, + __NR_getpid, + __NR_getpid, + __NR_exit_group + }; + + for (ptrace_stop =3D 0; ; ++ptrace_stop) { + ASSERT_EQ(tracee_pid, wait(&status)) { + /* cannot happen */ + LOG_KILL_TRACEE("wait: %m"); + } + =09 + if (WIFEXITED(status)) { + tracee_pid =3D 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 =3D 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 =3D { + .op =3D 0xff /* invalid PTRACE_SYSCALL_INFO_* op */ + }; + size_t info_size =3D 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 =3D=3D __NR_exit_group && info.seccomp.args= [0] =3D=3D 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"); + } + switch (ptrace_stop) { + case 0: + case 3: + break; + case 1: + info.seccomp.nr =3D __NR_getppid; + break; + case 2: + info.op =3D PTRACE_SYSCALL_INFO_EXIT; + info.exit.rval =3D 42; + info.exit.is_error =3D 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 --=20 2.55.0