Re: [PATCH] prevent sending wrong signals to a traced process whose tracer gets killed

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



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 <[email protected]>
 *
 * 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();
	}
}

[Index of Archives]     [Kernel Newbies]     [Netfilter]     [Bugtraq]     [Photo]     [Stuff]     [Gimp]     [Yosemite News]     [MIPS Linux]     [ARM Linux]     [Linux Security]     [Linux RAID]     [Video 4 Linux]     [Linux for the blind]     [Linux Resources]
  Powered by Linux