GCC如何检测堆栈缓冲区溢出问题

10

在gcc中有一个选项-fstack-protector-strong用于检测堆栈破坏。然而,它并不能始终检测到堆栈缓冲区溢出。对于第一个函数func,当我输入一个10个字符以上的字符串时,程序并不总是崩溃。我的问题是有没有一种方法可以检测堆栈缓冲区溢出。

void func()
{
    char array[10];
    gets(array);
}

void func2()
{
    char buffer[10];
    int n = sprintf(buffer, "%s", "abcdefghpapeas");
    printf("aaaa [%d], [%s]\n", n, buffer);
}

int main ()
{
   func();
   func2();
}

4
为什么你给这个问题打上C++的标签,然后又写出那样的代码? - David Heffernan
在第一时间避免这种情况不是更合乎逻辑吗?例如,使用snprintf(buffer, sizeof(buffer), "%s", ...) - mvp
2
它的存在是为了检测堆栈是否被破坏,而不是为了检测缓冲区是否溢出。请使用更多字符。 - Hans Passant
1
你认为是否存在未记录的-fstack-protector-strong-but-i-mean-for-real-now选项或其他类似选项吗? - n. m.
1
栈和缓冲区是两个完全不相关的东西。 - Adam
显示剩余2条评论
4个回答

9
"

堆栈溢出要么难以检测,要么非常昂贵 - 选择你的毒药。

简而言之,当你有这个:

"
 char a,b;
 char *ptr=&a;
 ptr[1] = 0;

如果这样做在技术上是合法的:栈中分配了属于函数的空间。但这非常危险。
因此,解决方法可能是在a和b之间添加一个间隙,并用模式填充它。但是,有些人确实像上面那样编写代码。因此,编译器需要检测到这一点。
或者,我们可以创建一个字节位图来显示您的代码实际分配的所有字节,然后仪器化所有代码以针对此地图进行检查。非常安全,相当慢,会膨胀您的内存使用率。积极的方面是有助于此类问题的工具(如Valgrind)。
明白我想表达什么吗?
结论:在C语言中,由于语言和API通常过于松散,因此没有好的方法自动检测许多内存问题。解决方案是将代码移入辅助函数中,严格检查其参数,始终执行正确操作并具有良好的单元测试覆盖率。
如果有选择,请始终使用函数的snprintf()版本。如果旧代码使用不安全版本,请更改。

1
编程语言并不会出错,程序员才会。 :) - vesperto
2
编写该语言和库的程序员很草率,这反映在产品中。 - Aaron Digulla

2

My question is where there is a way to detect stack buffer overflow...

void func()
{
    char array[10];
    gets(array);
}

void func2()
{
    char buffer[10];
    int n = sprintf(buffer, "%s", "abcdefghpapeas");
    printf("aaaa [%d], [%s]\n", n, buffer);
}
因为您正在使用GCC,所以可以使用FORTIFY_SOURCES。
FORTIFY_SOURCE使用“更安全”的高风险函数变体,例如memcpy,strcpy和gets。编译器在能够推断目标缓冲区大小时使用更安全的变体。如果复制超过目标缓冲区大小,则程序调用abort()。如果编译器无法推断目标缓冲区大小,则不使用“更安全”的变体。
为了进行测试而禁用FORTIFY_SOURCE,应使用-U_FORTIFY_SOURCE或-D_FORTIFY_SOURCE = 0编译程序。
C标准通过ISO/IEC TR 24731-1,边界检查接口提供了“更安全”的函数。在符合规范的平台上,您可以简单地调用gets_ssprintf_s。它们提供一致的行为(例如始终确保字符串以NULL结尾)和一致的返回值(例如成功时返回0或errno_t)。
不幸的是,gcc和glibc不符合C标准。glibc的维护者之一Ulrich Drepper称边界检查接口为"可怕低效的BSD垃圾",并未添加。希望将来会有所改变。

2

这是不正确的。Valgrind是一个很好的工具,但它不能检测基于堆栈的缓冲区溢出。请参见此处:https://dev59.com/I10a5IYBdhLWcg3w48FP#29842977。 - thatWiseGuy
AddressSanitizer会是更好的选择,它更加精确,例如可以检测堆栈溢出。 - yugr
Valgrind可以检测堆栈溢出,例如,“线程#1中的堆栈溢出:无法增加堆栈...”,也许您需要调试信息。 - Elliott Beach

0
首先,不要使用gets。现在几乎每个人都知道gets可能会出现的所有安全和可靠性问题。但是由于历史原因,它仍然被包含在这里,因为它是一个非常糟糕的编程示例。
让我们来看看代码中的所有问题:
// Really bad code
char line[100];
gets(line);

由于 gets 函数不做边界检查,长度超过100个字符的字符串会覆盖内存。如果你很幸运程序只会崩溃,或者它可能表现出奇怪的行为。

gets 函数是如此糟糕,以至于 GNU GCC 链接器在使用它时会发出警告。

/tmp/ccI5WJ5m.o(.text+0x24): In function `main':
: warning: the `gets' function is dangerous and should not be used.

使用assert保护数组访问

C/C++不会进行边界检查。

例如:

int data[10]

i = 20
data[20] = 100 //Memory Corruption

使用assert函数来检查以上代码

#include<assert.h>


int data[10];
i=20

assert((i >= 0) && (i < sizeof(data) / sizeof(data[0]))); // throws 

data[i] = 100

数组溢出是最常见的编程错误之一,而且极其令人沮丧难以定位。这段代码虽然不能消除它们,但它确实会使有缺陷的代码在早期崩溃,从而使问题变得更容易找到。

并使用snprintf(buffer, sizeof(buffer), "%s", "abcdefghpapeas")和一些工具,如valgrind或GDB。

希望这能帮助你。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接