本篇主要分析sudo的信号处理函数。
首先回顾下上篇博客分析的sudo执行的5个步骤:
(void) sigemptyset(&mask);
(void) sigprocmask(SIG_SETMASK, &mask, NULL);
save_signals();
// do something check and prepare
init_signals();
// setuid(ROOT_ID);
restore_signals();
// seteuid and exec
static struct signal_state {
int signo;
int restore;
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 }
};
这个saved_signals数组保存了sudo在调用execve之前需要修改的信号处理函数。save_signals将上述信号的信号处理函数保存到数组中,restore_signals将保存的信号处理函数恢复。这个数组的每个元素是一个signal_state结构体,这个结构体包含信号值,该信号处理函数是否需要被恢复,以及一个sigaction_t变量。
void
save_signals(void)
{
struct signal_state *ss;
debug_decl(save_signals, SUDO_DEBUG_MAIN)
for (ss = saved_signals; ss->signo != -1; ss++) {
if (sigaction(ss->signo, NULL, &ss->sa) != 0)
sudo_warn(U_("unable to save handler for signal %d"), ss->signo);
}
debug_return;
}
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)
sudo_fatal(U_("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) {
if (sigaction(ss->signo, &sa, NULL) != 0) {
sudo_warn(U_("unable to set handler for signal %d"),
ss->signo);
}
}
break;
}
}
debug_return;
}
至此,不得不看一眼神秘的sudo_handler,这个信号处理函数做的仅仅是将信号值(一个字节)通过管道发送出去。
static void
sudo_handler(int s)
{
unsigned char signo = (unsigned char)s;
/*
* The pipe is non-blocking, if we overflow the kernel's pipe
* buffer we drop the signal. This is not a problem in practice.
*/
while (write(signal_pipe[1], &signo, sizeof(signo)) == -1) {
if (errno != EINTR)
break;
}
}
static int
dispatch_pending_signals(struct command_status *cstat)
{
ssize_t nread;
struct sigaction sa;
unsigned char signo = 0;
int rval = 0;
debug_decl(dispatch_pending_signals, SUDO_DEBUG_EXEC);
for (;;) {
nread = read(signal_pipe[0], &signo, sizeof(signo));
if (nread <= 0) {
/* It should not be possible to get EOF but just in case. */
if (nread == 0)
errno = ECONNRESET;
/* Restart if interrupted by signal so the pipe doesn't fill. */
if (errno == EINTR)
continue;
/* If pipe is empty, we are done. */
if (errno == EAGAIN)
break;
sudo_debug_printf(SUDO_DEBUG_ERROR, "error reading signal pipe %s",
strerror(errno));
cstat->type = CMD_ERRNO;
cstat->val = errno;
rval = 1;
break;
}
/* Take the first terminal signal. */
if (signo == SIGINT || signo == SIGQUIT) {
cstat->type = CMD_WSTATUS;
cstat->val = signo + 128;
rval = 1;
break;
}
}
/* Only stop if we haven't already been terminated. */
if (signo == SIGTSTP)
{
memset(&sa, 0, sizeof(sa));
sigemptyset(&sa.sa_mask);
sa.sa_flags = SA_RESTART;
sa.sa_handler = SIG_DFL;
if (sudo_sigaction(SIGTSTP, &sa, NULL) != 0)
sudo_warn(U_("unable to set handler for signal %d"), SIGTSTP);
if (kill(getpid(), SIGTSTP) != 0)
sudo_warn("kill(%d, SIGTSTP)", (int)getpid());
/* No need to reinstall SIGTSTP handler. */
}
debug_return_int(rval);
}
另外在调用execve之前还会调用restore_signals将信号处理函数恢复,这样在执行命令的时候就都是程序启动时的信号处理函数了。至此,一个sudo的简单场景下的信号处理机制就讲完了,是不是觉得这种处理方法很别扭,而且貌似还有很多信号没处理。你若真以为本文到此为止就输了,sudo的设计者显然不可能专门在单进程下使用管道。再回到最初说的5个步骤,这5个步骤被我简化的太多了,比如fork。但是我这样的简化也无可厚非,sudo的手册中这样说了“As a special case, if the policy plugin does not define a close function and no pty is required, sudo will execute the command directly instead of calling fork(2) first.”也就是说:sudo确实是存在这样的步骤的,但是对于大部分情况来说,在exec之前是需要先fork的。这种情况下,首先由sudo_execute调用fork_cmnd,在fork_cmnd中进行fork后调用exec_cmnd。
static int fork_cmnd(struct command_details *details, int sv[2])
{
struct command_status cstat;
sigaction_t sa;
memset(&sa, 0, sizeof(sa));
sigfillset(&sa.sa_mask);
sa.sa_flags = SA_INTERRUPT; /* do not restart syscalls */
#ifdef SA_SIGINFO
sa.sa_flags |= SA_SIGINFO;
sa.sa_sigaction = handler;
#else
sa.sa_handler = handler;
#endif
if (sudo_sigaction(SIGCHLD, &sa, NULL) != 0)
sudo_warn(U_("unable to set handler for signal %d"), SIGCHLD);
if (sudo_sigaction(SIGCONT, &sa, NULL) != 0)
sudo_warn(U_("unable to set handler for signal %d"), SIGCONT);
#ifdef SA_SIGINFO
sa.sa_sigaction = handler_user_only;
#endif
if (sudo_sigaction(SIGTSTP, &sa, NULL) != 0)
sudo_warn(U_("unable to set handler for signal %d"), SIGTSTP);
cmnd_pid = sudo_debug_fork();
switch (cmnd_pid) {
case -1:
sudo_fatal(U_("unable to fork"));
break;
case 0:
/* child */
close(sv[0]);
close(signal_pipe[0]);
close(signal_pipe[1]);
fcntl(sv[1], F_SETFD, FD_CLOEXEC);
exec_cmnd(details, &cstat, sv[1]);
send(sv[1], &cstat, sizeof(cstat), 0);
sudo_debug_exit_int(__func__, __FILE__, __LINE__, sudo_debug_subsys, 1);
_exit(1);
}
sudo_debug_printf(SUDO_DEBUG_INFO, "executed %s, pid %d", details->command,
(int)cmnd_pid);
debug_return_int(cmnd_pid);
}
void
handler(int s, siginfo_t *info, void *context)
{
unsigned char signo = (unsigned char)s;
if (s != SIGCHLD && USER_SIGNALED(info)) {
pid_t si_pgrp = getpgid(info->si_pid);
if (si_pgrp != (pid_t)-1) {
if (si_pgrp == ppgrp || si_pgrp == cmnd_pid)
return;
} else if (info->si_pid == cmnd_pid) {
return;
}
}
while (write(signal_pipe[1], &signo, sizeof(signo)) == -1) {
if (errno != EINTR)
break;
}
}
static void
signal_pipe_cb(int fd, int what, void *v)
{
struct exec_closure *ec = v;
char signame[SIG2STR_MAX];
unsigned char signo;
ssize_t nread;
int rc = 0;
debug_decl(signal_pipe_cb, SUDO_DEBUG_EXEC)
do {
nread = read(fd, &signo, sizeof(signo));
if (nread <= 0) {
/* It should not be possible to get EOF but just in case... */
if (nread == 0)
errno = ECONNRESET;
/* Restart if interrupted by signal so the pipe doesn't fill. */
if (errno == EINTR)
continue;
/* On error, store errno and break out of the event loop. */
if (errno != EAGAIN) {
ec->cstat->type = CMD_ERRNO;
ec->cstat->val = errno;
sudo_warn(U_("error reading from signal pipe"));
sudo_ev_loopbreak(ec->evbase);
}
break;
}
if (sig2str(signo, signame) == -1)
snprintf(signame, sizeof(signame), "%d", signo);
sudo_debug_printf(SUDO_DEBUG_DIAG, "received SIG%s", signame);
rc = dispatch_signal(ec->evbase, ec->child, signo, signame,
ec->cstat);
} while (rc == 0);
debug_return;
}
static int
dispatch_signal(struct sudo_event_base *evbase, pid_t child,
int signo, char *signame, struct command_status *cstat)
{
int rc = 1;
debug_decl(dispatch_signal, SUDO_DEBUG_EXEC)
sudo_debug_printf(SUDO_DEBUG_INFO,
"%s: evbase %p, child: %d, signo %s(%d), cstat %p",
__func__, evbase, (int)child, signame, signo, cstat);
if (signo == SIGCHLD) {
pid_t pid;
int status;
do {
pid = waitpid(child, &status, WUNTRACED|WNOHANG);
} while (pid == -1 && errno == EINTR);
if (pid == child) {
// do something with child
}
} else {
/* Send signal to child. */
if (signo == SIGALRM) {
terminate_command(child, false);
} else if (kill(child, signo) != 0) {
sudo_warn("kill(%d, SIG%s)", (int)child, signame);
}
}
rc = 0;
done:
debug_return_int(rc);
}