第4章:字符串函数的安全隐患——strcpy、strcat、sprintf 的缓冲区溢出问题
说实话,C语言的字符串函数,是我见过最"温柔"的陷阱。
它们用起来特别顺手,但一不留神,就能让你的程序原地爆炸。我入行第三年,就因为在产品代码里用了一个 strcpy,导致现场设备死机,客户差点投诉到总部。从那以后,我对这几个函数的态度就变成了:能用,但必须带着"镣铐"用。
4.1 缓冲区溢出:到底是怎么发生的?
先看一个最经典的例子:
#include <string.h>
void vulnerable() {
char buf[8];
char *src = "Hello, World!";
strcpy(buf, src); // 源字符串长度 13,目标缓冲区只有 8 字节
}
这段代码能编译通过,运行也不报错。但 buf 只有 8 字节,src 却塞了 13 个字符进去。多出来的 5 个字节,直接写到了 buf 后面的内存里。
这就是缓冲区溢出——说白了,就是往一个杯子里倒了一桶水,水全洒外面了。
4.2 strcpy:最经典的"坑王"
strcpy 不会检查目标缓冲区的大小。它只管复制,直到遇到源字符串的 '\0' 为止。
char dest[10];
char *src = "This is a very long string that will overflow!";
strcpy(dest, src); // 溢出!dest 只有 10 字节
我个人习惯,在嵌入式开发中,永远不用 strcpy。标准库提供了 strncpy,虽然它也有自己的小毛病,但至少能限制拷贝长度。
strncpy 并手动在末尾加 '\0'。因为 strncpy 在源字符串长度 ≥ n 时,不会自动添加终止符。
char dest[10];
strncpy(dest, src, sizeof(dest) - 1);
dest[sizeof(dest) - 1] = '\0'; // 确保字符串终止
4.3 strcat:拼接字符串的"隐形杀手"
strcat 的问题和 strcpy 一样——它不检查目标缓冲区剩余空间。
char buf[16] = "Hello";
strcat(buf, ", this is a very long message!"); // 溢出!
你想想看,buf 里已经放了 5 个字符,再拼接 30 多个字符进去,总长度远超 16 字节。多出来的内容直接覆盖了栈上的其他数据。
我在项目中遇到过这种情况:一个日志拼接函数用了 strcat,结果日志内容稍微长一点,就把相邻的变量给覆盖了。排查了整整两天,最后发现是 strcat 惹的祸。
替代方案:用 strncat,并计算剩余空间。
char buf[16] = "Hello";
size_t remaining = sizeof(buf) - strlen(buf) - 1;
strncat(buf, ", this is a very long message!", remaining);
4.4 sprintf:格式化输出的"定时炸弹"
sprintf 比前两个更隐蔽。它不光复制字符串,还能格式化输出。但同样,它不检查目标缓冲区大小。
char buf[32];
int val = 123456789;
sprintf(buf, "Value: %d, and some extra text here...", val); // 溢出!
格式化后的字符串长度,取决于参数的值。你没法在写代码时精确预判。比如 %d 输出一个 int,可能是 1 位,也可能是 10 位(负数加最大值)。
snprintf,它接受缓冲区大小参数,并且保证不会写出超过 n-1 个字符。
char buf[32];
snprintf(buf, sizeof(buf), "Value: %d, and some extra text here...", val);
嗯,这里要注意:snprintf 的返回值是"如果缓冲区足够大,本应写入的字符数"。所以你可以用它来检测是否发生了截断:
int needed = snprintf(buf, sizeof(buf), "Value: %d", val);
if (needed >= sizeof(buf)) {
// 数据被截断了,需要处理
}
4.5 一张图看懂这三个函数的"坑"
下面这张 SVG 图,把三个函数的核心问题画清楚了:
4.6 避坑指南:我曾经踩过的雷
我曾经在一个嵌入式项目中,用 sprintf 拼接网络数据包。数据包格式固定,我以为长度不会超。结果某次固件升级后,某个字段的值变大了,直接撑爆了缓冲区,导致整个协议栈崩溃。
从那以后,我给自己定了几条铁律:
- 绝不使用
strcpy、strcat、sprintf——不管场景多简单。 - 所有字符串操作,必须指定缓冲区大小——用
sizeof或显式常量。 - 每次操作后,检查返回值或手动加
'\0'——防止截断后没有终止符。 - 在代码审查中,把这三个函数列为"一票否决"项——看到就直接打回。
4.7 总结
缓冲区溢出,是 C 语言里最经典、也最致命的安全问题之一。strcpy、strcat、sprintf 这三个函数,就是"罪魁祸首"。它们用起来方便,但代价可能是程序崩溃、安全漏洞,甚至产品召回。
我个人建议:别跟它们较劲。直接用安全版本,养成肌肉记忆。代码写多了你会发现,真正的高手不是用花哨的技巧,而是把基础的安全习惯刻进骨子里。