如何解决stat校验目录后执行mkdir的TOCTOU类Coverity问题
Coverity检出的目录操作TOCTOU缺陷修复方案
缺陷产生的根因
现有实现是典型的TOCTOU(时间检查-使用时间不匹配)竞态:先通过stat做路径状态预判,再根据预判结果跨逻辑分支延迟执行mkdir、opendir操作,两次独立系统调用之间存在不受控的时间窗口——线程被调度挂起、其他进程介入篡改路径(比如把目标路径替换为指向系统敏感目录的恶意符号链接),都会让后续操作读写非预期路径,这也是Coverity检出该缺陷的核心判定依据。
核心修复逻辑
从根源上消除“预检查-延迟操作”的分离设计,把预判动作从流程最前端移到系统调用返回后即时执行,压缩所有可能的竞态窗口:
- 取消最前面的
stat预检查,直接发起mkdir(dir, S_IRWXU)调用。内核的mkdir是原子操作,不存在外部插入篡改的窗口 - 完全基于
mkdir的返回值和errno走分支,不依赖之前的预检查结果:mkdir返回0:说明目录由当前调用成功创建,直接进入后续流程即可mkdir返回-1时,仅对errno == EEXIST(目标路径已存在)的场景做即时校验,其他错误(无权限、上级目录缺失、磁盘满等)直接按失败处理
- 即时校验环节的安全加固:
- 用
lstat替代原有的stat做路径属性检查:lstat不会跟随符号链接,只要目标路径本身是恶意符号链接,就会直接识别并拒绝,避免被诱骗访问非预期目录 - 修正原代码的逻辑错配:原实现中
stat校验的是dir变量,opendir却传入硬编码的fabcSsrDbStorageDir,两个路径不一致,校验逻辑完全失效,修复时要统一使用同一个路径变量 - 拿到
opendir返回的有效目录句柄后,后续涉及该目录的文件操作,优先使用基于目录句柄的相对路径调用(如openat系列接口),彻底避免后续操作被路径篡改影响
- 用
修复示例代码
#include <sys/stat.h> #include <dirent.h> #include <errno.h> int ensure_storage_dir_valid(const char* dir) { struct stat stat_res; DIR* dir_fd = NULL; // 直接发起mkdir,不提前做状态预检查 int mk_ret = mkdir(dir, S_IRWXU); if (mk_ret == 0) { return 0; } // 非“路径已存在”类错误直接返回 if (errno != EEXIST) { return -1; } // 路径已存在,即时校验路径属性 if (lstat(dir, &stat_res) == -1) { return -1; } // 拒绝非目录类型的路径(包括符号链接) if (!S_ISDIR(stat_res.st_mode)) { return -1; } // 统一用同一个dir变量打开目录 dir_fd = opendir(dir); if (dir_fd == NULL) { return -1; } closedir(dir_fd); return 0; }
适配Coverity规则的注意点
- 整个流程中不存在跨分支的预检查逻辑,
mkdir和后续的路径校验是连续执行的系统调用,无开放时间窗口,不会被Coverity判定为TOCTOU缺陷 - 保留原有的目录权限设置(
S_IRWXU),仅给运行进程的当前用户开放读写执行权限,避免目录权限过松带来的其他安全问题 - 如果进程的umask会导致创建的目录权限偏离预期,可在创建成功后通过
fchmod校准权限,不要依赖默认的umask值
内容的提问来源于stack exchange,提问作者Red Gundu
相关产品推荐
相关产品推荐

