菜鸟笔记
提升您的技术认知

gcc-stack-protector机制

GCC “stack smashing detected”机制

相信使用C/C++语言开发软件的程序猿们都经历过‘栈溢出’的问题。‘栈溢出’问题通常十分的隐蔽,有的时候问题复现也十分的困难。每当软件出现莫名其妙的问题时,总是有人怀疑是不是栈溢出了,但是问题的排查又十分的困难,所以,‘栈溢出’就是广大C/C++开发者的噩梦。大家不禁要问有没有通用的方法来避免或者来检测’栈溢出‘问题呢?其实,’栈溢出‘问题有的时候是没有办法避免的,即使是经验丰富的程序猿同样可以陷入’栈溢出‘的陷阱,相信大家都听说过很多知名的软件系统由于‘栈溢出’而导致的各类安全问题。为了减少‘栈溢出’的出现几率,我们平时在编写代码的时候要时刻注意栈上的缓冲区的使用方式,熟悉关于栈缓冲区的各种操作API(使用最为频繁的就是字符串操作函数)的正确使用方式。但是,人总有犯错误的时候,那有没有一种机制来检测‘栈溢出’呢?今天要说的就是gcc编译器提供关于stack保护的各种机制。
首先,来看一个比较明显的栈溢出的代码段,本段代码的编译平台为:Ubuntu14.04 Gcc的版本为4.8.4。代码内容如下:

#include <string.h>
#include <stdio.h>

int main(void)
{
    char array[5] = {
  0};

    strcpy(array, "stackoverflow");

    return 0;
}

该段代码,首先定义了一个char型数组,数组大小为5,然后我们使用strcpy函数拷贝字符串“stackvoerflow”到array缓冲区,很明显array缓冲区溢出了,稍后会介绍strcpy的使用方式。

然后,我们编译运行,结果如下:

lhl@lhl-Aspire-4930:~/技术总结/linux/gcc$ ./buscore 
*** stack smashing detected ***: ./buscore terminated
已放弃 (核心已转储)

可以看到程序崩溃了,而且还有一些提示信息:

*** stack smashing detected ***: ./buscore terminated

该提示信息就是gcc提供的栈溢出保护机制在检测到程序存在缓冲区溢出时所打印出来的提示信息。那gcc提供栈溢出保护机制是什么呢?

通过查看gcc的使用手册,我们可以知道该报机制为gcc的-fstack-protector一系列选项所提供的缓冲区溢出检测机制。下面为该机制的原理介绍:

当-fstack-protector启用时,当其检测到缓冲区溢出时(例如,缓冲区溢出攻击)时会立即终止正在执行的程序,并提示其检测到缓冲区存在的溢出的问题。这种机制是通过在函数中的易被受到攻击的目标上下文添加保护变量来完成的。这些函数包括使用了allcoa函数以及缓冲区大小超过8bytes的函数(此处不是很明白,上面的例子缓冲区大小为5同样触发了该机制)。这些保护变量在进入函数的时候进行初始化,当函数退出时进行检测,如果某些变量检测失败,那么会打印出错误提示信息并且终止当前的进程。

注意:在测试过程中,当缓冲区的大小小于8bytes同样触发了该机制。在函数中使用了alloca函数后,缓冲区溢出后但没有触发该机制,不知为何?
>

    char *stack_ptr = alloca(0);

    char *str = "hello, world";

    strcpy(stack_ptr, str); 

    printf("stack_ptr = %s\n", stack_ptr);
    上面的代码并不能触发该机制?

那问题来了,-fstack-protector真的是万能的吗?它真的可以检测一切栈溢出的问题吗?我们可以通过下面的例子测试一下:

#include <alloca.h>
#include <string.h>
#include <stdio.h>

typedef struct
{
    char str[2];
}overflow_t;
int main(void) 
{
char *str = "welcom to China";

    overflow_t v;  

    printf("sizoef(v) = %d\n", sizeof(v));

    memcpy(&v, str, strlen(str));

    printf("v.str = %s\n", v.str);
}
gcc buscore.c -o buscore,我们编译运行结果如下:
sizoef(v) = 2
v.str = welcom to China

我们看到程序正常执行了,并没有触发栈溢出检测?这是为什么呢?通过进一步查阅gcc手册,发现其还提供一个-fstack-protector-all选项,其解释为:

其功能类似于-fstack-protector,但是其为所有的函数都进行栈溢出检测。重新编译程序,并加上-fstack-protector-all选项,然后运行结果如下:

sizoef(v) = 2
v.str = welcom to China
* stack smashing detected *: ./buscore terminated
已放弃 (核心已转储)。可以看到进程成功的检测到了栈溢出。

所以,我们在开发软件时,最好加上-fstack-protector-all选项,这很大一部分栈溢出问题就会被探测到。

同时我们也应该注意到,当进程由于栈溢出崩溃时,会产生coredump文件,对于coredump的配置可以参见
Android系统coredump这篇文章。
那怎么根据coredump文件定位问题呢?上面介绍-fstack-protector机制的原理时,说过其会在函数退出时进行检测,这样我们就可以根据coredump中的函数栈信息定位到发送栈溢出的函数,这样问题的范围就大大缩小了,我们可以专注于查看出问题的函数,然后定位出哪里存在栈溢出。