From 9fd617f8105fc232a908714df0f5a06f407e0455 Mon Sep 17 00:00:00 2001 From: Alexander Bluhm Date: Wed, 28 Apr 2021 17:59:54 +0000 Subject: To hunt kernel bugs during exit, terminate processes simultaneously. Fork 300 children that sleep. Kill them together as process group. Sleeping can optionally be done with individual memory layout by executing sleep(1). --- regress/sys/kern/Makefile | 4 +- regress/sys/kern/fork-exit/Makefile | 40 ++++++ regress/sys/kern/fork-exit/fork-exit.c | 216 +++++++++++++++++++++++++++++++++ 3 files changed, 258 insertions(+), 2 deletions(-) create mode 100644 regress/sys/kern/fork-exit/Makefile create mode 100644 regress/sys/kern/fork-exit/fork-exit.c (limited to 'regress/sys/kern') diff --git a/regress/sys/kern/Makefile b/regress/sys/kern/Makefile index f0114cc48ae..0a64a811317 100644 --- a/regress/sys/kern/Makefile +++ b/regress/sys/kern/Makefile @@ -1,4 +1,4 @@ -# $OpenBSD: Makefile,v 1.88 2020/11/10 16:13:35 bluhm Exp $ +# $OpenBSD: Makefile,v 1.89 2021/04/28 17:59:53 bluhm Exp $ SUBDIR+= __syscall SUBDIR+= accept access @@ -6,7 +6,7 @@ SUBDIR+= bind SUBDIR+= clock_gettime cmsgsize SUBDIR+= descrip dup2 dup2_accept dup2_self SUBDIR+= exec_self execve exit extent -SUBDIR+= fchdir fchown fcntl_dup flock ftruncate futex +SUBDIR+= fchdir fchown fcntl_dup flock fork-exit ftruncate futex SUBDIR+= getpeereid getrusage gettimeofday SUBDIR+= itimer SUBDIR+= kqueue diff --git a/regress/sys/kern/fork-exit/Makefile b/regress/sys/kern/fork-exit/Makefile new file mode 100644 index 00000000000..5104429ffdd --- /dev/null +++ b/regress/sys/kern/fork-exit/Makefile @@ -0,0 +1,40 @@ +# $OpenBSD: Makefile,v 1.1 2021/04/28 17:59:53 bluhm Exp $ + +# Copyright (c) 2021 Alexander Bluhm +# +# Permission to use, copy, modify, and distribute this software for any +# purpose with or without fee is hereby granted, provided that the above +# copyright notice and this permission notice appear in all copies. +# +# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +# OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + +# To hunt kernel bugs during exit, terminate processes simultaneously. +# Fork 300 children that sleep. Kill them together as process group. +# Sleeping can optionally be done with individual memory layout by +# executing sleep(1). + +PROG= fork-exit +WARNINGS= yes + +REGRESS_TARGETS += run-fork1-exit +run-fork1-exit: + # test forking a single child + ./fork-exit + +REGRESS_TARGETS += run-fork-exit +run-fork-exit: + # fork 300 children and kill them simultaneously as process group + ulimit -p 500 -n 1000; ./fork-exit -p 300 + +REGRESS_TARGETS += run-fork-exec-exit +run-fork-exec-exit: + # fork 300 children, exec sleep programs, and kill process group + ulimit -p 500 -n 1000; ./fork-exit -e -p 300 + +.include diff --git a/regress/sys/kern/fork-exit/fork-exit.c b/regress/sys/kern/fork-exit/fork-exit.c new file mode 100644 index 00000000000..b66e4a7dc34 --- /dev/null +++ b/regress/sys/kern/fork-exit/fork-exit.c @@ -0,0 +1,216 @@ +/* $OpenBSD: fork-exit.c,v 1.1 2021/04/28 17:59:53 bluhm Exp $ */ + +/* + * Copyright (c) 2021 Alexander Bluhm + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +int execute = 0; +int daemonize = 0; +int procs = 1; +int timeout = 30; + +static void __dead +usage(void) +{ + fprintf(stderr, "fork-exit [-ed] [-p procs] [-t timeout]\n" + " -e child execs sleep(1), default call sleep(3)\n" + " -d daemonize, if already process group leader\n" + " -p procs number of processes to fork, default 1\n" + " -t timeout parent and children will exit, default 30 sec\n"); + exit(2); +} + +static void __dead +exec_sleep(void) +{ + execl("/bin/sleep", "sleep", "30", NULL); + err(1, "exec sleep"); +} + +static void +fork_sleep(int fd) +{ + switch (fork()) { + case -1: + err(1, "fork"); + case 0: + break; + default: + return; + } + /* close pipe to parent and sleep until killed */ + if (execute) { + if (fcntl(fd, F_SETFD, FD_CLOEXEC)) + err(1, "fcntl FD_CLOEXEC"); + exec_sleep(); + } else { + if (close(fd) == -1) + err(1, "close write"); + if (sleep(timeout) != 0) + err(1, "sleep %d", timeout); + } + _exit(0); +} + +static void +sigexit(int sig) +{ + int i, status; + pid_t pid; + + /* all children must terminate in time */ + if ((int)alarm(timeout) == -1) + err(1, "alarm"); + + for (i = 0; i < procs; i++) { + pid = wait(&status); + if (pid == -1) + err(1, "wait"); + if (!WIFSIGNALED(status)) + errx(1, "child %d not killed", pid); + if(WTERMSIG(status) != SIGTERM) + errx(1, "child %d signal %d", pid, WTERMSIG(status)); + } + exit(0); +} + +int +main(int argc, char *argv[]) +{ + const char *errstr; + int ch, i, fdmax, fdlen, *rfds, waiting; + fd_set *fdset; + pid_t pgrp; + struct timeval tv; + + while ((ch = getopt(argc, argv, "edp:t:")) != -1) { + switch (ch) { + case 'e': + execute = 1; + break; + case 'd': + daemonize = 1; + break; + case 'p': + procs = strtonum(optarg, 0, INT_MAX, &errstr); + if (errstr != NULL) + errx(1, "number of procs is %s: %s", errstr, + optarg); + break; + case 't': + timeout = strtonum(optarg, 0, INT_MAX, &errstr); + if (errstr != NULL) + errx(1, "timeout is %s: %s", errstr, optarg); + default: + usage(); + } + } + + /* become process group leader */ + pgrp = setsid(); + if (pgrp == -1) { + if (errno == EPERM && daemonize) { + /* get rid of leadership */ + switch (fork()) { + case -1: + err(1, "fork parent"); + case 0: + /* try again */ + pgrp = setsid(); + break; + default: + _exit(0); + } + } + if (!daemonize) + warnx("try -d to become process group leader"); + if (pgrp == -1) + err(1, "setsid"); + } + + /* create pipes to keep in contact with children */ + rfds = reallocarray(NULL, procs, sizeof(int)); + if (rfds == NULL) + err(1, "rfds"); + fdmax = 0; + + /* fork child processes and pass writing end of pipe */ + for (i = 0; i < procs; i++) { + int pipefds[2]; + + if (pipe(pipefds) == -1) + err(1, "pipe"); + if (fdmax < pipefds[0]) + fdmax = pipefds[0]; + rfds[i] = pipefds[0]; + fork_sleep(pipefds[1]); + if (close(pipefds[1]) == -1) + err(1, "close parent"); + } + + /* create select mask with all reading ends of child pipes */ + fdlen = howmany(fdmax + 1, NFDBITS); + fdset = calloc(fdlen, sizeof(fd_mask)); + if (fdset == NULL) + err(1, "fdset"); + for (i = 0; i < procs; i++) { + FD_SET(rfds[i], fdset); + } + + /* wait until all child processes are waiting */ + do { + waiting = 0; + tv.tv_sec = timeout; + tv.tv_usec = 0; + errno = ETIMEDOUT; + if (select(fdmax + 1, fdset, NULL, NULL, &tv) <= 0) + err(1, "select"); + + /* remove fd of children that closed their end */ + for (i = 0; i < procs; i++) { + if (rfds[i] >= 0) { + if (FD_ISSET(rfds[i], fdset)) { + if (close(rfds[i]) == -1) + err(1, "close read"); + FD_CLR(rfds[i], fdset); + rfds[i] = -1; + } else { + FD_SET(rfds[i], fdset); + waiting = 1; + } + } + } + } while (waiting); + + /* kill all children simultaneously, parent exits in signal handler */ + if (signal(SIGTERM, sigexit) == SIG_ERR) + err(1, "signal SIGTERM"); + if (kill(-pgrp, SIGTERM) == -1) + err(1, "kill %d", -pgrp); + + errx(1, "alive"); +} -- cgit v1.2.3