第97章 安全编码:如何避免常见的安全漏洞?
说实话,做了这么多年嵌入式开发,我见过太多因为安全漏洞翻车的项目了。有的被黑客远程控制了设备,有的因为缓冲区溢出导致系统崩溃,还有的因为整数溢出让计费系统出了大问题。嗯,今天我们就来聊聊C语言中那些最容易踩的安全坑。
缓冲区溢出:老生常谈但依然致命
缓冲区溢出可以说是C语言最经典的安全问题。我在项目中遇到过一位同事,他用strcpy()复制用户输入的数据,结果输入长度超过了缓冲区大小,直接覆盖了返回地址。程序崩溃还算好的,更可怕的是攻击者可以精心构造输入数据,让程序跳转到恶意代码。
char buf[64];
strcpy(buf, user_input); // 危险!没有长度检查
正确的做法是什么?我个人习惯用strncpy()或者snprintf(),并且一定要检查返回值。
char buf[64];
size_t len = strnlen(user_input, sizeof(buf));
if (len >= sizeof(buf)) {
// 输入过长,记录日志并拒绝
log_error("Input too long");
return -1;
}
strncpy(buf, user_input, sizeof(buf) - 1);
buf[sizeof(buf) - 1] = '\0'; // 确保以空字符结尾
整数溢出:看不见的陷阱
你想想看,一个int类型能表示的最大值是2147483647。如果在这个值上加1,会发生什么?会变成-2147483648。这就是整数溢出。
我曾经在做一个网络协议栈时,遇到过一个bug:计算数据包长度时,把两个16位整数相加,结果溢出了,导致分配了一个很小的缓冲区,但后续却往里面写入了大量数据。嗯,后果可想而知。
| 操作 | 风险 | 安全做法 |
|---|---|---|
| 加法 | 结果可能超过最大值 | 检查是否大于任一加数 |
| 乘法 | 结果可能溢出 | 使用更大类型或检查溢出 |
| 移位 | 移位位数可能过大 | 限制移位位数范围 |
int safe_add(int a, int b) {
if (a > INT_MAX - b) {
// 溢出,处理错误
return -1;
}
return a + b;
}
格式化字符串漏洞:printf也能被攻击?
很多人觉得printf很安全,其实不然。如果你这样写:
printf(user_input); // 危险!
攻击者可以在输入中包含%x、%n等格式说明符,读取栈上的数据,甚至写入任意内存地址。我见过一个案例,攻击者通过这个漏洞读取了服务器的私钥。
正确的做法是:
printf("%s", user_input); // 安全!指定格式字符串
空指针解引用:最基础的错误
这个错误太常见了。malloc返回NULL后直接使用,或者函数返回NULL指针没有检查。我记得有一次,一个同事在中断处理函数中调用了malloc,结果返回了NULL,但代码没有检查,直接解引用,系统直接挂掉。
我曾经在代码审查中,要求所有malloc后面必须跟NULL检查。一开始大家觉得麻烦,但后来发现这个习惯至少避免了三次线上事故。
资源泄露:内存、文件描述符、锁
C语言没有垃圾回收,所有资源都得手动管理。我见过一个项目,每次请求都malloc一块内存,但只在正常路径下free了,异常路径下直接return了。运行了几天后,内存耗尽,系统重启。
我的习惯是:在函数开头就规划好所有资源的释放路径。用goto语句集中处理错误,虽然goto名声不好,但在资源管理上确实好用。
void process_data(void) {
char *buf = malloc(1024);
FILE *fp = fopen("data.txt", "r");
if (!buf || !fp) {
goto cleanup;
}
// 处理数据...
cleanup:
if (buf) free(buf);
if (fp) fclose(fp);
}
安全编码的核心逻辑
下面这张图总结了安全编码的核心思路:
实际项目中的安全编码清单
最后,我整理了一份安全编码检查清单,每次提交代码前对照检查一遍:
- 所有输入都经过验证:长度、类型、范围
- 字符串操作使用安全函数:
strncpy、snprintf、strncat - 整数运算检查溢出:特别是加法和乘法
- malloc/calloc后检查NULL:不要假设内存分配一定成功
- 所有资源都有对应的释放代码:内存、文件、锁、套接字
- 格式化字符串使用固定格式:不要直接把用户输入当格式串
- 数组访问检查边界:特别是循环中的索引
- 函数指针调用前检查是否为NULL
说实话,安全编码不是一朝一夕能练成的。我刚开始写C语言时也犯过很多低级错误。但只要你养成习惯,每次写代码时多问自己一句「这里会不会被攻击?」,慢慢地就会形成肌肉记忆。
记住,安全不是功能,而是一种思维方式。你写的每一行代码,都可能成为攻击者的突破口。嗯,今天就聊到这里,希望这些经验对你有帮助。
公众号:蓝海资料掘金营,微信deep3321