/* * Copyright (c) 2001 Markus Friedl. All rights reserved. * Copyright (c) 2021 Darren Tucker (dtucker at dtucker net). * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #include "includes.h" #ifndef HAVE_PSELECT #include #include #ifdef HAVE_SYS_SELECT_H # include #endif #include #include #include #include #include #include #include "log.h" #include "misc.h" /* for set_nonblock */ #ifndef HAVE_SIGHANDLER_T typedef void (*sighandler_t)(int); #endif static sighandler_t saved_sighandler[_NSIG]; /* * Set up the descriptors. Because they are close-on-exec, in the case * where sshd's re-exec fails notify_pipe will still point to a descriptor * that was closed by the exec attempt but if that descriptor has been * reopened then we'll attempt to use that. Ensure that notify_pipe is * outside of the range used by sshd re-exec but within NFDBITS (so we don't * need to expand the fd_sets). */ #define REEXEC_MIN_FREE_FD (STDERR_FILENO + 4) static int pselect_notify_setup_fd(int *fd) { int r; if ((r = fcntl(*fd, F_DUPFD, REEXEC_MIN_FREE_FD)) < 0 || fcntl(r, F_SETFD, FD_CLOEXEC) < 0 || r >= FD_SETSIZE) return -1; (void)close(*fd); return (*fd = r); } /* * we write to this pipe if a SIGCHLD is caught in order to avoid * the race between select() and child_terminated */ static pid_t notify_pid; static int notify_pipe[2]; static void pselect_notify_setup(void) { static int initialized; if (initialized && notify_pid == getpid()) return; if (notify_pid == 0) debug3_f("initializing"); else { debug3_f("pid changed, reinitializing"); if (notify_pipe[0] != -1) close(notify_pipe[0]); if (notify_pipe[1] != -1) close(notify_pipe[1]); } if (pipe(notify_pipe) == -1) { error("pipe(notify_pipe) failed %s", strerror(errno)); } else if (pselect_notify_setup_fd(¬ify_pipe[0]) == -1 || pselect_notify_setup_fd(¬ify_pipe[1]) == -1) { error("fcntl(notify_pipe, ...) failed %s", strerror(errno)); close(notify_pipe[0]); close(notify_pipe[1]); } else { set_nonblock(notify_pipe[0]); set_nonblock(notify_pipe[1]); notify_pid = getpid(); debug3_f("pid %d saved %d pipe0 %d pipe1 %d", getpid(), notify_pid, notify_pipe[0], notify_pipe[1]); initialized = 1; return; } notify_pipe[0] = -1; /* read end */ notify_pipe[1] = -1; /* write end */ } static void pselect_notify_parent(void) { if (notify_pipe[1] != -1) (void)write(notify_pipe[1], "", 1); } static void pselect_notify_prepare(fd_set *readset) { if (notify_pipe[0] != -1) FD_SET(notify_pipe[0], readset); } static void pselect_notify_done(fd_set *readset) { char c; if (notify_pipe[0] != -1 && FD_ISSET(notify_pipe[0], readset)) { while (read(notify_pipe[0], &c, 1) != -1) debug2_f("reading"); FD_CLR(notify_pipe[0], readset); } } /*ARGSUSED*/ static void pselect_sig_handler(int sig) { int save_errno = errno; pselect_notify_parent(); if (saved_sighandler[sig] != NULL) (*saved_sighandler[sig])(sig); /* call original handler */ errno = save_errno; } /* * A minimal implementation of pselect(2), built on top of select(2). */ int pselect(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, const struct timespec *timeout, const sigset_t *mask) { int ret, sig, saved_errno, unmasked = 0; sigset_t osig; struct sigaction sa, osa; struct timeval tv, *tvp = NULL; if (timeout != NULL) { tv.tv_sec = timeout->tv_sec; tv.tv_usec = timeout->tv_nsec / 1000; tvp = &tv; } if (mask == NULL) /* no signal mask, just call select */ return select(nfds, readfds, writefds, exceptfds, tvp); /* For each signal we're unmasking, install our handler if needed. */ for (sig = 0; sig < _NSIG; sig++) { if (sig == SIGKILL || sig == SIGSTOP || sigismember(mask, sig)) continue; if (sigaction(sig, NULL, &sa) == 0 && sa.sa_handler != SIG_IGN && sa.sa_handler != SIG_DFL) { unmasked = 1; if (sa.sa_handler == pselect_sig_handler) continue; sa.sa_handler = pselect_sig_handler; if (sigaction(sig, &sa, &osa) == 0) { debug3_f("installing signal handler for %s, " "previous %p", strsignal(sig), osa.sa_handler); saved_sighandler[sig] = osa.sa_handler; } } } if (unmasked) { pselect_notify_setup(); pselect_notify_prepare(readfds); nfds = MAX(nfds, notify_pipe[0] + 1); } /* Unmask signals, call select then restore signal mask. */ sigprocmask(SIG_SETMASK, mask, &osig); ret = select(nfds, readfds, writefds, exceptfds, tvp); saved_errno = errno; sigprocmask(SIG_SETMASK, &osig, NULL); if (unmasked) pselect_notify_done(readfds); errno = saved_errno; return ret; } #endif