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