【发布时间】:2021-03-25 10:30:41
【问题描述】:
考虑这个演示程序:
#include <string.h>
#include <unistd.h>
typedef struct {
int a;
int b;
int c;
} mystruct;
int main() {
int TOO_BIG = getpagesize();
int SIZE = sizeof(mystruct);
mystruct foo = {
123, 323, 232
};
mystruct bar;
memset(&bar, 0, SIZE);
memcpy(&bar, &foo, TOO_BIG);
}
我编译这两种方式:
gcc -O2 -o buffer -Wall buffer.cgcc -g -o buffer_debug -Wall buffer.c
即第一次启用优化,第二次使用调试标志且没有优化。
首先要注意的是编译时没有警告,尽管getpagesize 返回的值会导致memcpy 的缓冲区溢出。
其次,运行第一个程序会产生:
*** buffer overflow detected ***: terminated
Aborted (core dumped)
而第二个产生
*** stack smashing detected ***: terminated
Aborted (core dumped)
或者,你必须相信我,因为我无法使用演示程序重现这一点,有时根本没有警告。该程序甚至没有中断,它正常运行。这是我在一些更复杂的代码中遇到的一种行为,这使得调试变得困难,直到我意识到发生了缓冲区溢出。
我的问题是:为什么会有两种不同的行为和不同的构建标志?为什么有时在构建为调试版本时执行时没有错误,但在构建优化时总是出错?
【问题讨论】:
-
编译器不需要为未定义的行为生成诊断。同样,带有 UB 的程序可能会“正常”运行或产生运行时错误或其他问题。
-
缓冲区溢出导致未定义行为,未定义行为未定义。
-
一个具有未定义行为的程序不需要每次都做同样的事情:那将是定义的行为。并且不同的代码不需要具有相同的未定义效果。
-
@jumbot 您的问题并不愚蠢,只是“为什么在 X 情况下与 Y 情况下存在不同的未定义行为”有很多变体。例如,我记得一个问题,有人问他们为什么在尝试写入字符串文字而不是 segfualt 后会出现总线错误。这个问题听起来可能不同,但每个问题的答案都是一样的:未定义的行为是未定义的。你可以在运行这个程序时让龙侵入你的房子,它不会违反 C 标准,因为它是未定义的行为,任何事情都被允许
-
这是一个很好的问题。不过,我不是那种“你的猫会变绿”的学校,这很愚蠢。
标签: c gcc memcpy buffer-overflow