【缺陷周话】第41期:忽略返回值
字数 1843 2025-08-18 11:38:45

忽略返回值缺陷分析与防范指南

1. 缺陷概述

忽略返回值(Unchecked Return Value)是指程序调用函数后未对其返回值进行适当检查,导致无法正确处理函数执行状态的一种安全缺陷。根据CWE分类,该缺陷被编号为CWE-252。

1.1 典型表现

  • 调用重要函数后未检查其返回值
  • 假设函数调用总是成功而不做错误处理
  • 仅使用返回值作为数据而忽略其状态指示功能

1.2 危害性

忽略函数返回值可能导致多种安全问题:

  1. 信息泄露:攻击者可能利用未处理的错误状态获取敏感信息
  2. 拒绝服务:程序可能因未处理的错误而崩溃或进入异常状态
  3. 逻辑缺陷:程序可能在不正确的状态下继续执行,产生未定义行为
  4. 安全绕过:安全检查函数的返回值被忽略可能导致安全机制被绕过

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);
}

修复要点

  1. 检查fgets()返回值是否为NULL
  2. 在失败时进行适当处理(此处为打印错误并退出)
  3. 仅在确认成功后才使用读取的数据

4. 检测与防范

4.1 检测方法

  1. 静态代码分析:使用专业工具检查函数返回值是否被检查

    • 示例工具:奇安信代码卫士
    • 检测效果:可标记未检查返回值的中风险缺陷
  2. 代码审查:重点关注以下函数调用:

    • 内存操作函数(malloc/calloc/realloc等)
    • I/O操作函数(fgets/read/write等)
    • 字符串处理函数(strcpy/strcat/sprintf等)
    • 安全相关函数(身份验证、授权检查等)

4.2 防范措施

  1. 基本准则

    • 对所有可能失败的函数调用检查返回值
    • 根据返回值采取适当的错误处理措施
    • 记录或报告关键错误
  2. 特定场景处理

    • 内存分配:检查malloc/calloc/realloc是否返回NULL
    • 文件I/O:检查fopen/read/write等操作是否成功
    • 字符串操作:检查sprintf/snprintf等是否发生截断
    • 系统调用:检查返回值及errno
  3. 错误处理模式

    • 立即返回错误
    • 使用默认安全值
    • 记录错误并继续(仅适用于非关键操作)
    • 程序终止(对于致命错误)

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++程序中常见但危险的安全缺陷,可能导致信息泄露、拒绝服务等严重后果。通过以下措施可有效防范:

  1. 培养检查返回值的编码习惯
  2. 使用静态分析工具进行自动化检测
  3. 对高风险函数特别关注
  4. 建立统一的错误处理机制
  5. 在代码审查中重点关注返回值检查

良好的返回值检查习惯是编写健壮、安全软件的基础,应作为开发人员的基本素养加以重视和实践。

忽略返回值缺陷分析与防范指南 1. 缺陷概述 忽略返回值(Unchecked Return Value)是指程序调用函数后未对其返回值进行适当检查,导致无法正确处理函数执行状态的一种安全缺陷。根据CWE分类,该缺陷被编号为CWE-252。 1.1 典型表现 调用重要函数后未检查其返回值 假设函数调用总是成功而不做错误处理 仅使用返回值作为数据而忽略其状态指示功能 1.2 危害性 忽略函数返回值可能导致多种安全问题: 信息泄露 :攻击者可能利用未处理的错误状态获取敏感信息 拒绝服务 :程序可能因未处理的错误而崩溃或进入异常状态 逻辑缺陷 :程序可能在不正确的状态下继续执行,产生未定义行为 安全绕过 :安全检查函数的返回值被忽略可能导致安全机制被绕过 2. 典型案例分析 2.1 示例函数:fgets() 成功时 :返回第一个参数 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 缺陷代码示例 问题 :直接使用 fgets() 的输入而不检查是否成功读取。 3.2 修复方案 修复要点 : 检查 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 返回值检查模式示例 6. 总结 忽略返回值是C/C++程序中常见但危险的安全缺陷,可能导致信息泄露、拒绝服务等严重后果。通过以下措施可有效防范: 培养检查返回值的编码习惯 使用静态分析工具进行自动化检测 对高风险函数特别关注 建立统一的错误处理机制 在代码审查中重点关注返回值检查 良好的返回值检查习惯是编写健壮、安全软件的基础,应作为开发人员的基本素养加以重视和实践。