C 语言缓冲区溢出与安全
更新时间:2026-08-26。本文是
languages/c/主题高手层文档。
本文要回答的问题
- 缓冲区溢出是怎么发生的?它为什么能"改写程序"?
- 哪些函数是重灾区?安全的替代是什么?
- 栈保护、ASLR 这些机制在防什么?
一、原理
写入的字节数超过了分配的空间,覆盖了相邻内存。栈上的局部数组越界写,会一路覆盖返回地址——函数返回时跳到攻击者控制的位置,程序行为就被劫持了。
c
char buf[16];
strcpy(buf, "很长很长的字符串……"); /* 溢出了 */二、危险函数与替代
| 危险函数 | 问题 | 安全替代 |
|---|---|---|
gets | 完全不限长度 | fgets(buf, size, stdin) |
strcpy | 不检查目标容量 | strncpy/snprintf/strlcpy |
sprintf | 不检查目标容量 | snprintf(buf, size, ...) |
strcat | 不检查总容量 | strncat 或先算长度 |
scanf("%s") | 无限读入 | 用 %Ns 限制或 fgets |
通用原则:给缓冲区操作用"带长度的版本",且长度传的是缓冲区真实容量。
三、现代缓解机制
| 机制 | 作用 |
|---|---|
| 栈保护(canary) | 返回地址前放随机哨兵值,被覆盖则崩溃告警(-fstack-protector) |
| ASLR | 栈/堆/库地址随机化,让固定地址攻击失效 |
| PIE | 可执行文件本身随机加载 |
| NX | 栈数据不可执行 |
这些机制让"溢出"从"轻易得手"变成"多半崩溃"——但正确写法才是根本。
实测:栈溢出触发栈保护
c
void overflow(void) {
char buf[8];
strcpy(buf, "this string is way too long for the buffer"); // 溢出
}实测(gcc 默认 -fstack-protector):
text
Segmentation fault ./sit栈保护(canary)生效了:strcpy 写入超过 8 字节,覆盖了返回地址前的金丝雀值(canary),函数返回前检测到 canary 被破坏,立即崩溃告警——而不是静默地跳转到攻击者控制的地址。
注意:canary 只能检测溢出、防止静默利用,不能"修复"溢出——崩溃比被劫持好,但正确写法(带容量的
strncpy/snprintf)才是根本。
四、检测与排查
- ASan:
-fsanitize=address运行时抓越界读写(含堆/栈),见 ASan 内存检测。 - Valgrind:内存错误检测,见 Valgrind 内存检测。
- 栈溢出崩溃的 gdb 分析,见 段错误与 core dump。
五、与入门层的衔接
一句话总结
缓冲区溢出是"写超了空间、劫持了流程":拒绝 gets/strcpy/sprintf 三兄弟、一律用带容量的函数,再配栈保护与 ASan 兜底——安全从源头写法开始。
本文已完成填充:示例代码与实测数据已补齐。