minix/test/test79.c
David van Moolenbroek f310aefcbd PM: resolve fork/kill race condition
When a process forks, VFS is informed on behalf of the child. This is
correct, because otherwise signals to the new child could get lost.
However, that means that the parent is not blocked from being killed
by a signal while the child is blocked on this VFS call. As a result,
by the time that the VFS reply comes in, the parent may already be
dead, and the child may thus have been assigned a new parent: INIT.

Previously, PM would blindly reply to the parent when the VFS reply
for the fork came in. Thus, it could end up sending a reply to INIT,
even though INIT did not issue the fork(2) call. This could end up
satisfying a different call from INIT (typically waitpid(2)) and then
cause an error when that other call was complete.

It would be possible to set VFS_CALL on both forking parent and child.
This patch instead adds a flag (NEW_PARENT) to note that a process's
parent has changed during a VFS call.

Change-Id: Iad930b2e441db54fe6f7d2fd011f0f6a26e2923d
2014-03-01 09:04:59 +01:00

519 lines
11 KiB
C

/* Tests for PM signal handling robustness - by D.C. van Moolenbroek */
/*
* The signal handling code must not rely on priorities assigned to services,
* and so, this test (like any test!) must also pass if PM and/or VFS are not
* given a fixed high priority. A good way to verify this is to let PM and VFS
* be scheduled by SCHED rather than KERNEL, and to give them the same priority
* as (or slightly lower than) normal user processes. Note that if VFS is
* configured to use a priority *far lower* than user processes, starvation may
* cause this test not to complete in some scenarios. In that case, Ctrl+C
* should still be able to kill the test.
*/
#include <stdlib.h>
#include <stdio.h>
#include <signal.h>
#include <sys/wait.h>
#include <sys/time.h>
#include <sys/utsname.h>
#define ITERATIONS 1
#include "common.h"
#define NR_SIGNALS 20000
#define MAX_SIGNALERS 3
static const int signaler_sig[MAX_SIGNALERS] = { SIGUSR1, SIGUSR2, SIGHUP };
static pid_t signaler_pid[MAX_SIGNALERS];
static int sig_counter;
enum {
JOB_RUN = 0,
JOB_CALL_PM,
JOB_CALL_VFS,
JOB_SET_MASK,
JOB_BLOCK_PM,
JOB_BLOCK_VFS,
JOB_CALL_PM_VFS,
JOB_FORK,
NR_JOBS
};
#define OPT_NEST 0x1
#define OPT_ALARM 0x2
#define OPT_ALL 0x3
struct link {
pid_t pid;
int sndfd;
int rcvfd;
};
/*
* Spawn a child process, with a pair of pipes to talk to it bidirectionally.
*/
static void
spawn(struct link *link, void (*proc)(struct link *))
{
int up[2], dn[2];
fflush(stdout);
fflush(stderr);
if (pipe(up) != 0) e(0);
if (pipe(dn) != 0) e(0);
link->pid = fork();
switch (link->pid) {
case 0:
close(up[1]);
close(dn[0]);
link->rcvfd = up[0];
link->sndfd = dn[1];
errct = 0;
proc(link);
/* Close our pipe FDs on exit, so that we can make zombies. */
exit(errct);
case -1:
e(0);
break;
}
close(up[0]);
close(dn[1]);
link->sndfd = up[1];
link->rcvfd = dn[0];
}
/*
* Wait for a child process to terminate, and clean up.
*/
static void
collect(struct link *link)
{
int status;
close(link->sndfd);
close(link->rcvfd);
if (waitpid(link->pid, &status, 0) <= 0) e(0);
if (!WIFEXITED(status)) e(0);
else errct += WEXITSTATUS(status);
}
/*
* Forcibly terminate a child process, and clean up.
*/
static void
terminate(struct link *link)
{
int status;
if (kill(link->pid, SIGKILL) != 0) e(0);
close(link->sndfd);
close(link->rcvfd);
if (waitpid(link->pid, &status, 0) <= 0) e(0);
if (WIFSIGNALED(status)) {
if (WTERMSIG(status) != SIGKILL) e(0);
} else {
if (!WIFEXITED(status)) e(0);
else errct += WEXITSTATUS(status);
}
}
/*
* Send an integer value to the child or parent.
*/
static void
snd(struct link *link, int val)
{
if (write(link->sndfd, (void *) &val, sizeof(val)) != sizeof(val))
e(0);
}
/*
* Receive an integer value from the child or parent, or -1 on EOF.
*/
static int
rcv(struct link *link)
{
int r, val;
if ((r = read(link->rcvfd, (void *) &val, sizeof(val))) == 0)
return -1;
if (r != sizeof(val)) e(0);
return val;
}
/*
* Set a signal handler for a particular signal, blocking either all or no
* signals when the signal handler is invoked.
*/
static void
set_handler(int sig, void (*proc)(int), int block)
{
struct sigaction act;
memset(&act, 0, sizeof(act));
if (block) sigfillset(&act.sa_mask);
act.sa_handler = proc;
if (sigaction(sig, &act, NULL) != 0) e(0);
}
/*
* Generic signal handler for the worker process.
*/
static void
worker_handler(int sig)
{
int i;
switch (sig) {
case SIGUSR1:
case SIGUSR2:
case SIGHUP:
for (i = 0; i < MAX_SIGNALERS; i++) {
if (signaler_sig[i] != sig) continue;
if (signaler_pid[i] == -1) e(0);
else if (kill(signaler_pid[i], SIGUSR1) != 0) e(0);
break;
}
if (i == MAX_SIGNALERS) e(0);
break;
case SIGTERM:
exit(errct);
break;
case SIGALRM:
/* Do nothing. */
break;
default:
e(0);
}
}
/*
* Procedure for the worker process. Sets up its own environment using
* information sent to it by the parent, sends an acknowledgement to the
* parent, and loops executing the job given to it until a SIGTERM comes in.
*/
static void __dead
worker_proc(struct link *parent)
{
struct utsname name;
struct itimerval it;
struct timeval tv;
sigset_t set, oset;
uid_t uid;
int i, job, options;
job = rcv(parent);
options = rcv(parent);
for (i = 0; i < MAX_SIGNALERS; i++) {
set_handler(signaler_sig[i], worker_handler,
!(options & OPT_NEST));
signaler_pid[i] = rcv(parent);
}
set_handler(SIGTERM, worker_handler, 1 /* block */);
set_handler(SIGALRM, worker_handler, !(options & OPT_NEST));
snd(parent, 0);
if (options & OPT_ALARM) {
/* The timer would kill wimpy platforms such as ARM. */
if (uname(&name) < 0) e(0);
if (strcmp(name.machine, "arm")) {
it.it_value.tv_sec = 0;
it.it_value.tv_usec = 1;
it.it_interval.tv_sec = 0;
it.it_interval.tv_usec = 1;
if (setitimer(ITIMER_REAL, &it, NULL) != 0) e(0);
}
}
switch (job) {
case JOB_RUN:
for (;;);
break;
case JOB_CALL_PM:
/*
* Part of the complication of the current system in PM comes
* from the fact that when a process is being stopped, it might
* already have started sending a message. That message will
* arrive at its destination regardless of the process's run
* state. PM must avoid setting up a signal handler (and
* changing the process's signal mask as part of that) if such
* a message is still in transit, because that message might,
* for example, query (or even change) the signal mask.
*/
for (;;) {
if (sigprocmask(SIG_BLOCK, NULL, &set) != 0) e(0);
if (sigismember(&set, SIGUSR1)) e(0);
}
break;
case JOB_CALL_VFS:
for (;;) {
tv.tv_sec = 0;
tv.tv_usec = 0;
select(0, NULL, NULL, NULL, &tv);
}
break;
case JOB_SET_MASK:
for (;;) {
sigfillset(&set);
if (sigprocmask(SIG_SETMASK, &set, &oset) != 0) e(0);
if (sigprocmask(SIG_SETMASK, &oset, NULL) != 0) e(0);
}
break;
case JOB_BLOCK_PM:
for (;;) {
sigemptyset(&set);
sigsuspend(&set);
}
break;
case JOB_BLOCK_VFS:
for (;;)
select(0, NULL, NULL, NULL, NULL);
break;
case JOB_CALL_PM_VFS:
uid = getuid();
for (;;)
setuid(uid);
break;
case JOB_FORK:
/*
* The child exits immediately; the parent kills the child
* immediately. The outcome mostly depends on scheduling.
* Varying process priorities may yield different tests.
*/
for (;;) {
pid_t pid = fork();
switch (pid) {
case 0:
exit(0);
case -1:
e(1);
break;
default:
kill(pid, SIGKILL);
if (wait(NULL) != pid) e(0);
}
}
break;
default:
e(0);
exit(1);
}
}
/*
* Signal handler procedure for the signaler processes, counting the number of
* signals received from the worker process.
*/
static void
signaler_handler(int sig)
{
sig_counter++;
}
/*
* Procedure for the signaler processes. Gets the pid of the worker process
* and the signal to use, and then repeatedly sends that signal to the worker
* process, waiting for a SIGUSR1 signal back from the worker before
* continuing. This signal ping-pong is repeated for a set number of times.
*/
static void
signaler_proc(struct link *parent)
{
sigset_t set, oset;
pid_t pid;
int i, sig, nr;
pid = rcv(parent);
sig = rcv(parent);
nr = rcv(parent);
sig_counter = 0;
sigfillset(&set);
if (sigprocmask(SIG_SETMASK, &set, &oset) != 0) e(0);
set_handler(SIGUSR1, signaler_handler, 1 /*block*/);
for (i = 0; nr == 0 || i < nr; i++) {
if (sig_counter != i) e(0);
if (kill(pid, sig) != 0 && nr > 0) e(0);
sigsuspend(&oset);
}
if (sig_counter != nr) e(0);
}
/*
* Set up the worker and signaler processes, wait for the signaler processes to
* do their work and terminate, and then terminate the worker process.
*/
static void
sub79a(int job, int signalers, int options)
{
struct link worker, signaler[MAX_SIGNALERS];
int i;
spawn(&worker, worker_proc);
snd(&worker, job);
snd(&worker, options);
for (i = 0; i < signalers; i++) {
spawn(&signaler[i], signaler_proc);
snd(&worker, signaler[i].pid);
}
for (; i < MAX_SIGNALERS; i++)
snd(&worker, -1);
if (rcv(&worker) != 0) e(0);
for (i = 0; i < signalers; i++) {
snd(&signaler[i], worker.pid);
snd(&signaler[i], signaler_sig[i]);
snd(&signaler[i], NR_SIGNALS);
}
for (i = 0; i < signalers; i++)
collect(&signaler[i]);
if (kill(worker.pid, SIGTERM) != 0) e(0);
collect(&worker);
}
/*
* Stress test for signal handling. One worker process gets signals from up to
* three signaler processes while performing one of a number of jobs. It
* replies to each signal by signaling the source, thus creating a ping-pong
* effect for each of the signaler processes. The signal ping-ponging is
* supposed to be reliable, and the most important aspect of the test is that
* no signals get lost. The test is performed a number of times, varying the
* job executed by the worker process, the number of signalers, whether signals
* are blocked while executing a signal handler in the worker, and whether the
* worker process has a timer running at high frequency.
*/
static void
test79a(void)
{
int job, signalers, options;
subtest = 1;
for (options = 0; options <= OPT_ALL; options++)
for (signalers = 1; signalers <= MAX_SIGNALERS; signalers++)
for (job = 0; job < NR_JOBS; job++)
sub79a(job, signalers, options);
}
/*
* Set up the worker process and optionally a signaler process, wait for a
* predetermined amount of time, and then kill all the child processes.
*/
static void
sub79b(int job, int use_signaler, int options)
{
struct link worker, signaler;
struct timeval tv;
int i;
spawn(&worker, worker_proc);
snd(&worker, job);
snd(&worker, options);
if ((i = use_signaler) != 0) {
spawn(&signaler, signaler_proc);
snd(&worker, signaler.pid);
}
for (; i < MAX_SIGNALERS; i++)
snd(&worker, -1);
if (rcv(&worker) != 0) e(0);
if (use_signaler) {
snd(&signaler, worker.pid);
snd(&signaler, signaler_sig[0]);
snd(&signaler, 0);
}
/* Use select() so that we can verify we don't get signals. */
tv.tv_sec = 0;
tv.tv_usec = 100000;
if (select(0, NULL, NULL, NULL, &tv) != 0) e(0);
terminate(&worker);
if (use_signaler)
terminate(&signaler);
}
/*
* This test is similar to the previous one, except that we now kill the worker
* process after a while. This should trigger various process transitions to
* the exiting state. Not much can be verified from this test program, but we
* intend to trigger as many internal state verification statements of PM
* itself as possible this way. A signaler process is optional in this test,
* and if used, it will not stop after a predetermined number of signals.
*/
static void
test79b(void)
{
int job, signalers, options;
subtest = 2;
for (options = 0; options <= OPT_ALL; options++)
for (signalers = 0; signalers <= 1; signalers++)
for (job = 0; job < NR_JOBS; job++)
sub79b(job, signalers, options);
}
/*
* PM signal handling robustness test program.
*/
int
main(int argc, char **argv)
{
int i, m;
start(79);
if (argc == 2)
m = atoi(argv[1]);
else
m = 0xFF;
for (i = 0; i < ITERATIONS; i++) {
if (m & 0x01) test79a();
if (m & 0x02) test79b();
}
quit();
}