// SPDX-License-Identifier: GPL-2.0+
/*
* Copyright (c) 2018-2025 Dmitry V. Levin <ldv@strace.io>
* All rights reserved.
*
* Check whether PTRACE_SET_SYSCALL_INFO semantics implemented in the kernel
* matches userspace expectations.
*/
#include "../kselftest_harness.h"
#include <err.h>
#include <fcntl.h>
#include <signal.h>
#include <asm /unistd.h>
#include <linux/types.h>
#include <linux/ptrace.h>
#if defined (_MIPS_SIM) && _MIPS_SIM == _MIPS_SIM_NABI32
/*
* MIPS N32 is the only architecture where __kernel_ulong_t
* does not match the bitness of syscall arguments.
*/
typedef unsigned long long kernel_ulong_t;
#else
typedef __kernel_ulong_t kernel_ulong_t;
#endif
struct si_entry {
int nr;
kernel_ulong_t args[6 ];
};
struct si_exit {
unsigned int is_error;
int rval;
};
static unsigned int ptrace_stop;
static pid_t tracee_pid;
static int
kill_tracee(pid_t pid)
{
if (!pid)
return 0 ;
int saved_errno = errno;
int rc = kill(pid, SIGKILL);
errno = saved_errno;
return rc;
}
static long
sys_ptrace(int request, pid_t pid, unsigned long addr, unsigned long data)
{
return syscall(__NR_ptrace, request, pid, addr, data);
}
#define LOG_KILL_TRACEE(fmt, ...) \
do { \
kill_tracee(tracee_pid); \
TH_LOG("wait #%d: " fmt, \
ptrace_stop, ## __VA_ARGS__); \
} while (0 )
static void
check_psi_entry(struct __test_metadata *_metadata,
const struct ptrace_syscall_info *info,
const struct si_entry *exp_entry,
const char *text)
{
unsigned int i;
int exp_nr = exp_entry->nr;
#if defined __s390__ || defined __s390x__
/* s390 is the only architecture that has 16-bit syscall numbers */
exp_nr &= 0 xffff;
#endif
ASSERT_EQ(PTRACE_SYSCALL_INFO_ENTRY, info->op) {
LOG_KILL_TRACEE("%s: entry stop mismatch" , text);
}
ASSERT_TRUE(info->arch) {
LOG_KILL_TRACEE("%s: entry stop mismatch" , text);
}
ASSERT_TRUE(info->instruction_pointer) {
LOG_KILL_TRACEE("%s: entry stop mismatch" , text);
}
ASSERT_TRUE(info->stack_pointer) {
LOG_KILL_TRACEE("%s: entry stop mismatch" , text);
}
ASSERT_EQ(exp_nr, info->entry.nr) {
LOG_KILL_TRACEE("%s: syscall nr mismatch" , text);
}
for (i = 0 ; i < ARRAY_SIZE(exp_entry->args); ++i) {
ASSERT_EQ(exp_entry->args[i], info->entry.args[i]) {
LOG_KILL_TRACEE("%s: syscall arg #%u mismatch" ,
text, i);
}
}
}
static void
check_psi_exit(struct __test_metadata *_metadata,
const struct ptrace_syscall_info *info,
const struct si_exit *exp_exit,
const char *text)
{
ASSERT_EQ(PTRACE_SYSCALL_INFO_EXIT, info->op) {
LOG_KILL_TRACEE("%s: exit stop mismatch" , text);
}
ASSERT_TRUE(info->arch) {
LOG_KILL_TRACEE("%s: exit stop mismatch" , text);
}
ASSERT_TRUE(info->instruction_pointer) {
LOG_KILL_TRACEE("%s: exit stop mismatch" , text);
}
ASSERT_TRUE(info->stack_pointer) {
LOG_KILL_TRACEE("%s: exit stop mismatch" , text);
}
ASSERT_EQ(exp_exit->is_error, info->exit .is_error) {
LOG_KILL_TRACEE("%s: exit stop mismatch" , text);
}
ASSERT_EQ(exp_exit->rval, info->exit .rval) {
LOG_KILL_TRACEE("%s: exit stop mismatch" , text);
}
}
TEST(set_syscall_info)
{
const pid_t tracer_pid = getpid();
const kernel_ulong_t dummy[] = {
(kernel_ulong_t) 0 xdad0bef0bad0fed0ULL,
(kernel_ulong_t) 0 xdad1bef1bad1fed1ULL,
(kernel_ulong_t) 0 xdad2bef2bad2fed2ULL,
(kernel_ulong_t) 0 xdad3bef3bad3fed3ULL,
(kernel_ulong_t) 0 xdad4bef4bad4fed4ULL,
(kernel_ulong_t) 0 xdad5bef5bad5fed5ULL,
};
int splice_in[2 ], splice_out[2 ];
ASSERT_EQ(0 , pipe(splice_in));
ASSERT_EQ(0 , pipe(splice_out));
ASSERT_EQ(sizeof (dummy), write(splice_in[1 ], dummy, sizeof (dummy)));
const struct {
struct si_entry entry[2 ];
struct si_exit exit [2 ];
} si[] = {
/* change scno, keep non-error rval */
{
{
{
__NR_gettid,
{
dummy[0 ], dummy[1 ], dummy[2 ],
dummy[3 ], dummy[4 ], dummy[5 ]
}
}, {
__NR_getppid,
{
dummy[0 ], dummy[1 ], dummy[2 ],
dummy[3 ], dummy[4 ], dummy[5 ]
}
}
}, {
{ 0 , tracer_pid }, { 0 , tracer_pid }
}
},
/* set scno to -1, keep error rval */
{
{
{
__NR_chdir,
{
(uintptr_t) "." ,
dummy[1 ], dummy[2 ],
dummy[3 ], dummy[4 ], dummy[5 ]
}
}, {
-1 ,
{
(uintptr_t) "." ,
dummy[1 ], dummy[2 ],
dummy[3 ], dummy[4 ], dummy[5 ]
}
}
}, {
{ 1 , -ENOSYS }, { 1 , -ENOSYS }
}
},
/* keep scno, change non-error rval */
{
{
{
__NR_getppid,
{
dummy[0 ], dummy[1 ], dummy[2 ],
dummy[3 ], dummy[4 ], dummy[5 ]
}
}, {
__NR_getppid,
{
dummy[0 ], dummy[1 ], dummy[2 ],
dummy[3 ], dummy[4 ], dummy[5 ]
}
}
}, {
{ 0 , tracer_pid }, { 0 , tracer_pid + 1 }
}
},
/* change arg1, keep non-error rval */
{
{
{
__NR_chdir,
{
(uintptr_t) "" ,
dummy[1 ], dummy[2 ],
dummy[3 ], dummy[4 ], dummy[5 ]
}
}, {
__NR_chdir,
{
(uintptr_t) "." ,
dummy[1 ], dummy[2 ],
dummy[3 ], dummy[4 ], dummy[5 ]
}
}
}, {
{ 0 , 0 }, { 0 , 0 }
}
},
/* set scno to -1, change error rval to non-error */
{
{
{
__NR_gettid,
{
dummy[0 ], dummy[1 ], dummy[2 ],
dummy[3 ], dummy[4 ], dummy[5 ]
}
}, {
-1 ,
{
dummy[0 ], dummy[1 ], dummy[2 ],
dummy[3 ], dummy[4 ], dummy[5 ]
}
}
}, {
{ 1 , -ENOSYS }, { 0 , tracer_pid }
}
},
/* change scno, change non-error rval to error */
{
{
{
__NR_chdir,
{
dummy[0 ], dummy[1 ], dummy[2 ],
dummy[3 ], dummy[4 ], dummy[5 ]
}
}, {
__NR_getppid,
{
dummy[0 ], dummy[1 ], dummy[2 ],
dummy[3 ], dummy[4 ], dummy[5 ]
}
}
}, {
{ 0 , tracer_pid }, { 1 , -EISDIR }
}
},
/* change scno and all args, change non-error rval */
{
{
{
__NR_gettid,
{
dummy[0 ], dummy[1 ], dummy[2 ],
dummy[3 ], dummy[4 ], dummy[5 ]
}
}, {
__NR_splice,
{
splice_in[0 ], 0 , splice_out[1 ], 0 ,
sizeof (dummy), SPLICE_F_NONBLOCK
}
}
}, {
{ 0 , sizeof (dummy) }, { 0 , sizeof (dummy) + 1 }
}
},
/* change arg1, no exit stop */
{
{
{
__NR_exit_group,
{
dummy[0 ], dummy[1 ], dummy[2 ],
dummy[3 ], dummy[4 ], dummy[5 ]
}
}, {
__NR_exit_group,
{
0 , dummy[1 ], dummy[2 ],
dummy[3 ], dummy[4 ], dummy[5 ]
}
}
}, {
{ 0 , 0 }, { 0 , 0 }
}
},
};
long rc;
unsigned int i;
tracee_pid = fork();
ASSERT_LE(0 , tracee_pid) {
TH_LOG("fork: %m" );
}
if (tracee_pid == 0 ) {
/* get the pid before PTRACE_TRACEME */
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" );
}
for (i = 0 ; i < ARRAY_SIZE(si); ++i) {
rc = syscall(si[i].entry[0 ].nr,
si[i].entry[0 ].args[0 ],
si[i].entry[0 ].args[1 ],
si[i].entry[0 ].args[2 ],
si[i].entry[0 ].args[3 ],
si[i].entry[0 ].args[4 ],
si[i].entry[0 ].args[5 ]);
if (si[i].exit [1 ].is_error) {
if (rc != -1 || errno != -si[i].exit [1 ].rval)
break ;
} else {
if (rc != si[i].exit [1 ].rval)
break ;
}
}
/*
* Something went wrong, but in this state tracee
* cannot reliably issue syscalls, so just crash.
*/
*(volatile unsigned char *) (uintptr_t) i = 42 ;
/* unreachable */
_exit (i + 1 );
}
for (ptrace_stop = 0 ; ; ++ptrace_stop) {
struct ptrace_syscall_info info = {
.op = 0 xff /* invalid PTRACE_SYSCALL_INFO_* op */
};
const size_t size = sizeof (info);
const int expected_entry_size =
(void *) &info.entry.args[6 ] - (void *) &info;
const int expected_exit_size =
(void *) (&info.exit .is_error + 1 ) -
(void *) &info;
int status;
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)) {
/* cannot happen */
LOG_KILL_TRACEE("unexpected wait status %#x" , status);
}
ASSERT_LT(ptrace_stop, ARRAY_SIZE(si) * 2 ) {
LOG_KILL_TRACEE("ptrace stop overflow" );
}
switch (WSTOPSIG(status)) {
case SIGSTOP:
ASSERT_EQ(0 , ptrace_stop) {
LOG_KILL_TRACEE("unexpected signal stop" );
}
ASSERT_EQ(0 , sys_ptrace(PTRACE_SETOPTIONS, tracee_pid,
0 , PTRACE_O_TRACESYSGOOD)) {
LOG_KILL_TRACEE("PTRACE_SETOPTIONS: %m" );
}
break ;
case SIGTRAP | 0 x80:
ASSERT_LT(0 , ptrace_stop) {
LOG_KILL_TRACEE("unexpected syscall stop" );
}
ASSERT_LT(0 , (rc = sys_ptrace(PTRACE_GET_SYSCALL_INFO,
tracee_pid, size,
(uintptr_t) &info))) {
LOG_KILL_TRACEE("PTRACE_GET_SYSCALL_INFO #1: %m" );
}
if (ptrace_stop & 1 ) {
/* entering syscall */
const struct si_entry *exp_entry =
&si[ptrace_stop / 2 ].entry[0 ];
const struct si_entry *set_entry =
&si[ptrace_stop / 2 ].entry[1 ];
/* check ptrace_syscall_info before the changes */
ASSERT_EQ(expected_entry_size, rc) {
LOG_KILL_TRACEE("PTRACE_GET_SYSCALL_INFO #1"
": entry stop mismatch" );
}
check_psi_entry(_metadata, &info, exp_entry,
"PTRACE_GET_SYSCALL_INFO #1" );
/* apply the changes */
info.entry.nr = set_entry->nr;
for (i = 0 ; i < ARRAY_SIZE(set_entry->args); ++i)
info.entry.args[i] = set_entry->args[i];
ASSERT_EQ(0 , sys_ptrace(PTRACE_SET_SYSCALL_INFO,
tracee_pid, size,
(uintptr_t) &info)) {
LOG_KILL_TRACEE("PTRACE_SET_SYSCALL_INFO: %m" );
}
/* check ptrace_syscall_info after the changes */
memset(&info, 0 , sizeof (info));
info.op = 0 xff;
ASSERT_LT(0 , (rc = sys_ptrace(PTRACE_GET_SYSCALL_INFO,
tracee_pid, size,
(uintptr_t) &info))) {
LOG_KILL_TRACEE("PTRACE_GET_SYSCALL_INFO: %m" );
}
ASSERT_EQ(expected_entry_size, rc) {
LOG_KILL_TRACEE("PTRACE_GET_SYSCALL_INFO #2"
": entry stop mismatch" );
}
check_psi_entry(_metadata, &info, set_entry,
"PTRACE_GET_SYSCALL_INFO #2" );
} else {
/* exiting syscall */
const struct si_exit *exp_exit =
&si[ptrace_stop / 2 - 1 ].exit [0 ];
const struct si_exit *set_exit =
&si[ptrace_stop / 2 - 1 ].exit [1 ];
/* check ptrace_syscall_info before the changes */
ASSERT_EQ(expected_exit_size, rc) {
LOG_KILL_TRACEE("PTRACE_GET_SYSCALL_INFO #1"
": exit stop mismatch" );
}
check_psi_exit(_metadata, &info, exp_exit,
"PTRACE_GET_SYSCALL_INFO #1" );
/* apply the changes */
info.exit .is_error = set_exit->is_error;
info.exit .rval = set_exit->rval;
ASSERT_EQ(0 , sys_ptrace(PTRACE_SET_SYSCALL_INFO,
tracee_pid, size,
(uintptr_t) &info)) {
LOG_KILL_TRACEE("PTRACE_SET_SYSCALL_INFO: %m" );
}
/* check ptrace_syscall_info after the changes */
memset(&info, 0 , sizeof (info));
info.op = 0 xff;
ASSERT_LT(0 , (rc = sys_ptrace(PTRACE_GET_SYSCALL_INFO,
tracee_pid, size,
(uintptr_t) &info))) {
LOG_KILL_TRACEE("PTRACE_GET_SYSCALL_INFO #2: %m" );
}
ASSERT_EQ(expected_exit_size, rc) {
LOG_KILL_TRACEE("PTRACE_GET_SYSCALL_INFO #2"
": exit stop mismatch" );
}
check_psi_exit(_metadata, &info, set_exit,
"PTRACE_GET_SYSCALL_INFO #2" );
}
break ;
default :
LOG_KILL_TRACEE("unexpected stop signal %u" ,
WSTOPSIG(status));
abort();
}
ASSERT_EQ(0 , sys_ptrace(PTRACE_SYSCALL, tracee_pid, 0 , 0 )) {
LOG_KILL_TRACEE("PTRACE_SYSCALL: %m" );
}
}
ASSERT_EQ(ptrace_stop, ARRAY_SIZE(si) * 2 );
}
TEST_HARNESS_MAIN
Messung V0.5 in Prozent C=94 H=85 G=89
¤ Dauer der Verarbeitung: 0.14 Sekunden
(vorverarbeitet am 2026-06-08)
¤
*© Formatika GbR, Deutschland