【缺陷周话】第41期:忽略返回值
字数 1843 2025-08-18 11:38:45
忽略返回值缺陷分析与防范指南
1. 缺陷概述
忽略返回值(Unchecked Return Value)是指程序调用函数后未对其返回值进行适当检查,导致无法正确处理函数执行状态的一种安全缺陷。根据CWE分类,该缺陷被编号为CWE-252。
1.1 典型表现
- 调用重要函数后未检查其返回值
- 假设函数调用总是成功而不做错误处理
- 仅使用返回值作为数据而忽略其状态指示功能
1.2 危害性
忽略函数返回值可能导致多种安全问题:
- 信息泄露:攻击者可能利用未处理的错误状态获取敏感信息
- 拒绝服务:程序可能因未处理的错误而崩溃或进入异常状态
- 逻辑缺陷:程序可能在不正确的状态下继续执行,产生未定义行为
- 安全绕过:安全检查函数的返回值被忽略可能导致安全机制被绕过
2. 典型案例分析
2.1 示例函数:fgets()
char *fgets(char *buf, int bufsize, FILE *stream);
- 成功时:返回第一个参数
buf - 失败时:返回
NULL - 风险:如果不检查返回值,当读取发生错误时程序可能继续使用无效数据
2.2 漏洞实例
CVE-2019-12107
MiniUPnP MiniUPnPd 2.1版本中,upnpevents.c文件的upnpeventprepare函数未检查snprintf()返回值,导致信息泄露。
CVE-2019-9704
Vixie Cron 3.0pl1-133之前版本未检查calloc返回值,攻击者可通过大crontab文件造成拒绝服务。
CVE-2018-20216
QEMU中hw/rdma/vmw/pvrdmadevring.c文件未检查返回值,导致可能发生无限循环。
CVE-2018-16643
ImageMagick 7.0.8-4中多个文件未检查fputc返回值,可被特制图像文件利用造成拒绝服务。
3. 代码示例与修复
3.1 缺陷代码示例
// CWE252_Unchecked_Return_Value__char_fgets_01.c
void badSink(char * data)
{
/* FLAW: 未检查fgets()返回值 */
fgets(data, 100, stdin);
printLine(data);
}
问题:直接使用fgets()的输入而不检查是否成功读取。
3.2 修复方案
void goodSink(char * data)
{
/* FIX: 检查fgets()返回值 */
if (fgets(data, 100, stdin) == NULL) {
printLine("fgets failed!");
exit(1);
}
printLine(data);
}
修复要点:
- 检查
fgets()返回值是否为NULL - 在失败时进行适当处理(此处为打印错误并退出)
- 仅在确认成功后才使用读取的数据
4. 检测与防范
4.1 检测方法
-
静态代码分析:使用专业工具检查函数返回值是否被检查
- 示例工具:奇安信代码卫士
- 检测效果:可标记未检查返回值的中风险缺陷
-
代码审查:重点关注以下函数调用:
- 内存操作函数(malloc/calloc/realloc等)
- I/O操作函数(fgets/read/write等)
- 字符串处理函数(strcpy/strcat/sprintf等)
- 安全相关函数(身份验证、授权检查等)
4.2 防范措施
-
基本准则:
- 对所有可能失败的函数调用检查返回值
- 根据返回值采取适当的错误处理措施
- 记录或报告关键错误
-
特定场景处理:
- 内存分配:检查malloc/calloc/realloc是否返回NULL
- 文件I/O:检查fopen/read/write等操作是否成功
- 字符串操作:检查sprintf/snprintf等是否发生截断
- 系统调用:检查返回值及errno
-
错误处理模式:
- 立即返回错误
- 使用默认安全值
- 记录错误并继续(仅适用于非关键操作)
- 程序终止(对于致命错误)
5. 扩展知识
5.1 高风险函数列表
以下函数应特别注意返回值检查:
| 函数类别 | 典型函数 | 风险 |
|---|---|---|
| 内存分配 | malloc, calloc, realloc | 内存不足导致返回NULL |
| 文件操作 | fopen, fread, fwrite | I/O错误导致操作失败 |
| 字符串处理 | sprintf, strcpy, strcat | 缓冲区溢出风险 |
| 系统调用 | read, write, socket | 部分成功或完全失败 |
| 安全函数 | chmod, setuid, chroot | 权限变更可能失败 |
5.2 返回值检查模式示例
// 模式1:立即返回错误
int func() {
FILE *fp = fopen("file.txt", "r");
if (fp == NULL) {
return -1; // 或更具体的错误码
}
// ... 其他操作
}
// 模式2:使用默认值
void safeCopy(char *dst, const char *src, size_t len) {
if (strncpy(dst, src, len) == NULL) {
memset(dst, 0, len); // 失败时清零缓冲区
}
}
// 模式3:记录并继续
void logErrorAndContinue() {
int ret = someOperation();
if (ret != SUCCESS) {
syslog(LOG_ERR, "Operation failed: %d", ret);
// 继续执行非关键路径
}
}
6. 总结
忽略返回值是C/C++程序中常见但危险的安全缺陷,可能导致信息泄露、拒绝服务等严重后果。通过以下措施可有效防范:
- 培养检查返回值的编码习惯
- 使用静态分析工具进行自动化检测
- 对高风险函数特别关注
- 建立统一的错误处理机制
- 在代码审查中重点关注返回值检查
良好的返回值检查习惯是编写健壮、安全软件的基础,应作为开发人员的基本素养加以重视和实践。