From 2a558087750eaf39929684c5c427495629ae87e4 Mon Sep 17 00:00:00 2001 From: "Todd C. Miller" Date: Fri, 12 May 2017 10:02:17 -0600 Subject: [PATCH] Add support for signal events in sudo's event subsystem --- include/sudo_event.h | 25 +++- lib/util/event.c | 333 +++++++++++++++++++++++++++++++++++++++---- 2 files changed, 328 insertions(+), 30 deletions(-) diff --git a/include/sudo_event.h b/include/sudo_event.h index 21b62cdfd..ac368dd6e 100644 --- a/include/sudo_event.h +++ b/include/sudo_event.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 2013-2014 Todd C. Miller + * Copyright (c) 2013-2015, 2017 Todd C. Miller * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above @@ -17,6 +17,7 @@ #ifndef SUDO_EVENT_H #define SUDO_EVENT_H +#include /* for sigatomic_t and NSIG */ #include "sudo_queue.h" /* Event types */ @@ -24,6 +25,8 @@ #define SUDO_EV_READ 0x02 /* fire when readable */ #define SUDO_EV_WRITE 0x04 /* fire when writable */ #define SUDO_EV_PERSIST 0x08 /* persist until deleted */ +#define SUDO_EV_SIGNAL 0x10 /* fire on signal receipt */ +#define SUDO_EV_SIGINFO 0x20 /* fire on signal receipt (siginfo) */ /* Event flags (internal) */ #define SUDO_EVQ_INSERTED 0x01 /* event is on the event queue */ @@ -45,13 +48,22 @@ typedef void (*sudo_ev_callback_t)(int fd, int what, void *closure); +/* + * Container for SUDO_EV_SIGINFO events that gets passed as the closure + * pointer. This allows us to pass a siginfo_t without changing everything. + */ +struct sudo_ev_siginfo_container { + void *closure; + siginfo_t siginfo; +}; + /* Member of struct sudo_event_base. */ struct sudo_event { TAILQ_ENTRY(sudo_event) entries; TAILQ_ENTRY(sudo_event) active_entries; TAILQ_ENTRY(sudo_event) timeouts_entries; struct sudo_event_base *base; /* base this event belongs to */ - int fd; /* fd we are interested in */ + int fd; /* fd/signal we are interested in */ short events; /* SUDO_EV_* flags (in) */ short revents; /* SUDO_EV_* flags (out) */ short flags; /* internal event flags */ @@ -60,13 +72,20 @@ struct sudo_event { struct timeval timeout; /* for SUDO_EV_TIMEOUT */ void *closure; /* user-provided data pointer */ }; - TAILQ_HEAD(sudo_event_list, sudo_event); struct sudo_event_base { struct sudo_event_list events; /* tail queue of all events */ struct sudo_event_list active; /* tail queue of active events */ struct sudo_event_list timeouts; /* tail queue of timeout events */ + struct sudo_event signal_event; /* storage for signal pipe event */ + struct sudo_event_list signals[NSIG]; /* array of signal event tail queues */ + struct sigaction *orig_handlers[NSIG]; /* original signal handlers */ + siginfo_t *siginfo[NSIG]; /* detailed signal info */ + sig_atomic_t signal_pending[NSIG]; /* pending signals */ + sig_atomic_t signal_caught; /* at least one signal caught */ + int num_handlers; /* number of installed handlers */ + int signal_pipe[2]; /* so we can wake up on singal */ #ifdef HAVE_POLL struct pollfd *pfds; /* array of struct pollfd */ int pfd_max; /* size of the pfds array */ diff --git a/lib/util/event.c b/lib/util/event.c index 1667324d9..b95a70ab8 100644 --- a/lib/util/event.c +++ b/lib/util/event.c @@ -1,5 +1,5 @@ /* - * Copyright (c) 2013-2016 Todd C. Miller + * Copyright (c) 2013-2017 Todd C. Miller * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above @@ -31,8 +31,9 @@ #ifdef HAVE_STRINGS_H # include #endif /* HAVE_STRINGS_H */ -#include #include +#include +#include #include "sudo_compat.h" #include "sudo_fatal.h" @@ -40,8 +41,15 @@ #include "sudo_event.h" #include "sudo_util.h" +static void sudo_ev_init(struct sudo_event *ev, int fd, short events, + sudo_ev_callback_t callback, void *closure); + +/* We need the event base to be available from the signal handler. */ +static struct sudo_event_base *signal_base; + /* * Add an event to the base's active queue and mark it active. + * This is extern so sudo_ev_scan_impl() can call it. */ void sudo_ev_activate(struct sudo_event_base *base, struct sudo_event *ev) @@ -67,70 +75,190 @@ static void sudo_ev_deactivate_all(struct sudo_event_base *base) { struct sudo_event *ev; + debug_decl(sudo_ev_deactivate_all, SUDO_DEBUG_EVENT) while ((ev = TAILQ_FIRST(&base->active)) != NULL) sudo_ev_deactivate(base, ev); + + debug_return; +} + +/* + * Activate all signal events for which the corresponding signal_pending[] + * flag is set. + * Returns true if at least one sigevent has been activated. + */ +static bool +sudo_ev_activate_sigevents(struct sudo_event_base *base) +{ + struct sudo_event *ev; + sigset_t set, oset; + bool ret = false; + int i; + debug_decl(sudo_ev_activate_sigevents, SUDO_DEBUG_EVENT) + + if (base->signal_caught == 0) + debug_return_bool(false); + + /* + * We treat this as a critical section since the signal handler + * could modify the siginfo[] entry. + */ + sigfillset(&set); + sigprocmask(SIG_BLOCK, &set, &oset); + base->signal_caught = 0; + for (i = 0; i < NSIG; i++) { + if (!base->signal_pending[i]) + continue; + base->signal_pending[i] = 0; + TAILQ_FOREACH(ev, &base->signals[i], entries) { + if (ISSET(ev->events, SUDO_EV_SIGINFO)) { + struct sudo_ev_siginfo_container *sc = ev->closure; + memcpy(&sc->siginfo, base->siginfo[i], sizeof(sc->siginfo)); + } + /* Make event active. */ + ev->revents = ev->events & (SUDO_EV_SIGNAL|SUDO_EV_SIGINFO); + TAILQ_INSERT_TAIL(&base->active, ev, active_entries); + SET(ev->flags, SUDO_EVQ_ACTIVE); + ret = true; + } + } + sigprocmask(SIG_SETMASK, &oset, NULL); + + debug_return_bool(ret); +} + +/* + * Internal callback for SUDO_EV_SIGNAL and SUDO_EV_SIGINFO. + */ +static void +signal_pipe_cb(int fd, int what, void *v) +{ + unsigned char ch; + + /* + * Drain signal_pipe, the signal handler updated base->signals_pending. + * Actual processing of signal events is done when poll/select is + * interrupted by a signal. + */ + while (read(fd, &ch, 1) > 0) + continue; } struct sudo_event_base * sudo_ev_base_alloc_v1(void) { struct sudo_event_base *base; + int i; debug_decl(sudo_ev_base_alloc, SUDO_DEBUG_EVENT) base = calloc(1, sizeof(*base)); if (base == NULL) { sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_LINENO, "%s: unable to allocate base", __func__); - debug_return_ptr(NULL); + goto bad; } TAILQ_INIT(&base->events); TAILQ_INIT(&base->timeouts); + for (i = 0; i < NSIG; i++) + TAILQ_INIT(&base->signals[i]); if (sudo_ev_base_alloc_impl(base) != 0) { sudo_debug_printf(SUDO_DEBUG_ERROR, "%s: unable to allocate impl base", __func__); - free(base); - base = NULL; + goto bad; + } + if (pipe2(base->signal_pipe, O_NONBLOCK|O_CLOEXEC) != 0) { + sudo_debug_printf(SUDO_DEBUG_ERROR, + "%s: unable to create signal pipe", __func__); + goto bad; } + sudo_ev_init(&base->signal_event, base->signal_pipe[1], + SUDO_EV_READ|SUDO_EV_PERSIST, signal_pipe_cb, NULL); debug_return_ptr(base); +bad: + if (base != NULL) { + sudo_ev_base_free_impl(base); + free(base); + } + debug_return_ptr(NULL); } void sudo_ev_base_free_v1(struct sudo_event_base *base) { struct sudo_event *ev, *next; + int i; debug_decl(sudo_ev_base_free, SUDO_DEBUG_EVENT) + if (base == NULL) + debug_return; + /* Remove any existing events before freeing the base. */ TAILQ_FOREACH_SAFE(ev, &base->events, entries, next) { sudo_ev_del(base, ev); } + for (i = 0; i < NSIG; i++) { + TAILQ_FOREACH_SAFE(ev, &base->signals[i], entries, next) { + sudo_ev_del(base, ev); + } + free(base->siginfo[i]); + free(base->orig_handlers[i]); + } sudo_ev_base_free_impl(base); + close(base->signal_pipe[0]); + close(base->signal_pipe[1]); free(base); debug_return; } +/* + * Clear and fill in a struct sudo_event. + */ +static void +sudo_ev_init(struct sudo_event *ev, int fd, short events, + sudo_ev_callback_t callback, void *closure) +{ + debug_decl(sudo_ev_init, SUDO_DEBUG_EVENT) + + /* XXX - sanity check events value */ + memset(ev, 0, sizeof(*ev)); + ev->fd = fd; + ev->events = events; + ev->pfd_idx = -1; + ev->callback = callback; + ev->closure = closure; + + debug_return; +} + struct sudo_event * sudo_ev_alloc_v1(int fd, short events, sudo_ev_callback_t callback, void *closure) { struct sudo_event *ev; debug_decl(sudo_ev_alloc, SUDO_DEBUG_EVENT) - /* XXX - sanity check events value */ - - ev = calloc(1, sizeof(*ev)); + ev = malloc(sizeof(*ev)); if (ev == NULL) { sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_LINENO, "%s: unable to allocate event", __func__); debug_return_ptr(NULL); } - ev->fd = fd; - ev->events = events; - ev->pfd_idx = -1; - ev->callback = callback; - ev->closure = closure; + /* For SUDO_EV_SIGINFO we use a container to store closure + siginfo_t */ + if (ISSET(events, SUDO_EV_SIGINFO)) { + struct sudo_ev_siginfo_container *container = + malloc(sizeof(*container)); + if (container == NULL) { + sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_LINENO, + "%s: unable to allocate siginfo container", __func__); + free(ev); + debug_return_ptr(NULL); + } + container->closure = closure; + closure = container; + } + sudo_ev_init(ev, fd, events, callback, closure); debug_return_ptr(ev); } @@ -140,13 +268,123 @@ sudo_ev_free_v1(struct sudo_event *ev) { debug_decl(sudo_ev_free, SUDO_DEBUG_EVENT) + if (ev == NULL) + debug_return; + /* Make sure ev is not in use before freeing it. */ if (ISSET(ev->flags, SUDO_EVQ_INSERTED)) (void)sudo_ev_del(NULL, ev); + if (ISSET(ev->events, SUDO_EV_SIGINFO)) + free(ev->closure); free(ev); + debug_return; } +static void +sudo_ev_handler(int signo, siginfo_t *info, void *context) +{ + unsigned char ch = (unsigned char)signo; + + if (signal_base != NULL) { + /* + * Update signals_pending[] and siginfo[]. + * All signals must be blocked any time siginfo[] is accessed. + */ + memcpy(signal_base->siginfo[signo], info, sizeof(*info)); + signal_base->signal_pending[signo] = 1; + signal_base->signal_caught = 1; + + /* Wake up the other end of the pipe. */ + while (write(signal_base->signal_pipe[0], &ch, 1) == -1) { + if (errno != EINTR) + break; + } + } +} + +int +sudo_ev_add_signal(struct sudo_event_base *base, struct sudo_event *ev, + bool tohead) +{ + const int signo = ev->fd; + debug_decl(sudo_ev_add_signal, SUDO_DEBUG_EVENT) + + sudo_debug_printf(SUDO_DEBUG_INFO, + "%s: adding event %p to base %p, signal %d, events %d", + __func__, ev, base, signo, ev->events); + if (signo >= NSIG) { + sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_LINENO, + "%s: signo %d larger than max %d", __func__, signo, NSIG - 1); + debug_return_int(-1); + } + if ((ev->events & ~(SUDO_EV_SIGNAL|SUDO_EV_SIGINFO|SUDO_EV_PERSIST)) != 0) { + sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_LINENO, + "%s: invalid event set 0x%x", __func__, ev->events); + debug_return_int(-1); + } + + /* + * Allocate base->siginfo[signo] and base->orig_handlers[signo] as needed. + */ + if (base->siginfo[signo] == NULL) { + base->siginfo[signo] = malloc(sizeof(*base->siginfo[signo])); + if (base->siginfo[signo] == NULL) { + sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_LINENO, + "%s: unable to allocate siginfo for signo %d", + __func__, signo); + debug_return_int(-1); + } + } + if (base->orig_handlers[signo] == NULL) { + base->orig_handlers[signo] = + malloc(sizeof(*base->orig_handlers[signo])); + if (base->orig_handlers[signo] == NULL) { + sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_LINENO, + "%s: unable to allocate orig_handlers for signo %d", + __func__, signo); + debug_return_int(-1); + } + } + + /* Install signal handler as needed, saving the original value. */ + if (TAILQ_EMPTY(&base->signals[signo])) { + struct sigaction sa; + memset(&sa, 0, sizeof(sa)); + sigfillset(&sa.sa_mask); + sa.sa_flags = SA_RESTART|SA_SIGINFO; + sa.sa_sigaction = sudo_ev_handler; + if (sigaction(signo, &sa, base->orig_handlers[signo]) != 0) { + sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_LINENO, + "%s: unable to install handler for signo %d", __func__, signo); + debug_return_int(-1); + } + base->num_handlers++; + } + + /* + * Insert signal event into the proper tail queue. + * Signal events are always persistent. + */ + ev->base = base; + if (tohead) { + TAILQ_INSERT_HEAD(&base->signals[signo], ev, entries); + } else { + TAILQ_INSERT_TAIL(&base->signals[signo], ev, entries); + } + SET(ev->events, SUDO_EV_PERSIST); + SET(ev->flags, SUDO_EVQ_INSERTED); + + /* Add the internal signal_pipe event on demand. */ + if (!ISSET(base->signal_event.flags, SUDO_EVQ_INSERTED)) + sudo_ev_add(base, &base->signal_event, NULL, true); + + /* Update global signal base so handler to update signals_pending[] */ + signal_base = base; + + debug_return_int(0); +} + int sudo_ev_add_v1(struct sudo_event_base *base, struct sudo_event *ev, struct timeval *timo, bool tohead) @@ -173,6 +411,10 @@ sudo_ev_add_v1(struct sudo_event_base *base, struct sudo_event *ev, TAILQ_REMOVE(&base->timeouts, ev, timeouts_entries); } } else { + /* Special handling for signal events. */ + if (ev->events & (SUDO_EV_SIGNAL|SUDO_EV_SIGINFO)) + debug_return_int(sudo_ev_add_signal(base, ev, tohead)); + /* Add event to the base. */ sudo_debug_printf(SUDO_DEBUG_INFO, "%s: adding event %p to base %p, fd %d, events %d", @@ -214,6 +456,10 @@ sudo_ev_add_v1(struct sudo_event_base *base, struct sudo_event *ev, debug_return_int(0); } +/* + * Remove an event from the base, if specified, or the base embedded + * in the event if not. Note that there are multiple tail queues. + */ int sudo_ev_del_v1(struct sudo_event_base *base, struct sudo_event *ev) { @@ -240,22 +486,46 @@ sudo_ev_del_v1(struct sudo_event_base *base, struct sudo_event *ev) debug_return_int(-1); } - sudo_debug_printf(SUDO_DEBUG_INFO, - "%s: removing event %p from base %p, fd %d, events %d", - __func__, ev, base, ev->fd, ev->events); + if (ev->events & (SUDO_EV_SIGNAL|SUDO_EV_SIGINFO)) { + const int signo = ev->fd; - /* Call backend. */ - if (ev->events & (SUDO_EV_READ|SUDO_EV_WRITE)) { - if (sudo_ev_del_impl(base, ev) != 0) - debug_return_int(-1); - } + sudo_debug_printf(SUDO_DEBUG_INFO, + "%s: removing event %p from base %p, signo %d, events %d", + __func__, ev, base, signo, ev->events); + + /* Unlink from signal event list. */ + TAILQ_REMOVE(&base->signals[signo], ev, entries); + if (TAILQ_EMPTY(&base->signals[signo])) { + if (sigaction(signo, base->orig_handlers[signo], NULL) != 0) { + sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_LINENO, + "%s: unable to restore handler for signo %d", + __func__, signo); + debug_return_int(-1); + } + base->num_handlers--; + } + if (base->num_handlers == 0) { + /* No registered signal events, remove internal event. */ + sudo_ev_del(base, &base->signal_event); + } + } else { + sudo_debug_printf(SUDO_DEBUG_INFO, + "%s: removing event %p from base %p, fd %d, events %d", + __func__, ev, base, ev->fd, ev->events); + + /* Call backend. */ + if (ev->events & (SUDO_EV_READ|SUDO_EV_WRITE)) { + if (sudo_ev_del_impl(base, ev) != 0) + debug_return_int(-1); + } - /* Unlink from event list. */ - TAILQ_REMOVE(&base->events, ev, entries); + /* Unlink from event list. */ + TAILQ_REMOVE(&base->events, ev, entries); - /* Unlink from timeouts list. */ - if (ISSET(ev->flags, SUDO_EVQ_TIMEOUTS)) - TAILQ_REMOVE(&base->timeouts, ev, timeouts_entries); + /* Unlink from timeouts list. */ + if (ISSET(ev->flags, SUDO_EVQ_TIMEOUTS)) + TAILQ_REMOVE(&base->timeouts, ev, timeouts_entries); + } /* Unlink from active list. */ if (ISSET(ev->flags, SUDO_EVQ_ACTIVE)) @@ -302,8 +572,14 @@ rescan: nready = sudo_ev_scan_impl(base, flags); switch (nready) { case -1: - if (errno == EINTR || errno == ENOMEM) + if (errno == ENOMEM) continue; + if (errno == EINTR) { + /* Interrupted by signal, check for sigevents. */ + if (sudo_ev_activate_sigevents(base)) + break; + continue; + } rc = -1; goto done; case 0: @@ -331,6 +607,9 @@ rescan: break; } + /* Activate signal events, if any. */ + sudo_ev_activate_sigevents(base); + /* * Service each event in the active queue. * We store the current event pointer in the base so that -- 2.40.0