| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519 |
- // 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 &= 0xffff;
- #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) 0xdad0bef0bad0fed0ULL,
- (kernel_ulong_t) 0xdad1bef1bad1fed1ULL,
- (kernel_ulong_t) 0xdad2bef2bad2fed2ULL,
- (kernel_ulong_t) 0xdad3bef3bad3fed3ULL,
- (kernel_ulong_t) 0xdad4bef4bad4fed4ULL,
- (kernel_ulong_t) 0xdad5bef5bad5fed5ULL,
- };
- 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 = 0xff /* 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 | 0x80:
- 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 = 0xff;
- 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 = 0xff;
- 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
|