// SPDX-License-Identifier: GPL-2.0+ /* * Copyright (c) 2018 Dmitry V. Levin <ldv@altlinux.org> * All rights reserved. * * Check whether PTRACE_GET_SYSCALL_INFO semantics implemented in the kernel * matches userspace expectations. */ #include "../kselftest_harness.h" #include <err.h> #include <signal.h> #include <asm/unistd.h> #include "linux/ptrace.h" 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(pid); \ TH_LOG("wait #%d: " fmt, \ ptrace_stop, ##__VA_ARGS__); \ } while (0) TEST(get_syscall_info) { static const unsigned long args[][7] = { /* a sequence of architecture-agnostic syscalls */ { __NR_chdir, (unsigned long) "", 0xbad1fed1, 0xbad2fed2, 0xbad3fed3, 0xbad4fed4, 0xbad5fed5 }, { __NR_gettid, 0xcaf0bea0, 0xcaf1bea1, 0xcaf2bea2, 0xcaf3bea3, 0xcaf4bea4, 0xcaf5bea5 }, { __NR_exit_group, 0, 0xfac1c0d1, 0xfac2c0d2, 0xfac3c0d3, 0xfac4c0d4, 0xfac5c0d5 } }; const unsigned long *exp_args; pid_t pid = fork(); ASSERT_LE(0, pid) { TH_LOG("fork: %m"); } if (pid == 0) { /* get the pid before PTRACE_TRACEME */ pid = getpid(); ASSERT_EQ(0, sys_ptrace(PTRACE_TRACEME, 0, 0, 0)) { TH_LOG("PTRACE_TRACEME: %m"); } ASSERT_EQ(0, kill(pid, SIGSTOP)) { /* cannot happen */ TH_LOG("kill SIGSTOP: %m"); } for (unsigned int i = 0; i < ARRAY_SIZE(args); ++i) { syscall(args[i][0], args[i][1], args[i][2], args[i][3], args[i][4], args[i][5], args[i][6]); } /* unreachable */ _exit(1); } const struct { unsigned int is_error; int rval; } *exp_param, exit_param[] = { { 1, -ENOENT }, /* chdir */ { 0, pid } /* gettid */ }; unsigned int ptrace_stop; 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_none_size = (void *) &info.entry - (void *) &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; long rc; ASSERT_EQ(pid, wait(&status)) { /* cannot happen */ LOG_KILL_TRACEE("wait: %m"); } if (WIFEXITED(status)) { pid = 0; /* the tracee is no more */ ASSERT_EQ(0, WEXITSTATUS(status)); break; } ASSERT_FALSE(WIFSIGNALED(status)) { 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); } switch (WSTOPSIG(status)) { case SIGSTOP: ASSERT_EQ(0, ptrace_stop) { LOG_KILL_TRACEE("unexpected signal stop"); } ASSERT_EQ(0, sys_ptrace(PTRACE_SETOPTIONS, pid, 0, PTRACE_O_TRACESYSGOOD)) { LOG_KILL_TRACEE("PTRACE_SETOPTIONS: %m"); } ASSERT_LT(0, (rc = sys_ptrace(PTRACE_GET_SYSCALL_INFO, pid, size, (unsigned long) &info))) { LOG_KILL_TRACEE("PTRACE_GET_SYSCALL_INFO: %m"); } ASSERT_EQ(expected_none_size, rc) { LOG_KILL_TRACEE("signal stop mismatch"); } ASSERT_EQ(PTRACE_SYSCALL_INFO_NONE, info.op) { LOG_KILL_TRACEE("signal stop mismatch"); } ASSERT_TRUE(info.arch) { LOG_KILL_TRACEE("signal stop mismatch"); } ASSERT_TRUE(info.instruction_pointer) { LOG_KILL_TRACEE("signal stop mismatch"); } ASSERT_TRUE(info.stack_pointer) { LOG_KILL_TRACEE("signal stop mismatch"); } break; case SIGTRAP | 0x80: ASSERT_LT(0, (rc = sys_ptrace(PTRACE_GET_SYSCALL_INFO, pid, size, (unsigned long) &info))) { LOG_KILL_TRACEE("PTRACE_GET_SYSCALL_INFO: %m"); } switch (ptrace_stop) { case 1: /* entering chdir */ case 3: /* entering gettid */ case 5: /* entering exit_group */ exp_args = args[ptrace_stop / 2]; ASSERT_EQ(expected_entry_size, rc) { LOG_KILL_TRACEE("entry stop mismatch"); } ASSERT_EQ(PTRACE_SYSCALL_INFO_ENTRY, info.op) { LOG_KILL_TRACEE("entry stop mismatch"); } ASSERT_TRUE(info.arch) { LOG_KILL_TRACEE("entry stop mismatch"); } ASSERT_TRUE(info.instruction_pointer) { LOG_KILL_TRACEE("entry stop mismatch"); } ASSERT_TRUE(info.stack_pointer) { LOG_KILL_TRACEE("entry stop mismatch"); } ASSERT_EQ(exp_args[0], info.entry.nr) { LOG_KILL_TRACEE("entry stop mismatch"); } ASSERT_EQ(exp_args[1], info.entry.args[0]) { LOG_KILL_TRACEE("entry stop mismatch"); } ASSERT_EQ(exp_args[2], info.entry.args[1]) { LOG_KILL_TRACEE("entry stop mismatch"); } ASSERT_EQ(exp_args[3], info.entry.args[2]) { LOG_KILL_TRACEE("entry stop mismatch"); } ASSERT_EQ(exp_args[4], info.entry.args[3]) { LOG_KILL_TRACEE("entry stop mismatch"); } ASSERT_EQ(exp_args[5], info.entry.args[4]) { LOG_KILL_TRACEE("entry stop mismatch"); } ASSERT_EQ(exp_args[6], info.entry.args[5]) { LOG_KILL_TRACEE("entry stop mismatch"); } break; case 2: /* exiting chdir */ case 4: /* exiting gettid */ exp_param = &exit_param[ptrace_stop / 2 - 1]; ASSERT_EQ(expected_exit_size, rc) { LOG_KILL_TRACEE("exit stop mismatch"); } ASSERT_EQ(PTRACE_SYSCALL_INFO_EXIT, info.op) { LOG_KILL_TRACEE("exit stop mismatch"); } ASSERT_TRUE(info.arch) { LOG_KILL_TRACEE("exit stop mismatch"); } ASSERT_TRUE(info.instruction_pointer) { LOG_KILL_TRACEE("exit stop mismatch"); } ASSERT_TRUE(info.stack_pointer) { LOG_KILL_TRACEE("exit stop mismatch"); } ASSERT_EQ(exp_param->is_error, info.exit.is_error) { LOG_KILL_TRACEE("exit stop mismatch"); } ASSERT_EQ(exp_param->rval, info.exit.rval) { LOG_KILL_TRACEE("exit stop mismatch"); } break; default: LOG_KILL_TRACEE("unexpected syscall stop"); abort(); } break; default: LOG_KILL_TRACEE("unexpected stop signal %#x", WSTOPSIG(status)); abort(); } ASSERT_EQ(0, sys_ptrace(PTRACE_SYSCALL, pid, 0, 0)) { LOG_KILL_TRACEE("PTRACE_SYSCALL: %m"); } } ASSERT_EQ(ARRAY_SIZE(args) * 2, ptrace_stop); } TEST_HARNESS_MAIN