Check whether PTRACE_GET_SYSCALL_INFO is supported by the kernel

The result of this check is going to be used by subsequent commits.

* ptrace_syscall_info.c: New file.
* ptrace_syscall_info.h: Likewise.
* Makefile.am (strace_SOURCES): Add them.
* strace.c: Include "ptrace_syscall_info.h".
(init): Call test_ptrace_get_syscall_info.
This commit is contained in:
Дмитрий Левин 2018-11-21 01:44:16 +00:00
parent 86d073674e
commit 7488ce9e23
4 changed files with 276 additions and 0 deletions

View File

@ -249,6 +249,8 @@ strace_SOURCES = \
process_vm.c \
ptp.c \
ptrace.h \
ptrace_syscall_info.c \
ptrace_syscall_info.h \
quota.c \
random_ioctl.c \
readahead.c \

252
ptrace_syscall_info.c Normal file
View File

@ -0,0 +1,252 @@
/*
* Copyright (c) 2018 Dmitry V. Levin <ldv@altlinux.org>
* All rights reserved.
*
* SPDX-License-Identifier: LGPL-2.1-or-later
*/
#include "defs.h"
#include "kill_save_errno.h"
#include "ptrace.h"
#include "ptrace_syscall_info.h"
#include "scno.h"
#include <signal.h>
#include <sys/wait.h>
bool ptrace_get_syscall_info_supported;
static int
kill_tracee(pid_t pid)
{
return kill_save_errno(pid, SIGKILL);
}
#define FAIL do { ptrace_stop = -1U; goto done; } while (0)
static const unsigned int expected_none_size =
offsetof(struct ptrace_syscall_info, entry);
static const unsigned int expected_entry_size =
offsetofend(struct ptrace_syscall_info, entry.args);
static const unsigned int expected_exit_size =
offsetofend(struct ptrace_syscall_info, exit.is_error);
/*
* Test that PTRACE_GET_SYSCALL_INFO API is supported by the kernel, and
* that the semantics implemented in the kernel matches our expectations.
*/
bool
test_ptrace_get_syscall_info(void)
{
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;
int pid = fork();
if (pid < 0)
perror_func_msg_and_die("fork");
if (pid == 0) {
/* get the pid before PTRACE_TRACEME */
pid = getpid();
if (ptrace(PTRACE_TRACEME, 0L, 0L, 0L) < 0) {
/* exit with a nonzero exit status */
perror_func_msg_and_die("PTRACE_TRACEME");
}
kill(pid, SIGSTOP);
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);
int status;
long rc = waitpid(pid, &status, 0);
if (rc != pid) {
/* cannot happen */
kill_tracee(pid);
perror_func_msg_and_die("#%d: unexpected wait result"
" %ld", ptrace_stop, rc);
}
if (WIFEXITED(status)) {
/* tracee is no more */
pid = 0;
if (WEXITSTATUS(status) == 0)
break;
debug_func_msg("#%d: unexpected exit status %u",
ptrace_stop, WEXITSTATUS(status));
FAIL;
}
if (WIFSIGNALED(status)) {
/* tracee is no more */
pid = 0;
debug_func_msg("#%d: unexpected signal %u",
ptrace_stop, WTERMSIG(status));
FAIL;
}
if (!WIFSTOPPED(status)) {
/* cannot happen */
kill_tracee(pid);
error_func_msg_and_die("#%d: unexpected wait status"
" %#x", ptrace_stop, status);
}
switch (WSTOPSIG(status)) {
case SIGSTOP:
if (ptrace_stop) {
debug_func_msg("#%d: unexpected signal stop",
ptrace_stop);
FAIL;
}
if (ptrace(PTRACE_SETOPTIONS, pid, 0L,
PTRACE_O_TRACESYSGOOD) < 0) {
/* cannot happen */
kill_tracee(pid);
perror_func_msg_and_die("PTRACE_SETOPTIONS");
}
rc = ptrace(PTRACE_GET_SYSCALL_INFO, pid,
(void *) size, &info);
if (rc < 0) {
debug_perror_msg("PTRACE_GET_SYSCALL_INFO");
FAIL;
}
if (rc < (long) expected_none_size
|| info.op != PTRACE_SYSCALL_INFO_NONE
|| !info.arch
|| !info.instruction_pointer
|| !info.stack_pointer) {
debug_func_msg("signal stop mismatch");
FAIL;
}
break;
case SIGTRAP | 0x80:
rc = ptrace(PTRACE_GET_SYSCALL_INFO, pid,
(void *) size, &info);
if (rc < 0) {
debug_perror_msg("#%d: PTRACE_GET_SYSCALL_INFO",
ptrace_stop);
FAIL;
}
switch (ptrace_stop) {
case 1: /* entering chdir */
case 3: /* entering gettid */
case 5: /* entering exit_group */
exp_args = args[ptrace_stop / 2];
if (rc < (long) expected_entry_size
|| info.op != PTRACE_SYSCALL_INFO_ENTRY
|| !info.arch
|| !info.instruction_pointer
|| !info.stack_pointer
|| (info.entry.nr != exp_args[0])
|| (info.entry.args[0] != exp_args[1])
|| (info.entry.args[1] != exp_args[2])
|| (info.entry.args[2] != exp_args[3])
|| (info.entry.args[3] != exp_args[4])
|| (info.entry.args[4] != exp_args[5])
|| (info.entry.args[5] != exp_args[6])) {
debug_func_msg("#%d: entry stop"
" mismatch",
ptrace_stop);
FAIL;
}
break;
case 2: /* exiting chdir */
case 4: /* exiting gettid */
exp_param = &exit_param[ptrace_stop / 2 - 1];
if (rc < (long) expected_exit_size
|| info.op != PTRACE_SYSCALL_INFO_EXIT
|| !info.arch
|| !info.instruction_pointer
|| !info.stack_pointer
|| info.exit.is_error != exp_param->is_error
|| info.exit.rval != exp_param->rval) {
debug_func_msg("#%d: exit stop"
" mismatch",
ptrace_stop);
FAIL;
}
break;
default:
debug_func_msg("#%d: unexpected syscall stop",
ptrace_stop);
FAIL;
}
break;
default:
debug_func_msg("#%d: unexpected stop signal %#x",
ptrace_stop, WSTOPSIG(status));
FAIL;
}
if (ptrace(PTRACE_SYSCALL, pid, 0L, 0L) < 0) {
/* cannot happen */
kill_tracee(pid);
perror_func_msg_and_die("PTRACE_SYSCALL");
}
}
done:
if (pid) {
kill_tracee(pid);
waitpid(pid, NULL, 0);
ptrace_stop = -1U;
}
ptrace_get_syscall_info_supported =
ptrace_stop == ARRAY_SIZE(args) * 2;
if (ptrace_get_syscall_info_supported)
debug_msg("PTRACE_GET_SYSCALL_INFO works");
else
debug_msg("PTRACE_GET_SYSCALL_INFO does not work");
return ptrace_get_syscall_info_supported;
}

14
ptrace_syscall_info.h Normal file
View File

@ -0,0 +1,14 @@
/*
* Copyright (c) 2018 Dmitry V. Levin <ldv@altlinux.org>
* All rights reserved.
*
* SPDX-License-Identifier: LGPL-2.1-or-later
*/
#ifndef STRACE_PTRACE_SYSCALL_INFO_H
#define STRACE_PTRACE_SYSCALL_INFO_H
extern bool ptrace_get_syscall_info_supported;
extern bool test_ptrace_get_syscall_info(void);
#endif /* !STRACE_PTRACE_SYSCALL_INFO_H */

View File

@ -35,6 +35,7 @@
#include "largefile_wrappers.h"
#include "mmap_cache.h"
#include "number_set.h"
#include "ptrace_syscall_info.h"
#include "scno.h"
#include "printsiginfo.h"
#include "trace_event.h"
@ -1800,6 +1801,13 @@ init(int argc, char *argv[])
debug_msg("ptrace_setoptions = %#x", ptrace_setoptions);
test_ptrace_seize();
/*
* NOMMU provides no forks necessary for PTRACE_GET_SYSCALL_INFO test,
* leave the default unchanged.
*/
if (!NOMMU_SYSTEM)
test_ptrace_get_syscall_info();
/*
* Is something weird with our stdin and/or stdout -
* for example, may they be not open? In this case,