-fstack-protector-strong 是最简单有效的栈溢出检测手段,它通过在函数栈帧插入 canary 值并在返回前校验来立即终止被覆盖的程序;c++标准不强制边界检查,故 strcpy、裸数组访问等合法但危险的操作默认无保护,溢出可能静默破坏邻近变量而非崩溃;该选项性能影响极小且全平台支持,而 -d_fortify_source=2 在编译期重写部分标准函数实现长度校验(需数组而非指针+足够优化级),addresssanitizer 则提供精准的开发期运行时检测(支持栈/堆/全局区越界,开销约2倍,仅用于测试)。

编译时加 -fstack-protector-strong 是最简单有效的检测手段,它能在绝大多数栈溢出发生时立即终止程序,而不是让漏洞静默运行。
为什么默认不检测?
C++ 语言标准不强制边界检查,strcpy、gets、裸数组下标访问等操作在语法上完全合法,编译器不会报错。运行时是否崩溃取决于溢出是否踩到不可访问内存(如页边界)或关键控制数据(如返回地址)。很多溢出只是“悄悄覆盖邻近变量”,程序继续跑,但逻辑已错——这才是最危险的。
- 未启用保护时,
char buf[8]; strcpy(buf, "123456789");不报错也不崩溃,只默默破坏后续变量 -
-fstack-protector-strong会在函数栈帧中插入 canary 值,一旦溢出覆盖到它,函数返回前就 abort - 该选项对性能影响极小(仅多一次内存读+比较),且 GCC/Clang 全平台支持
-D_FORTIFY_SOURCE=2 能捕获哪些溢出?
它不是运行时检测,而是在编译期重写部分标准函数调用,插入隐式长度校验。适用于明确知道目标缓冲区大小的场景,比如 snprintf、memcpy、strcpy 等。
- 生效前提:必须用
sizeof直接传入缓冲区大小,例如strcpy(dest, src)且dest是数组(非指针) - 典型触发:
char buf[10]; strcpy(buf, long_str);→ 编译期警告 + 运行时 abort - 不生效情况:
char* p = new char[10]; strcpy(p, src);(动态分配,sizeof(p)是指针大小) - 必须配合
-O2或更高优化级才能启用重写逻辑
运行时检测:AddressSanitizer(ASan)怎么用?
它是目前最实用的开发期检测工具,能精准定位溢出位置、方向(读/写)、越界字节数,甚至显示栈帧和源码行。
- 编译加
-fsanitize=address -g,链接时也需同样标志,运行时报错带完整堆栈 - 能检测栈、堆、全局区溢出,包括
std::vector::operator[]越界(需开-D_GLIBCXX_DEBUG) - 性能开销约 2×,内存占用翻倍,**仅用于开发/测试,不可上线**
- 注意:ASan 与
-fstack-protector可共存,但优先用 ASan 定位,用 canary 防御线上
容易被忽略的关键点
所有检测机制都依赖“溢出实际发生”——如果输入永远不超长,它们就什么也抓不到。真实风险常藏在边界条件里:
-
strncpy(dest, src, sizeof(dest)-1)后漏掉dest[sizeof(dest)-1] = '\0',会导致后续strlen越界读 -
std::string虽安全,但调用.c_str()后传给 C 函数,仍要自己保证目标缓冲区够大 - 结构体成员对齐可能让看似独立的数组实际相邻,溢出一个会影响另一个字段
- 第三方库(如 OpenSSL、libpng)内部若用裸数组+不安全函数,你的编译选项管不了它
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!











