NetBSD-Bugs archive

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

kern/51600: Tracer must detect zombie before child's parent



>Number:         51600
>Category:       kern
>Synopsis:       Tracer must detect zombie before child's parent
>Confidential:   no
>Severity:       serious
>Priority:       medium
>Responsible:    kern-bug-people
>State:          open
>Class:          sw-bug
>Submitter-Id:   net
>Arrival-Date:   Sat Nov 05 01:30:01 +0000 2016
>Originator:     Kamil Rytarowski
>Release:        NetBSD 7.99.42 amd64
>Organization:
TNF
>Environment:
NetBSD chieftec 7.99.42 NetBSD 7.99.42 (GENERIC) #0: Fri Nov  4 20:52:34 CET 2016  kamil@chieftec:/tmp/netbsd-tmp/sys/arch/amd64/compile/GENERIC amd64
>Description:
Debugger must detect that a child has been terminated before the child's parent.

This is expected behavior found on Linux and FreeBSD.
>How-To-Repeat:
/* WARNING kernel newer than 2016-11-05 is needed in order to no panic the system */


#include <sys/param.h>
#include <sys/types.h>
#include <sys/ptrace.h>
#include <sys/sysctl.h>
#include <sys/wait.h>
#include <assert.h>
#include <err.h>
#include <errno.h>
#include <signal.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

#define ATF_REQUIRE(a) assert(a)

/*
 * A child process cannot call atf functions and expect them to magically
 * work like in the parent.
 * The printf(3) messaging from a child will not work out of the box as well
 * without estabilishing a communication protocol with its parent. To not
 * overcomplicate the tests - do not log from a child and use err(3)/errx(3)
 * wrapped with FORKEE_ASSERT()/FORKEE_ASSERTX() as that is guaranteed to work.
 */
#define FORKEE_ASSERTX(x)							\
do {										\
	int ret = (x);								\
	if (!ret)								\
		errx(EXIT_FAILURE, "%s:%d %s(): Assertion failed for: %s",	\
		     __FILE__, __LINE__, __func__, #x);				\
} while (0)

#define FORKEE_ASSERT(x)							\
do {										\
	int ret = (x);								\
	if (!ret)								\
		err(EXIT_FAILURE, "%s:%d %s(): Assertion failed for: %s",	\
		     __FILE__, __LINE__, __func__, #x);				\
} while (0)

/* This function is currently designed to be run in the main/parent process */
static void
await_zombie(pid_t process)
{
#if defined(__NetBSD__)
	struct kinfo_proc2 p;
	size_t len = sizeof(p);

	const int name[] = {
		[0] = CTL_KERN,
		[1] = KERN_PROC2,
		[2] = KERN_PROC_PID,
		[3] = process,
		[4] = sizeof(p),
		[5] = 1
	};

	const size_t namelen = __arraycount(name);

	/* Await the process becoming a zombie */
	while(1) {
		ATF_REQUIRE(sysctl(name, namelen, &p, &len, NULL, 0) == 0);

		if (p.p_stat == LSZOMB)
			break;

		ATF_REQUIRE(usleep(1000) == 0);
	}
#elif defined(__FreeBSD__)
	struct kinfo_proc p;
	size_t len = sizeof(p);

	const int name[] = {
		[0] = CTL_KERN,
		[1] = KERN_PROC,
		[2] = KERN_PROC_PID,
		[3] = process
	};

	const size_t namelen = __arraycount(name);

	/* Await the process becoming a zombie */
	while(1) {
		if(sysctl(name, namelen, &p, &len, NULL, 0) == -1) {
			if (errno == ESRCH)
				break;
			err(EXIT_FAILURE, "sysctl");
		}

		ATF_REQUIRE(usleep(1000) == 0);
	}
#elif defined(linux)
	int iszombie = 0;
	char pbuf[60];
	snprintf(pbuf, sizeof(pbuf), "/proc/%d/stat", (int)process);

	/* Detect that tracee is zombie */
	while(1) {
		FILE* fpstat = fopen(pbuf, "r");
		if (!fpstat) {
			err(EXIT_FAILURE, "fopen");
		};
		int rpid =0; char rcmd[32]; char rstatc = 0;
		fscanf(fpstat, "%d %30s %c", &rpid, rcmd, &rstatc);
		iszombie = rstatc == 'Z';

		fclose(fpstat);
		if (iszombie == 1) {
			break;
		}

		usleep(1000);
	}
#endif
}

int
main(int argc, char **argv)
{
	int fds_totracee[2], fds_totracer[2], fds_fromtracer[2];
	int status, rv;
	const int exitval_tracee = 5;
	const int exitval_tracer = 10;
	pid_t tracee, tracer, wpid;
	uint8_t msg = 0xde; /* dummy message for IPC based on pipe(2) */

	printf("Spawn tracee\n");
	ATF_REQUIRE(pipe(fds_totracee) == 0);
	ATF_REQUIRE((tracee = fork()) != -1);
	if (tracee == 0) {
		FORKEE_ASSERT(close(fds_totracee[1]) == 0);

		/* Wait for message from the parent */
		rv = read(fds_totracee[0], &msg, sizeof(msg));
		FORKEE_ASSERT(rv == sizeof(msg));

		_exit(exitval_tracee);
	}
	ATF_REQUIRE(close(fds_totracee[0]) == 0);

	printf("Spawn debugger\n");
	ATF_REQUIRE(pipe(fds_totracer) == 0);
	ATF_REQUIRE(pipe(fds_fromtracer) == 0);
	ATF_REQUIRE((tracer = fork()) != -1);
	if (tracer == 0) {
		/* No IPC to communicate with the child */
		FORKEE_ASSERT(close(fds_totracee[1]) == 0);

		FORKEE_ASSERT(close(fds_totracer[1]) == 0);
		FORKEE_ASSERT(close(fds_fromtracer[0]) == 0);

		FORKEE_ASSERT(ptrace(PT_ATTACH, tracee, NULL, 0) != -1);

		/* Wait for tracee and assert that it was stopped w/ SIGSTOP */
		wpid = waitpid(tracee, &status, 0);
		FORKEE_ASSERTX(wpid == tracee);
		FORKEE_ASSERTX(!WIFEXITED(status));
		FORKEE_ASSERTX(!WIFCONTINUED(status));
		FORKEE_ASSERTX(!WIFSIGNALED(status));
		FORKEE_ASSERTX(WIFSTOPPED(status));
		FORKEE_ASSERTX(WSTOPSIG(status) == SIGSTOP);

		/* Resume tracee with PT_CONTINUE */
		FORKEE_ASSERT(ptrace(PT_CONTINUE, tracee, (void *)1, 0) != -1);

		/* Inform parent that tracer has attached to tracee */
		rv = write(fds_fromtracer[1], &msg, sizeof(msg));
		FORKEE_ASSERT(rv == sizeof(msg));

		/* Wait for parent */
		rv = read(fds_totracer[0], &msg, sizeof(msg));
		FORKEE_ASSERT(rv == sizeof(msg));

		/* Wait for tracee and assert that it exited */
		wpid = waitpid(tracee, &status, 0);
		FORKEE_ASSERTX(wpid == tracee);
		FORKEE_ASSERTX(WIFEXITED(status));
		FORKEE_ASSERTX(!WIFCONTINUED(status));
		FORKEE_ASSERTX(!WIFSIGNALED(status));
		FORKEE_ASSERTX(!WIFSTOPPED(status));
		FORKEE_ASSERTX(WEXITSTATUS(status) == exitval_tracee);

		_exit(exitval_tracer);
	}
	ATF_REQUIRE(close(fds_totracer[0]) == 0);
	ATF_REQUIRE(close(fds_fromtracer[1]) == 0);

	printf("Wait for the tracer to attach to the tracee\n");
	rv = read(fds_fromtracer[0], &msg, sizeof(msg));
	ATF_REQUIRE(rv == sizeof(msg));

	printf("Resume the tracee and let it exit\n");
	rv = write(fds_totracee[1], &msg, sizeof(msg));
	ATF_REQUIRE(rv == sizeof(msg));

	printf("fds_totracee is no longer needed - close it\n");
	ATF_REQUIRE(close(fds_totracee[1]) == 0);

	printf("Detect that tracee is zombie\n");
	await_zombie(tracee);

	printf("Assert that there is no status about tracee - "
	    "Tracer must detect zombie first\n");
	wpid = waitpid(tracee, &status, WNOHANG);
	ATF_REQUIRE(wpid == 0);

	printf("Resume the tracer and let it detect exited tracee\n");
	rv = write(fds_totracer[1], &msg, sizeof(msg));
	ATF_REQUIRE(rv == sizeof(msg));

	printf("Wait for tracer to finish its job and exit\n");
	wpid = waitpid(tracer, &status, 0);
	ATF_REQUIRE(wpid == tracer);
	ATF_REQUIRE(WIFEXITED(status));
	ATF_REQUIRE(!WIFCONTINUED(status));
	ATF_REQUIRE(!WIFSIGNALED(status));
	ATF_REQUIRE(!WIFSTOPPED(status));
	ATF_REQUIRE(WEXITSTATUS(status) == exitval_tracer);

	printf("Wait for tracer to finish its job and exit\n");
	wpid = waitpid(tracee, &status, WNOHANG);
	ATF_REQUIRE(wpid == tracee);
	ATF_REQUIRE(WIFEXITED(status));
	ATF_REQUIRE(!WIFCONTINUED(status));
	ATF_REQUIRE(!WIFSIGNALED(status));
	ATF_REQUIRE(!WIFSTOPPED(status));
	ATF_REQUIRE(WEXITSTATUS(status) == exitval_tracee);

	printf("fds_fromtracer is no longer needed - close it\n");
	ATF_REQUIRE(close(fds_fromtracer[0]) == 0);

	printf("fds_totracer is no longer needed - close it\n");
	ATF_REQUIRE(close(fds_totracer[1]) == 0);

	return EXIT_SUCCESS;
}
>Fix:
N/A



Home | Main Index | Thread Index | Old Index