selftests/ptrace: add a test case for PTRACE_GET_SYSCALL_INFO
Check whether PTRACE_GET_SYSCALL_INFO semantics implemented in the kernel matches userspace expectations. [akpm@linux-foundation.org: coding-style fixes] Link: http://lkml.kernel.org/r/20190510152852.GG28558@altlinux.org Signed-off-by: Dmitry V. Levin <ldv@altlinux.org> Acked-by: Shuah Khan <shuah@kernel.org> Cc: Oleg Nesterov <oleg@redhat.com> Cc: Andy Lutomirski <luto@kernel.org> Cc: Elvira Khabirova <lineprinter@altlinux.org> Cc: Eugene Syromyatnikov <esyr@redhat.com> Cc: Benjamin Herrenschmidt <benh@kernel.crashing.org> Cc: Greentime Hu <greentime@andestech.com> Cc: Helge Deller <deller@gmx.de> [parisc] Cc: James E.J. Bottomley <jejb@parisc-linux.org> Cc: James Hogan <jhogan@kernel.org> Cc: kbuild test robot <lkp@intel.com> Cc: Kees Cook <keescook@chromium.org> Cc: Michael Ellerman <mpe@ellerman.id.au> Cc: Paul Burton <paul.burton@mips.com> Cc: Paul Mackerras <paulus@samba.org> Cc: Ralf Baechle <ralf@linux-mips.org> Cc: Richard Kuo <rkuo@codeaurora.org> Cc: Vincent Chen <deanbo422@gmail.com> Signed-off-by: Andrew Morton <akpm@linux-foundation.org> Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
This commit is contained in:
parent
201766a20e
commit
ac76de555d
1
tools/testing/selftests/ptrace/.gitignore
vendored
1
tools/testing/selftests/ptrace/.gitignore
vendored
|
@ -1 +1,2 @@
|
|||
get_syscall_info
|
||||
peeksiginfo
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
# SPDX-License-Identifier: GPL-2.0-only
|
||||
CFLAGS += -iquote../../../../include/uapi -Wall
|
||||
|
||||
TEST_GEN_PROGS := peeksiginfo
|
||||
TEST_GEN_PROGS := get_syscall_info peeksiginfo
|
||||
|
||||
include ../lib.mk
|
||||
|
|
271
tools/testing/selftests/ptrace/get_syscall_info.c
Normal file
271
tools/testing/selftests/ptrace/get_syscall_info.c
Normal file
|
@ -0,0 +1,271 @@
|
|||
// 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
|
Loading…
Reference in New Issue
Block a user