From 855a11af2b8baca922df3a1fdcf040af77e93225 Mon Sep 17 00:00:00 2001 From: "Todd C. Miller" Date: Thu, 17 Jan 2013 13:29:46 -0500 Subject: [PATCH] Move signal code into its own source file and add sudo_sigaction() wrapper that has an extra flag to check the saved_signals list to only install the handler if the signal is not already ignored. Bump plugin API version for the new front-end signal behavior. --- MANIFEST | 1 + include/sudo_plugin.h | 2 +- src/Makefile.in | 10 ++- src/exec.c | 90 ++++++----------------- src/exec_pty.c | 44 +++++------ src/signal.c | 167 ++++++++++++++++++++++++++++++++++++++++++ src/sudo.c | 44 +---------- src/sudo.h | 11 ++- 8 files changed, 229 insertions(+), 140 deletions(-) create mode 100644 src/signal.c diff --git a/MANIFEST b/MANIFEST index 48454ecac..aeb9661d6 100644 --- a/MANIFEST +++ b/MANIFEST @@ -347,6 +347,7 @@ src/po/zh_CN.po src/preload.c src/selinux.c src/sesh.c +src/signal.c src/solaris.c src/sudo.c src/sudo.h diff --git a/include/sudo_plugin.h b/include/sudo_plugin.h index 52dee0adf..7a2e3b6fb 100644 --- a/include/sudo_plugin.h +++ b/include/sudo_plugin.h @@ -19,7 +19,7 @@ /* API version major/minor */ #define SUDO_API_VERSION_MAJOR 1 -#define SUDO_API_VERSION_MINOR 2 +#define SUDO_API_VERSION_MINOR 3 #define SUDO_API_MKVERSION(x, y) ((x << 16) | y) #define SUDO_API_VERSION SUDO_API_MKVERSION(SUDO_API_VERSION_MAJOR, SUDO_API_VERSION_MINOR) diff --git a/src/Makefile.in b/src/Makefile.in index 8b2474d9f..261afd937 100644 --- a/src/Makefile.in +++ b/src/Makefile.in @@ -85,8 +85,8 @@ SHELL = @SHELL@ PROGS = @PROGS@ OBJS = conversation.o env_hooks.o exec.o exec_common.o exec_pty.o get_pty.o \ - hooks.o net_ifs.o load_plugins.o parse_args.o sudo.o sudo_edit.o \ - tgetpass.o ttyname.o utmp.o @SUDO_OBJS@ + hooks.o net_ifs.o load_plugins.o parse_args.o signal.o sudo.o \ + sudo_edit.o tgetpass.o ttyname.o utmp.o @SUDO_OBJS@ SESH_OBJS = sesh.o locale_stub.o exec_common.o @@ -261,6 +261,12 @@ sesh.o: $(srcdir)/sesh.c $(top_builddir)/config.h \ $(incdir)/list.h $(incdir)/sudo_debug.h $(srcdir)/sudo_exec.h \ $(incdir)/sudo_plugin.h $(CC) -c $(CPPFLAGS) $(CFLAGS) $(PIE_CFLAGS) $(SSP_CFLAGS) $(DEFS) $(srcdir)/sesh.c +signal.o: $(srcdir)/signal.c $(top_builddir)/config.h $(srcdir)/sudo.h \ + $(top_builddir)/pathnames.h $(top_srcdir)/compat/stdbool.h \ + $(incdir)/missing.h $(incdir)/alloc.h $(incdir)/error.h \ + $(incdir)/fileops.h $(incdir)/list.h $(incdir)/sudo_conf.h \ + $(incdir)/list.h $(incdir)/sudo_debug.h $(incdir)/gettext.h + $(CC) -c $(CPPFLAGS) $(CFLAGS) $(PIE_CFLAGS) $(SSP_CFLAGS) $(DEFS) $(srcdir)/signal.c solaris.o: $(srcdir)/solaris.c $(top_builddir)/config.h $(srcdir)/sudo.h \ $(top_builddir)/pathnames.h $(top_srcdir)/compat/stdbool.h \ $(incdir)/missing.h $(incdir)/alloc.h $(incdir)/error.h \ diff --git a/src/exec.c b/src/exec.c index 11ab6ac18..dd6000b8e 100644 --- a/src/exec.c +++ b/src/exec.c @@ -104,7 +104,7 @@ static int fork_cmnd(struct command_details *details, int sv[2]) * XXX - currently we send SIGCONT upon resume in some cases where * we don't need to (e.g. command pgrp == parent pgrp). */ - zero_bytes(&sa, sizeof(sa)); + memset(&sa, 0, sizeof(sa)); sigfillset(&sa.sa_mask); sa.sa_flags = SA_INTERRUPT; /* do not restart syscalls */ #ifdef SA_SIGINFO @@ -113,11 +113,11 @@ static int fork_cmnd(struct command_details *details, int sv[2]) #else sa.sa_handler = handler; #endif - sigaction(SIGCONT, &sa, NULL); + sudo_sigaction(SIGCONT, &sa, NULL, false); #ifdef SA_SIGINFO sa.sa_sigaction = handler_user_only; #endif - sigaction(SIGTSTP, &sa, NULL); + sudo_sigaction(SIGTSTP, &sa, NULL, true); /* * The policy plugin's session init must be run before we fork @@ -175,56 +175,6 @@ static int fork_cmnd(struct command_details *details, int sv[2]) debug_return_int(cmnd_pid); } -static struct signal_state { - int signo; - sigaction_t sa; -} saved_signals[] = { - { SIGALRM }, /* SAVED_SIGALRM */ - { SIGCHLD }, /* SAVED_SIGCHLD */ - { SIGCONT }, /* SAVED_SIGCONT */ - { SIGHUP }, /* SAVED_SIGHUP */ - { SIGINT }, /* SAVED_SIGINT */ - { SIGPIPE }, /* SAVED_SIGPIPE */ - { SIGQUIT }, /* SAVED_SIGQUIT */ - { SIGTERM }, /* SAVED_SIGTERM */ - { SIGTSTP }, /* SAVED_SIGTSTP */ - { SIGTTIN }, /* SAVED_SIGTTIN */ - { SIGTTOU }, /* SAVED_SIGTTOU */ - { SIGUSR1 }, /* SAVED_SIGUSR1 */ - { SIGUSR2 }, /* SAVED_SIGUSR2 */ - { -1 } -}; - -/* - * Save signal handler state so it can be restored before exec. - */ -void -save_signals(void) -{ - struct signal_state *ss; - debug_decl(save_signals, SUDO_DEBUG_EXEC) - - for (ss = saved_signals; ss->signo != -1; ss++) - sigaction(ss->signo, NULL, &ss->sa); - - debug_return; -} - -/* - * Restore signal handlers to initial state. - */ -void -restore_signals(void) -{ - struct signal_state *ss; - debug_decl(restore_signals, SUDO_DEBUG_EXEC) - - for (ss = saved_signals; ss->signo != -1; ss++) - sigaction(ss->signo, &ss->sa, NULL); - - debug_return; -} - /* * Execute a command, potentially in a pty with I/O loggging. * This is a little bit tricky due to how POSIX job control works and @@ -288,7 +238,7 @@ sudo_execute(struct command_details *details, struct command_status *cstat) * We block all other signals while running the signal handler. * Note: HP-UX select() will not be interrupted if SA_RESTART set. */ - zero_bytes(&sa, sizeof(sa)); + memset(&sa, 0, sizeof(sa)); sigfillset(&sa.sa_mask); sa.sa_flags = SA_INTERRUPT; /* do not restart syscalls */ #ifdef SA_SIGINFO @@ -297,12 +247,12 @@ sudo_execute(struct command_details *details, struct command_status *cstat) #else sa.sa_handler = handler; #endif - sigaction(SIGALRM, &sa, NULL); - sigaction(SIGCHLD, &sa, NULL); - sigaction(SIGPIPE, &sa, NULL); - sigaction(SIGTERM, &sa, NULL); - sigaction(SIGUSR1, &sa, NULL); - sigaction(SIGUSR2, &sa, NULL); + sudo_sigaction(SIGTERM, &sa, NULL, true); + sudo_sigaction(SIGALRM, &sa, NULL, false); /* XXX - only if there is a timeout */ + sudo_sigaction(SIGCHLD, &sa, NULL, false); + sudo_sigaction(SIGPIPE, &sa, NULL, false); + sudo_sigaction(SIGUSR1, &sa, NULL, true); + sudo_sigaction(SIGUSR2, &sa, NULL, true); /* * When not running the command in a pty, we do not want to @@ -317,9 +267,9 @@ sudo_execute(struct command_details *details, struct command_status *cstat) sa.sa_sigaction = handler_user_only; } #endif - sigaction(SIGHUP, &sa, NULL); - sigaction(SIGINT, &sa, NULL); - sigaction(SIGQUIT, &sa, NULL); + sudo_sigaction(SIGHUP, &sa, NULL, true); + sudo_sigaction(SIGINT, &sa, NULL, true); + sudo_sigaction(SIGQUIT, &sa, NULL, true); /* Max fd we will be selecting on. */ maxfd = MAX(sv[0], signal_pipe[0]); @@ -560,15 +510,16 @@ dispatch_signals(int sv[2], pid_t child, int log_io, struct command_status *csta saved_pgrp = -1; } if (signo == SIGTSTP) { - zero_bytes(&sa, sizeof(sa)); + memset(&sa, 0, sizeof(sa)); sigemptyset(&sa.sa_mask); + sa.sa_flags = SA_RESTART; sa.sa_handler = SIG_DFL; - sigaction(SIGTSTP, &sa, &osa); + sudo_sigaction(SIGTSTP, &sa, &osa, false); } if (kill(getpid(), signo) != 0) warning("kill(%d, SIG%s)", (int)getpid(), signame); if (signo == SIGTSTP) - sigaction(SIGTSTP, &osa, NULL); + sudo_sigaction(SIGTSTP, &osa, NULL, false); if (fd != -1) { /* * Restore command's process group if different. @@ -603,7 +554,7 @@ dispatch_signals(int sv[2], pid_t child, int log_io, struct command_status *csta } /* - * Read pending signals on signale_pipe written by sudo_handler(). + * Drain pending signals from signale_pipe written by sudo_handler(). * Handles the case where the signal was sent to us before * we have executed the command. * Returns 1 if we should terminate, else 0. @@ -647,10 +598,11 @@ dispatch_pending_signals(struct command_status *cstat) /* Only stop if we haven't already been terminated. */ if (signo == SIGTSTP) { - zero_bytes(&sa, sizeof(sa)); + memset(&sa, 0, sizeof(sa)); sigemptyset(&sa.sa_mask); + sa.sa_flags = SA_RESTART; sa.sa_handler = SIG_DFL; - sigaction(SIGTSTP, &sa, NULL); + sudo_sigaction(SIGTSTP, &sa, NULL, false); if (kill(getpid(), SIGTSTP) != 0) warning("kill(%d, SIGTSTP)", (int)getpid()); /* No need to reinstall SIGTSTP handler. */ diff --git a/src/exec_pty.c b/src/exec_pty.c index d0601102c..42221a5f3 100644 --- a/src/exec_pty.c +++ b/src/exec_pty.c @@ -376,11 +376,11 @@ suspend_parent(int signo) /* Suspend self and continue command when we resume. */ if (signo != SIGSTOP) { - zero_bytes(&sa, sizeof(sa)); + memset(&sa, 0, sizeof(sa)); sigemptyset(&sa.sa_mask); - sa.sa_flags = SA_INTERRUPT; /* do not restart syscalls */ + sa.sa_flags = SA_RESTART; sa.sa_handler = SIG_DFL; - sigaction(signo, &sa, &osa); + sudo_sigaction(signo, &sa, &osa, false); } sudo_debug_printf(SUDO_DEBUG_INFO, "kill parent SIG%s", signame); if (killpg(ppgrp, signo) != 0) @@ -413,7 +413,7 @@ suspend_parent(int signo) } if (signo != SIGSTOP) - sigaction(signo, &osa, NULL); + sudo_sigaction(signo, &osa, NULL, false); rval = ttymode == TERM_RAW ? SIGCONT_FG : SIGCONT_BG; break; } @@ -569,13 +569,13 @@ fork_pty(struct command_details *details, int sv[], int *maxfd, sigset_t *omask) ppgrp = getpgrp(); /* parent's pgrp, so child can signal us */ - zero_bytes(&sa, sizeof(sa)); + memset(&sa, 0, sizeof(sa)); sigemptyset(&sa.sa_mask); if (io_fds[SFD_USERTTY] != -1) { sa.sa_flags = SA_RESTART; sa.sa_handler = sigwinch; - sigaction(SIGWINCH, &sa, NULL); + sudo_sigaction(SIGWINCH, &sa, NULL, false); } /* So we can block tty-generated signals */ @@ -644,8 +644,8 @@ fork_pty(struct command_details *details, int sv[], int *maxfd, sigset_t *omask) /* We don't want to receive SIGTTIN/SIGTTOU, getting EIO is preferable. */ sa.sa_handler = SIG_IGN; - sigaction(SIGTTIN, &sa, NULL); - sigaction(SIGTTOU, &sa, NULL); + sudo_sigaction(SIGTTIN, &sa, NULL, true); + sudo_sigaction(SIGTTOU, &sa, NULL, true); /* Job control signals to relay from parent to child. */ sigfillset(&sa.sa_mask); @@ -656,7 +656,7 @@ fork_pty(struct command_details *details, int sv[], int *maxfd, sigset_t *omask) #else sa.sa_handler = handler; #endif - sigaction(SIGTSTP, &sa, NULL); + sudo_sigaction(SIGTSTP, &sa, NULL, true); if (foreground) { /* Copy terminal attrs from user tty -> pty slave. */ @@ -987,17 +987,17 @@ exec_monitor(struct command_details *details, int backchannel) error(1, _("unable to create pipe")); /* Reset SIGWINCH and SIGALRM. */ - zero_bytes(&sa, sizeof(sa)); + memset(&sa, 0, sizeof(sa)); sigemptyset(&sa.sa_mask); sa.sa_flags = SA_RESTART; sa.sa_handler = SIG_DFL; - sigaction(SIGWINCH, &sa, NULL); - sigaction(SIGALRM, &sa, NULL); + sudo_sigaction(SIGWINCH, &sa, NULL, false); + sudo_sigaction(SIGALRM, &sa, NULL, false); /* XXX - saved value */ /* Ignore any SIGTTIN or SIGTTOU we get. */ sa.sa_handler = SIG_IGN; - sigaction(SIGTTIN, &sa, NULL); - sigaction(SIGTTOU, &sa, NULL); + sudo_sigaction(SIGTTIN, &sa, NULL, true); + sudo_sigaction(SIGTTOU, &sa, NULL, true); /* Block all signals in mon_handler(). */ sigfillset(&sa.sa_mask); @@ -1010,7 +1010,7 @@ exec_monitor(struct command_details *details, int backchannel) #else sa.sa_handler = mon_handler; #endif - sigaction(SIGCHLD, &sa, NULL); + sudo_sigaction(SIGCHLD, &sa, NULL, false); /* Catch common signals so we can cleanup properly. */ sa.sa_flags = SA_RESTART; @@ -1020,13 +1020,13 @@ exec_monitor(struct command_details *details, int backchannel) #else sa.sa_handler = mon_handler; #endif - sigaction(SIGHUP, &sa, NULL); - sigaction(SIGINT, &sa, NULL); - sigaction(SIGQUIT, &sa, NULL); - sigaction(SIGTERM, &sa, NULL); - sigaction(SIGTSTP, &sa, NULL); - sigaction(SIGUSR1, &sa, NULL); - sigaction(SIGUSR2, &sa, NULL); + sudo_sigaction(SIGHUP, &sa, NULL, true); + sudo_sigaction(SIGINT, &sa, NULL, true); + sudo_sigaction(SIGQUIT, &sa, NULL, true); + sudo_sigaction(SIGTERM, &sa, NULL, true); + sudo_sigaction(SIGTSTP, &sa, NULL, true); + sudo_sigaction(SIGUSR1, &sa, NULL, true); + sudo_sigaction(SIGUSR2, &sa, NULL, true); /* * Start a new session with the parent as the session leader diff --git a/src/signal.c b/src/signal.c new file mode 100644 index 000000000..549279637 --- /dev/null +++ b/src/signal.c @@ -0,0 +1,167 @@ +/* + * Copyright (c) 2009-2012 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 + * 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 +#ifdef STDC_HEADERS +# include +# include +#else +# ifdef HAVE_STDLIB_H +# include +# endif +#endif /* STDC_HEADERS */ +#ifdef HAVE_STRING_H +# include +#endif /* HAVE_STRING_H */ +#ifdef HAVE_STRINGS_H +# include +#endif /* HAVE_STRINGS_H */ +#ifdef HAVE_UNISTD_H +# include +#endif /* HAVE_UNISTD_H */ +#include +#include + +#include "sudo.h" + +int signal_pipe[2]; + +static struct signal_state { + int signo; + sigaction_t sa; +} saved_signals[] = { + { SIGALRM }, /* SAVED_SIGALRM */ + { SIGCHLD }, /* SAVED_SIGCHLD */ + { SIGCONT }, /* SAVED_SIGCONT */ + { SIGHUP }, /* SAVED_SIGHUP */ + { SIGINT }, /* SAVED_SIGINT */ + { SIGPIPE }, /* SAVED_SIGPIPE */ + { SIGQUIT }, /* SAVED_SIGQUIT */ + { SIGTERM }, /* SAVED_SIGTERM */ + { SIGTSTP }, /* SAVED_SIGTSTP */ + { SIGTTIN }, /* SAVED_SIGTTIN */ + { SIGTTOU }, /* SAVED_SIGTTOU */ + { SIGUSR1 }, /* SAVED_SIGUSR1 */ + { SIGUSR2 }, /* SAVED_SIGUSR2 */ + { -1 } +}; + +/* + * Save signal handler state so it can be restored before exec. + */ +void +save_signals(void) +{ + struct signal_state *ss; + debug_decl(save_signals, SUDO_DEBUG_MAIN) + + for (ss = saved_signals; ss->signo != -1; ss++) + sigaction(ss->signo, NULL, &ss->sa); + + debug_return; +} + +/* + * Restore signal handlers to initial state for exec. + */ +void +restore_signals(void) +{ + struct signal_state *ss; + debug_decl(restore_signals, SUDO_DEBUG_MAIN) + + for (ss = saved_signals; ss->signo != -1; ss++) + sigaction(ss->signo, &ss->sa, NULL); + + debug_return; +} + +static void +sudo_handler(int signo) +{ + /* + * The pipe is non-blocking, if we overflow the kernel's pipe + * buffer we drop the signal. This is not a problem in practice. + */ + ignore_result(write(signal_pipe[1], &signo, sizeof(signo))); +} + +/* + * Trap tty-generated (and other) signals so we can't be killed before + * calling the policy close function. The signal pipe will be drained + * in sudo_execute() before running the command and new handlers will + * be installed in the parent. + */ +void +init_signals(void) +{ + struct sigaction sa; + struct signal_state *ss; + debug_decl(init_signals, SUDO_DEBUG_MAIN) + + /* + * We use a pipe to atomically handle signal notification within + * the select() loop without races (we may not have pselect()). + */ + if (pipe_nonblock(signal_pipe) != 0) + error(1, _("unable to create pipe")); + + memset(&sa, 0, sizeof(sa)); + sigfillset(&sa.sa_mask); + sa.sa_flags = SA_RESTART; + sa.sa_handler = sudo_handler; + + for (ss = saved_signals; ss->signo > 0; ss++) { + switch (ss->signo) { + case SIGCHLD: + case SIGCONT: + case SIGPIPE: + case SIGTTIN: + case SIGTTOU: + /* Don't install these until exec time. */ + break; + default: + if (ss->sa.sa_handler != SIG_IGN) + sigaction(ss->signo, &sa, NULL); + break; + } + } + debug_return; +} + +/* + * Like sigaction() but includes an udpate_only flag. + * In update-only mode, don't override SIG_IGN. + */ +int +sudo_sigaction(int signo, struct sigaction *sa, struct sigaction *osa, bool update_only) +{ + /* Don't override SIG_IGN if the update_only flag is set. */ + if (update_only) { + struct signal_state *ss; + for (ss = saved_signals; ss->signo > 0; ss++) { + if (ss->signo == signo) { + if (ss->sa.sa_handler == SIG_IGN) + return 0; + break; + } + } + } + return sigaction(signo, sa, osa); +} diff --git a/src/sudo.c b/src/sudo.c index c81e2eb32..1246b26f5 100644 --- a/src/sudo.c +++ b/src/sudo.c @@ -95,7 +95,6 @@ struct plugin_container policy_plugin; struct plugin_container_list io_plugins; struct user_details user_details; const char *list_user, *runas_user, *runas_group; /* extern for parse_args.c */ -int signal_pipe[2]; static int sudo_mode; /* @@ -107,7 +106,6 @@ static void sudo_check_suid(const char *path); static char **get_user_info(struct user_details *); static void command_info_to_details(char * const info[], struct command_details *details); -static void setup_signals(void); /* Policy plugin convenience functions. */ static int policy_open(struct plugin_container *plugin, char * const settings[], @@ -209,7 +207,7 @@ main(int argc, char *argv[], char *envp[]) errorx(1, _("unable to initialize policy plugin")); } - setup_signals(); + init_signals(); switch (sudo_mode & MODE_MASK) { case MODE_VERSION: @@ -1249,43 +1247,3 @@ iolog_unlink(struct plugin_container *plugin) debug_return; } - -static void -sudo_handler(int signo) -{ - /* - * The pipe is non-blocking, if we overflow the kernel's pipe - * buffer we drop the signal. This is not a problem in practice. - */ - ignore_result(write(signal_pipe[1], &signo, sizeof(signo))); -} - -/* - * Trap tty-generated signals so we can't be killed before calling - * the policy close function. The signal pipe will be checked - * in sudo_execute(). - */ -static void -setup_signals(void) -{ - struct sigaction sa; - debug_decl(setup_signals, SUDO_DEBUG_MAIN) - - /* - * We use a pipe to atomically handle signal notification within - * the select() loop without races (we may not have pselect()). - */ - if (pipe_nonblock(signal_pipe) != 0) - error(1, _("unable to create pipe")); - - /* XXX - should not install handler if ignored by default. */ - memset(&sa, 0, sizeof(sa)); - sigfillset(&sa.sa_mask); - sa.sa_flags = SA_RESTART; - sa.sa_handler = sudo_handler; - sigaction(SIGINT, &sa, NULL); - sigaction(SIGQUIT, &sa, NULL); - sigaction(SIGTSTP, &sa, NULL); - - debug_return; -} diff --git a/src/sudo.h b/src/sudo.h index 6887a13dd..1517b560f 100644 --- a/src/sudo.h +++ b/src/sudo.h @@ -184,8 +184,6 @@ void zero_bytes(volatile void *, size_t); /* exec.c */ int pipe_nonblock(int fds[2]); int sudo_execute(struct command_details *details, struct command_status *cstat); -void restore_signals(void); -void save_signals(void); /* term.c */ int term_cbreak(int); @@ -218,7 +216,6 @@ int run_command(struct command_details *details); int os_init_common(int argc, char *argv[], char *envp[]); extern const char *list_user, *runas_user, *runas_group; extern struct user_details user_details; -extern int signal_pipe[2]; /* sudo_edit.c */ int sudo_edit(struct command_details *details); @@ -267,4 +264,12 @@ int sudo_setgroups(int ngids, const GETGROUPS_T *gids); /* ttyname.c */ char *get_process_ttyname(void); +/* signal.c */ +struct sigaction; +extern int signal_pipe[2]; +int sudo_sigaction(int signo, struct sigaction *sa, struct sigaction *osa, bool update_only); +void init_signals(void); +void restore_signals(void); +void save_signals(void); + #endif /* _SUDO_SUDO_H */