lkml.org 
[lkml]   [2007]   [Dec]   [5]   [last100]   RSS Feed
Views: [wrap][no wrap]   [headers]  [forward] 
 
Messages in this thread
/
Date
From
SubjectRe: [PATCH] prevent sending wrong signals to a traced process whose tracer gets killed
Petr Tesarik wrote:
> Hi,
>
> I experienced troubles when tracing a process with strace. Sometimes,
> when I killed the strace process (SIGKILL), the traced process was also
> killed. I found out that it was getting SIGTRAP and, indeed, when the
> traced process set up a signal handler for SIGTRAP, it no longer died.
>
> I noticed that normally, when the traced process is continued (via
> PTRACE_CONT or similar), the signal to be sent to it is stored in
> current->exit_code, which is then examined by the arch-specific code and
> usually leads to something like:
>
> send_sig(current->exit_code, current, 1);
>
> The exit_code is set in ptrace_stop(), but the tracing process may go
> away while the traced process waits for it, and in that case exit_code
> is left as-is. I think we must set it to zero in ptrace_untrace().

My patch was very wrong, but at least I produced a test case. It fails
on all systems I could test. The only correct solution (TM) could be
achieved if we could tell the tracee in ptrace_stop() that the tracer
actually died and it should use nostop_code instead of exit_code.
Possibly a new flag?

Regards,
Petr Tesarik

/*
* Author: Petr Tesarik <ptesarik@suse.cz>
*
* This program tests what happens when the tracer goes away while
* the tracee is waiting for it.
*
* A successful run should produce output like this:
*
* Started child 4206
* Attached process 4206
*
* OK
* Parent terminated with exit code 0
*
* A failed run looks like this:
*
* Started child 4318
* Attached process 4318
* Parent terminated with exit code 0
* Child died unexpectedly
*
* The exit status is:
* 0 (TEST_OK) success
* 1 (TEST_FAILED) the test failed
* 2 (TEST_ABORTED) unexpected failure prevented the test
* to be performed
*/

#include <stdio.h>
#include <string.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
#include <sys/time.h>
#include <time.h>
#include <signal.h>
#include <sys/ptrace.h>

#define TEST_OK 0
#define TEST_FAILED 1
#define TEST_ABORTED 2

int run_child()
{
struct timeval tv;

if (ptrace(PTRACE_TRACEME, 0, 0, 0)) {
perror("PTRACE_TRACEME");
return TEST_ABORTED;
}
raise(SIGSTOP);

gettimeofday(&tv, NULL);

puts("\nOK");
return TEST_OK;
}

int ptrace_wait(pid_t pid, char *who)
{
int status;

if (waitpid(pid, &status, WUNTRACED) == -1) {
perror("waitpid for ptrace");
return TEST_ABORTED;
}

if (WIFEXITED(status)) {
printf("%s terminated with code %d\n",
who, WEXITSTATUS(status));
return WEXITSTATUS(status);
} else if (WIFSIGNALED(status)) {
printf("%s got signal %d\n", who, WTERMSIG(status));
return TEST_ABORTED;
} else if (!WIFSTOPPED(status)) {
printf("%s died with status %d\n", who, status);
return TEST_ABORTED;
}

return TEST_OK;
}

int run_parent()
{
pid_t childpid;
int ret;

if (!(childpid = fork()))
return run_child();

printf("Started child %d\n", childpid);

if ((ret = ptrace_wait(childpid, "Child")))
return ret;

printf("Attached process %d\n", childpid);

if (ptrace(PTRACE_SYSCALL, childpid, 0, SIGCONT)) {
perror("PTRACE_SYSCALL child");
return TEST_ABORTED;
}

/* The rest of this function is artificial, but I
* could achieve similar effects by sending a SIGKILL
* to strace of a heavily multithreaded application.
*/

/* Give the child some time to get into TASK_TRACED */
sleep(1);
/* do not detach from child and exit */
return TEST_OK;
}

#define BUFSIZE 512

int copy_stdout(int fd)
{
FILE *f;
static char line[BUFSIZE];
int ret = TEST_FAILED;

if (! (f = fdopen(fd, "r"))) {
perror("fdopen");
return TEST_ABORTED;
}

while (fgets(line, BUFSIZE, f)) {
if (!strcmp(line, "OK\n"))
ret = TEST_OK;
fputs(line, stdout);
}

return ret;
}

int run_checker(pid_t pid, int fd)
{
int status;
int ret;

ret = copy_stdout(fd);

/* Wait for the parent process to terminate */
if (waitpid(pid, &status, 0) == -1) {
perror("waitpid parent");
return TEST_ABORTED;
}

if (WIFEXITED(status)) {
int exitcode = WEXITSTATUS(status);

printf("Parent terminated with exit code %d\n", exitcode);
if (exitcode == TEST_OK && ret == TEST_FAILED)
printf("Child died unexpectedly\n");
if (ret < exitcode)
ret = exitcode;
} else {
if (WIFSIGNALED(status))
printf("Parent got signal %d\n", WTERMSIG(status));
else
printf("Parent died with status %d\n", status);
if (ret < TEST_ABORTED)
ret = TEST_ABORTED;
}

return ret;
}

int main()
{
pid_t pid;
int fds[2];

if (pipe(fds)) {
perror("pipe");
return TEST_ABORTED;
}

if ((pid = fork())) {
/* If closing the write side of the pipe fails,
* copy_stdout() will never reach EOF, so this
* is fairly important.
*/
if (close(fds[1])) {
perror("close");
return TEST_ABORTED;
}

return run_checker(pid, fds[0]);
} else {
if (dup2(fds[1], 1) == -1) {
perror("dup2");
return TEST_ABORTED;
}
setlinebuf(stdout);

/* I don't care if these closes fail... */
close(fds[0]);
close(fds[1]);

return run_parent();
}
}
\
 
 \ /
  Last update: 2007-12-05 17:57    [W:0.056 / U:0.072 seconds]
©2003-2020 Jasper Spaans|hosted at Digital Ocean and TransIP|Read the blog|Advertise on this site