本篇主要分析sudo的信号处理函数。
首先回顾下上篇博客分析的sudo执行的5个步骤:
- 修改信号处理函数:保存原来的信号处理函数,设置新的信号处理函数
- 调用setuid将实际用户设置为ROOT
- 恢复信号处理函数
- 设置用户程序指定的权限(默认ROOT),并设置其他运行环境参数
- 调用execve执行用户程序
将信号相关的部分单独列出来就是这样:
(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
除了一开始将所有信号都设置为非阻塞状态,主要就是save_signals()、init_signals()和restore_signals()三个函数,在分析这三个函数之前我们需要先了解一个数组:
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变量。
首先看下save_signals函数,这个函数很简单,就是将saved_signals中初始化了的信号的信号处理函数取出并保存。将sigaction函数的第二个参数设置为NULL,就能在地撒个参数中得到对应信号的信号处理函数。
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;
}
然后是init_signals函数,这个函数首先创建一个非阻塞的管道(用处后面会说),然后将saved_signals中的信号的信号处理函数设置为sudo_handler,信号处理函数被调用时将阻塞所有信号以防函数重入,而且这些信号并不会中断系统的某些阻塞调用(flags=SA_RESTART)。另外,五个信号(SIGCHLD、SIGCONT、SIGPIPE、SIGTTIN和SIGTTOU)不设置新的信号处理函数,这几个信号的信号处理会在后面其他地方根据不同的需要进行设置。
void
init_signals(void)
{