内核处理信号对应用层堆栈的影响 by alert7 < alert7@xfocus.org > 主页: http://www.xfocus.org/ http://www.whitecell.org/ 时间:2003年8月1日 好久没有为组织做点贡献了,真有点过意不去:( 本文着重点在内核信号处理对应用层堆栈的影响上,其他的一些在处理信号细节上被忽略。 至于本文是否跟安全相关,那就是仁者见仁智者见智了。 1 发送信号过程: 发送信号的过程比接收信号的过程简单的多。当应用层用KILL命令向某个进程发送进程的时候, 内核只在进程task_struct的sigpending结构中安排一个信号位。 2 接收信号过程 信号处理的时机。 当某个进程有悬而未决的信号的时候,内核就会调用do_signal函数 do_signal做一些其他功能上的事情,真正递送一个信号是在handle_signal函数。于是在最后 do_signal函数调用了handle_signal真正递送一个信号。当然要想到达这一步需要一些条件。比如说 应用层已经声明要处理该信号,信号不是些不可捕获的信号等等... 重点中的重点,我们来看看handle_signal函数 /* * OK, we're invoking a handler */ static void handle_signal(unsigned long sig, struct k_sigaction *ka, siginfo_t *info, sigset_t *oldset, struct pt_regs * regs) { .... /* Set up the stack frame */ if (ka->sa.sa_flags & SA_SIGINFO) setup_rt_frame(sig, ka, info, oldset, regs); else setup_frame(sig, ka, oldset, regs); ..... } 去掉一些我们不想关心的东西,代码就剩下上面这些。 以上函数setup_rt_frame和setup_frame就是内核在应用层的堆栈上安排信号堆栈帧的过程,就是我们所 要关注的。setup_rt_frame和setup_frame雷同,我们就来分析下setup_frame函数。 static void setup_frame(int sig, struct k_sigaction *ka, sigset_t *set, struct pt_regs * regs) { struct sigframe *frame; int err = 0; frame = get_sigframe(ka, regs, sizeof(*frame)); //决定要使用应用层堆栈的地址 if (!access_ok(VERIFY_WRITE, frame, sizeof(*frame))) //判断是否可写 goto give_sigsegv; err |= __put_user((current->exec_domain && current->exec_domain->signal_invmap && sig < 32 ? current->exec_domain->signal_invmap[sig] : sig), &frame->sig); if (err) goto give_sigsegv; /*保存寄存器信号到&frame->sc和&frame->fpstate中*/ err |= setup_sigcontext(&frame->sc, &frame->fpstate, regs, set->sig[0]); if (err) goto give_sigsegv; if (_NSIG_WORDS > 1) { err |= __copy_to_user(frame->extramask, &set->sig[1], sizeof(frame->extramask)); } if (err) goto give_sigsegv; /* Set up to return from userspace. If provided, use a stub already in userspace. */ if (ka->sa.sa_flags & SA_RESTORER) { err |= __put_user(ka->sa.sa_restorer, &frame->pretcode); } else { /*把frame->retcod的地址放到&frame->pretcode中,这样当信号处理函数返回时候就会*/ /*跳到frame->retcode地址去执行代码了*/ err |= __put_user(frame->retcode, &frame->pretcode); /* This is popl %eax ; movl $,%eax ; int $0x80 */ err |= __put_user(0xb858, (short *)(frame->retcode+0)); err |= __put_user(__NR_sigreturn, (int *)(frame->retcode+2)); err |= __put_user(0x80cd, (short *)(frame->retcode+6)); /*以上在frame->retcode上安排了popl %eax ; movl $,%eax ; int $0x80指令*/ } if (err) goto give_sigsegv; /* Set up registers for signal handler */ regs->esp = (unsigned long) frame; //让应用层的esp指向frame; regs->eip = (unsigned long) ka->sa.sa_handler;//EIP为信号处理函数 set_fs(USER_DS); regs->xds = __USER_DS; regs->xes = __USER_DS; regs->xss = __USER_DS; regs->xcs = __USER_CS; regs->eflags &= ~TF_MASK; #if DEBUG_SIG printk("SIG deliver (%s:%d): sp=%p pc=%p ra=%p\n", current->comm, current->pid, frame, regs->eip, frame->pretcode); #endif return; give_sigsegv: if (sig == SIGSEGV) ka->sa.sa_handler = SIG_DFL; force_sig(SIGSEGV, current); } 到此,内核在应用层的堆栈上就安排了一个帧,我们来看一下一个实际的例子。 [alert7@redhat73 sigal]$ cat test.c test () { printf("test"); return; } int main(int argv,char **argc) { char buf[256]; signal(10,test); while(1); } [alert7@redhat73] (gdb) b main Breakpoint 1 at 0x8048501 (gdb) r dd dd Starting program: /home/alert7/sigal/test dd dd Breakpoint 1, 0x08048501 in main () (gdb) Breakpoint 2 at 0x42029098 (gdb) c Continuing. (gdb) x/5i 0x42029098 0x42029098 <__restore>: pop %eax 0x42029099 <__restore+1>: mov $0x77,%eax 0x4202909e <__restore+6>: int $0x80 0x420290a0 <__restore+8>: mov (%esp,1),%ebx 0x420290a3 <__restore+11>: ret (gdb) i reg esp ebp eip esp 0xbffff748 0xbffff748 ebp 0xbffffb38 0xbffffb38 eip 0x4202909e 0x4202909e (gdb) x/50x $esp-8 //$esp-8就是内核构造的一个信号帧 0xbffff740: 0x42029098 0x0000000a 0x00000000 0x00000000 0xbffff750: 0x0000002b 0x0000002b 0xbffffba4 0x40013020 0xbffff760: 0xbffffb38 0xbffffa20 0x4213030c 0xbffffc00 0xbffff770: 0x08049752 0xbffffb2c 0x00000001 0x00000000 0xbffff780: 0x08048570 0x00000023 0x00000346 0xbffffa20 0xbffff790: 0x0000002b 0x00000000 0x00000000 0x00000000 0xbffff7a0: 0x4000083e 0x400005b8 0x40000218 0x400131e8 0xbffff7b0: 0x00000003 0x40013e48 0x00000003 0x42009e38 0xbffff7c0: 0x40013d68 0x0d1fc7ae 0x0d1fc7ae 0xbffff890 0xbffff7d0: 0x40013bc8 0x4200f624 0x00000000 0x00000000 0xbffff7e0: 0x42009e38 0x40013bc8 0x00000000 0x00000000 0xbffff7f0: 0x00000000 0x00000000 0x00000000 0x00000000 0xbffff800: 0x00000000 0x00000000 struct sigframe { char *pretcode; //这里为0x42029098,在该程序中,ka->sa.sa_flags 有 SA_RESTORER标志, //所以没有在堆栈中安排指令,而是使用了一个现成的地址 int sig; //信号为10 struct sigcontext sc; struct _fpstate fpstate; unsigned long extramask[_NSIG_WORDS-1]; char retcode[8]; }; struct sigcontext { unsigned short gs, __gsh;//0,0 unsigned short fs, __fsh;//0,0 unsigned short es, __esh;//0x2b,0 unsigned short ds, __dsh;//0x2b,0 unsigned long edi; //0xbffffba4 unsigned long esi; //0x40013020 unsigned long ebp; //0xbffffb38 unsigned long esp; //0xbffffa20 unsigned long ebx; //0x4213030c unsigned long edx; //0xbffffc00 unsigned long ecx; //0x08049752 unsigned long eax; //0xbffffb2c unsigned long trapno; //0x00000001 unsigned long err; //0x00000000 unsigned long eip; //0x08048570 unsigned short cs, __csh; //0x23,0 unsigned long eflags; //0x00000346 unsigned long esp_at_signal; //0xbffffa20 unsigned short ss, __ssh; //0x2b,0 struct _fpstate * fpstate; //0x00000000 unsigned long oldmask; //0x00000000 unsigned long cr2; //0x00000000 }; 内核在应用层的堆栈上安了一个帧后,当一返回到应用态的时候就跳到信号处理函数test去执行了。 此时图一 ①,应用层的堆栈多了一个帧,如下: ********************************************************************************** 图一 (内存高址) +--------------------------------------+ | ... | +--------------------------------------+ | char retcode[8] | 8个字节 +--------------------------------------+ | long extramask[_NSIG_WORDS-1]; | +--------------------------------------+ | struct _fpstate fpstate; | +--------------------------------------+ | struct sigcontext sc; | +--------------------------------------+ <---------esp指向这里 ③ | int sig; | +--------------------------------------+ <---------esp指向这里 ② | char *pretcode; | +--------------------------------------+ <---------esp指向这里 ① | ... | +--------------------------------------+ (内存低址) ********************************************************************************** 由于内核是让应用程序跳到信号处理函数的,所以不象一般的调用会把当前的EIP压入堆栈,所以现在 esp指向的pretcode的值将来信号处理完就返回到那里去了。此时ESP情况如图一 ② 的情况 当test信号处理函数完成时候,将返回到frame->pretcode也就是0x42029098的地址去执行,在这里0x42029098地址代码如下: 0x42029098 <__restore>: pop %eax //弹出frame->sig,这里为10 0x42029099 <__restore+1>: mov $0x77,%eax 0x4202909e <__restore+6>: int $0x80 //请求sys_sigreturn系统调用 当执行完以上三条指令的时候,应用层的堆栈就变成了 ③ 的情况了。 忽略切入内核的细节,sys_sigreturn系统调用被调用。下面是该函数的实现细节。 asmlinkage int sys_sigreturn(unsigned long __unused) { struct pt_regs *regs = (struct pt_regs *) &__unused; struct sigframe *frame = (struct sigframe *)(regs->esp - 8);//取得frame地址,-8是为了补上ret和pop //这两个指令分别弹出的pretcode和sig //看看上面的图会更清楚 sigset_t set; int eax; if (verify_area(VERIFY_READ, frame, sizeof(*frame))) goto badframe; if (__get_user(set.sig[0], &frame->sc.oldmask) || (_NSIG_WORDS > 1 && __copy_from_user(&set.sig[1], &frame->extramask, sizeof(frame->extramask)))) goto badframe; sigdelsetmask(&set, ~_BLOCKABLE); spin_lock_irq(¤t->sigmask_lock); current->blocked = set; recalc_sigpending(current); spin_unlock_irq(¤t->sigmask_lock); /*把frame保存的一些信息恢复出来,修改regs一些寄存器*/ if (restore_sigcontext(regs, &frame->sc, &eax)) goto badframe; return eax; badframe: force_sig(SIGSEGV, current); return 0; } restore_sigcontext函数好象也没有什么好说的,等到sys_sigreturn函数返回,regs的一些寄存器又恢复到信号来之前的值了。 所以等到内核态在返回到应用态的时候,又恢复到原来的地址去执行了。 参考资料: linux 2.4.18 kernel src -------the end--------