Fix PTRACE_SETOPTIONS tests
* strace.c [LINUX] (kill_save_errno): New function.
(test_ptrace_setoptions_followfork): Change return type to void.
Fix and harden error handling. Use kill_save_errno() to avoid errno
clobbering. Treat EIO from ptrace() the same way as EINVAL.
(test_ptrace_setoptions_for_all): Use kill_save_errno() to avoid errno
clobbering. Treat EIO from ptrace() the same way as EINVAL.
(main): Update use of test_ptrace_setoptions_followfork().
diff --git a/strace.c b/strace.c
index 1d79cc4..bfa9a0d 100644
--- a/strace.c
+++ b/strace.c
@@ -723,12 +723,20 @@
}
#ifdef LINUX
+static void kill_save_errno(pid_t pid, int sig)
+{
+ int saved_errno = errno;
+
+ (void) kill(pid, sig);
+ errno = saved_errno;
+}
+
/*
* Test whether the kernel support PTRACE_O_TRACECLONE et al options.
* First fork a new child, call ptrace with PTRACE_SETOPTIONS on it,
* and then see which options are supported by the kernel.
*/
-static int
+static void
test_ptrace_setoptions_followfork(void)
{
int pid, expected_grandchild = 0, found_grandchild = 0;
@@ -737,59 +745,93 @@
PTRACE_O_TRACEVFORK;
if ((pid = fork()) < 0)
- return -1;
+ perror_msg_and_die("fork");
else if (pid == 0) {
- if (ptrace(PTRACE_TRACEME, 0, (char *)1, 0) < 0)
- _exit(1);
- kill(getpid(), SIGSTOP);
- _exit(fork() < 0);
+ pid = getpid();
+ if (ptrace(PTRACE_TRACEME, 0, 0, 0) < 0)
+ perror_msg_and_die("%s: PTRACE_TRACEME doesn't work",
+ __func__);
+ kill(pid, SIGSTOP);
+ if (fork() < 0)
+ perror_msg_and_die("fork");
+ _exit(0);
}
while (1) {
int status, tracee_pid;
+ errno = 0;
tracee_pid = wait(&status);
- if (tracee_pid == -1) {
+ if (tracee_pid <= 0) {
if (errno == EINTR)
continue;
else if (errno == ECHILD)
break;
- perror("test_ptrace_setoptions_followfork");
- return -1;
+ kill_save_errno(pid, SIGKILL);
+ perror_msg_and_die("%s: unexpected wait result %d",
+ __func__, tracee_pid);
+ }
+ if (WIFEXITED(status)) {
+ if (WEXITSTATUS(status)) {
+ if (tracee_pid != pid)
+ kill_save_errno(pid, SIGKILL);
+ error_msg_and_die("%s: unexpected exit status %u",
+ __func__, WEXITSTATUS(status));
+ }
+ continue;
+ }
+ if (WIFSIGNALED(status)) {
+ if (tracee_pid != pid)
+ kill_save_errno(pid, SIGKILL);
+ error_msg_and_die("%s: unexpected signal %u",
+ __func__, WTERMSIG(status));
+ }
+ if (!WIFSTOPPED(status)) {
+ if (tracee_pid != pid)
+ kill_save_errno(tracee_pid, SIGKILL);
+ kill(pid, SIGKILL);
+ error_msg_and_die("%s: unexpected wait status %x",
+ __func__, status);
}
if (tracee_pid != pid) {
found_grandchild = tracee_pid;
- if (ptrace(PTRACE_CONT, tracee_pid, 0, 0) < 0 &&
- errno != ESRCH)
- kill(tracee_pid, SIGKILL);
- }
- else if (WIFSTOPPED(status)) {
- switch (WSTOPSIG(status)) {
- case SIGSTOP:
- if (ptrace(PTRACE_SETOPTIONS, pid,
- NULL, test_options) < 0) {
- kill(pid, SIGKILL);
- return -1;
- }
- break;
- case SIGTRAP:
- if (status >> 16 == PTRACE_EVENT_FORK) {
- long msg = 0;
-
- if (ptrace(PTRACE_GETEVENTMSG, pid,
- NULL, (long) &msg) == 0)
- expected_grandchild = msg;
- }
- break;
+ if (ptrace(PTRACE_CONT, tracee_pid, 0, 0) < 0) {
+ kill_save_errno(tracee_pid, SIGKILL);
+ kill_save_errno(pid, SIGKILL);
+ perror_msg_and_die("PTRACE_CONT doesn't work");
}
- if (ptrace(PTRACE_SYSCALL, pid, 0, 0) < 0 &&
- errno != ESRCH)
- kill(pid, SIGKILL);
+ continue;
+ }
+ switch (WSTOPSIG(status)) {
+ case SIGSTOP:
+ if (ptrace(PTRACE_SETOPTIONS, pid, 0, test_options) < 0
+ && errno != EINVAL && errno != EIO)
+ perror_msg("PTRACE_SETOPTIONS");
+ break;
+ case SIGTRAP:
+ if (status >> 16 == PTRACE_EVENT_FORK) {
+ long msg = 0;
+
+ if (ptrace(PTRACE_GETEVENTMSG, pid,
+ NULL, (long) &msg) == 0)
+ expected_grandchild = msg;
+ }
+ break;
+ }
+ if (ptrace(PTRACE_SYSCALL, pid, 0, 0) < 0) {
+ kill_save_errno(pid, SIGKILL);
+ perror_msg_and_die("PTRACE_SYSCALL doesn't work");
}
}
- if (expected_grandchild && expected_grandchild == found_grandchild)
+ if (expected_grandchild && expected_grandchild == found_grandchild) {
ptrace_setoptions |= test_options;
- return 0;
+ if (debug)
+ fprintf(stderr, "ptrace_setoptions = %#x\n",
+ ptrace_setoptions);
+ return;
+ }
+ error_msg("Test for PTRACE_O_TRACECLONE failed, "
+ "giving up using this feature.");
}
/*
@@ -809,7 +851,8 @@
static void
test_ptrace_setoptions_for_all(void)
{
- const unsigned int test_options = PTRACE_O_TRACESYSGOOD | PTRACE_O_TRACEEXEC;
+ const unsigned int test_options = PTRACE_O_TRACESYSGOOD |
+ PTRACE_O_TRACEEXEC;
int pid;
int it_worked = 0;
@@ -821,7 +864,8 @@
pid = getpid();
if (ptrace(PTRACE_TRACEME, 0L, 0L, 0L) < 0)
/* Note: exits with exitcode 1 */
- perror_msg_and_die("%s: PTRACE_TRACEME doesn't work", __func__);
+ perror_msg_and_die("%s: PTRACE_TRACEME doesn't work",
+ __func__);
kill(pid, SIGSTOP);
_exit(0); /* parent should see entry into this syscall */
}
@@ -834,18 +878,24 @@
if (tracee_pid <= 0) {
if (errno == EINTR)
continue;
- kill(pid, SIGKILL);
- perror_msg_and_die("%s: unexpected wait result %d", __func__, tracee_pid);
+ kill_save_errno(pid, SIGKILL);
+ perror_msg_and_die("%s: unexpected wait result %d",
+ __func__, tracee_pid);
}
if (WIFEXITED(status)) {
if (WEXITSTATUS(status) == 0)
break;
- /* PTRACE_TRACEME failed in child. This is fatal. */
- exit(1);
+ error_msg_and_die("%s: unexpected exit status %u",
+ __func__, WEXITSTATUS(status));
+ }
+ if (WIFSIGNALED(status)) {
+ error_msg_and_die("%s: unexpected signal %u",
+ __func__, WTERMSIG(status));
}
if (!WIFSTOPPED(status)) {
kill(pid, SIGKILL);
- error_msg_and_die("%s: unexpected wait status %x", __func__, status);
+ error_msg_and_die("%s: unexpected wait status %x",
+ __func__, status);
}
if (WSTOPSIG(status) == SIGSTOP) {
/*
@@ -854,15 +904,15 @@
* and thus will decide to not use the option.
* IOW: the outcome of the test will be correct.
*/
- if (ptrace(PTRACE_SETOPTIONS, pid, 0L, test_options) < 0)
- if (errno != EINVAL)
- perror_msg("PTRACE_SETOPTIONS");
+ if (ptrace(PTRACE_SETOPTIONS, pid, 0L, test_options) < 0
+ && errno != EINVAL && errno != EIO)
+ perror_msg("PTRACE_SETOPTIONS");
}
if (WSTOPSIG(status) == (SIGTRAP | 0x80)) {
it_worked = 1;
}
if (ptrace(PTRACE_SYSCALL, pid, 0L, 0L) < 0) {
- kill(pid, SIGKILL);
+ kill_save_errno(pid, SIGKILL);
perror_msg_and_die("PTRACE_SYSCALL doesn't work");
}
}
@@ -876,8 +926,8 @@
return;
}
- fprintf(stderr,
- "Test for PTRACE_O_TRACESYSGOOD failed, giving up using this feature.\n");
+ error_msg("Test for PTRACE_O_TRACESYSGOOD failed, "
+ "giving up using this feature.");
}
#endif
@@ -1067,17 +1117,8 @@
}
#ifdef LINUX
- if (followfork) {
- if (test_ptrace_setoptions_followfork() < 0) {
- fprintf(stderr,
- "Test for options supported by PTRACE_SETOPTIONS "
- "failed, giving up using this feature.\n");
- ptrace_setoptions = 0;
- }
- if (debug)
- fprintf(stderr, "ptrace_setoptions = %#x\n",
- ptrace_setoptions);
- }
+ if (followfork)
+ test_ptrace_setoptions_followfork();
test_ptrace_setoptions_for_all();
#endif