4. 使用可替换信号栈&&sigaltstack函数
使用可替换栈优化后的程序如下:
#include <stdio.h> #include <string.h> #include <stdlib.h> #include <signal.h> #include <unistd.h> #include <time.h> #include <sys/types.h> #include <execinfo.h> void blackbox_handler(int sig) { printf("Enter blackbox_handler: "); printf("SIG name is %s, SIG num is %d\n", strsignal(sig), sig); // 打印堆栈信息 printf("Stack information:\n"); int j, nptrs; #define SIZE 100 void *buffer[100]; char **strings; nptrs = backtrace(buffer, SIZE); printf("backtrace() returned %d addresses\n", nptrs); strings = backtrace_symbols(buffer, nptrs); if (strings == NULL) { perror("backtrace_symbol"); exit(EXIT_FAILURE); } for(j = 0; j < nptrs; j++) printf("%s\n", strings[j]); free(strings); _exit(EXIT_SUCCESS); } long count = 0; void bad_iter() { int a, b, c, d; a = b = c = d = 1; a = b + 3; c = count + 4; d = count + 5 * c; count++; printf("count:%ld\n", count); bad_iter(); } int main() { stack_t ss; struct sigaction sa; ss.ss_sp = malloc(SIGSTKSZ); ss.ss_size = SIGSTKSZ; ss.ss_flags = 0; if (sigaltstack(&ss, NULL) == -1) { return EXIT_FAILURE; } memset(&sa, 0, sizeof(sa)); sa.sa_handler = blackbox_handler; sigemptyset(&sa.sa_mask); sa.sa_flags = SA_ONSTACK; if (sigaction(SIGSEGV, &sa, NULL) < 0) { return EXIT_FAILURE; } bad_iter(); while(1); return EXIT_SUCCESS; } |
编译 gcc –rdynamic blackbox_overflow.c 后运行,输出为:
... ... count:261989 count:261990 count:261991 count:261992 Enter blackbox_handler: SIG name is Segmentation fault, SIG num is 11 Stack information: backtrace() returned 100 addresses ./a.out(blackbox_handler+0x63) [0x400c30] /lib/x86_64-linux-gnu/libc.so.6(+0x36ff0) [0x7f6e68d74ff0] /lib/x86_64-linux-gnu/libc.so.6(_IO_file_write+0xb) [0x7f6e68db7e0b] /lib/x86_64-linux-gnu/libc.so.6(_IO_do_write+0x7c) [0x7f6e68db931c] /lib/x86_64-linux-gnu/libc.so.6(_IO_file_xsputn+0xb1) [0x7f6e68db84e1] /lib/x86_64-linux-gnu/libc.so.6(_IO_vfprintf+0x7fa) [0x7f6e68d8879a] /lib/x86_64-linux-gnu/libc.so.6(_IO_printf+0x99) [0x7f6e68d92749] ./a.out(bad_iter+0x7a) [0x400d62] ./a.out(bad_iter+0x84) [0x400d6c] ./a.out(bad_iter+0x84) [0x400d6c] ./a.out(bad_iter+0x84) [0x400d6c] ./a.out(bad_iter+0x84) [0x400d6c] ./a.out(bad_iter+0x84) [0x400d6c] ... ... |
可以看到,使用可替换栈以后,虽然同样栈溢出了,但是我们的黑匣子程序还是起作用了。所以这种优化是有效的。下面我们来看优化的代码。
可以看到我们的代码中使用了sigaltstack函数,该函数的作用就是在在堆中为函数分配一块区域,作为该函数的栈使用。所以,虽然递归函数将系统默认的栈空间用尽了,但是当调用我们的信号处理函数时,使用的栈是它实现在堆中分配的空间,而不是系统默认的栈,所以它仍旧可以正常工作。
该函数函数原型如下:
#include <signal.h>
int sigaltstack(const stack_t *ss, stack_t *oss);
该函数两个个参数为均为stack_t类型的结构体,先来看下这个结构体:
typedef struct {
void *ss_sp; /* Base address of stack */
int ss_flags; /* Flags */
size_t ss_size; /* Number of bytes in stack */
} stack_t;
要想创建一个新的可替换信号栈,ss_flags必须设置为0,ss_sp和ss_size分别指明可替换信号栈的起始地址和栈大小。系统定义了一个常数SIGSTKSZ,该常数对极大多数可替换信号栈来说都可以满足需求,MINSIGSTKSZ规定了可替换信号栈的最小值。
如果想要禁用已存在的一个可替换信号栈,可将ss_flags设置为SS_DISABLE。
而sigaltstack第一个参数为创建的新的可替换信号栈,第二个参数可以设置为NULL,如果不为NULL的话,将会将旧的可替换信号栈的信息保存在里面。函数成功返回0,失败返回-1.
一般来说,使用可替换信号栈的步骤如下:
在内存中分配一块区域作为可替换信号栈
使用sigaltstack()函数通知系统可替换信号栈的存在和内存地址
使用sigaction()函数建立信号处理函数的时候,通过将sa_flags设置为SA_ONSTACK来告诉系统信号处理函数将在可替换信号栈上面运行。