RHEL 8/9下gcc C代码mkdir()/chmod()的TOCTOU竞争条件解决咨询
问题背景
你的代码在RHEL 8.x/9.x环境下运行,SonarQube检测到mkdir()后调用chmod()存在TOCTOU(时间检查-时间使用)竞争:mkdir()创建目录后到chmod()修改权限的间隙中,恶意进程可能将该路径替换为其他文件或符号链接,导致chmod()错误地修改无关对象的权限。
由于代码是共享库,无法修改进程umask(会影响整个进程及其他线程),且open()无法创建目录,因此无法通过fchmod()规避竞争。你的场景中,子目录位于重启即清空的/tmp类目录下,仅在创建子目录内文件返回ENOENT时才触发创建逻辑。
可行的标准解决方案
1. 父目录粘滞位 + 0权限mkdir(推荐)
/tmp类目录默认带有粘滞位(sticky bit),该权限确保只有目录所有者、文件所有者或root用户才能删除/重命名目录内的对象。利用这一特性,你可以:
- 以
0权限调用mkdir():umask不会对权限产生影响(0与任何umask按位与结果仍是0) - 立即调用
chmod()设置目标权限
由于粘滞位的保护,恶意用户无法在mkdir()和chmod()的间隙中替换你刚创建的目录(当前进程是目录所有者,其他用户无权限修改该路径),彻底消除TOCTOU风险。
修改后的create_subdir函数示例:
int create_subdir(char *temp_dir_path) { int rc = 0; // 以0权限创建目录,umask不影响最终初始权限 rc = mkdir(temp_dir_path, 0); if (rc == 0) { // 立即设置目标权限 rc = chmod(temp_dir_path, S_IRWXU | S_IRWXG | S_IRWXO); } return rc; }
2. 额外验证目录属性(增强防护)
如果需要额外安全保障,可以在chmod()前添加lstat()检查,确认路径确实是当前进程创建的目录:
#include <unistd.h> int create_subdir(char *temp_dir_path) { int rc = 0; struct stat st; rc = mkdir(temp_dir_path, 0); if (rc == 0) { // 验证路径是目录,且所有者为当前进程 if (lstat(temp_dir_path, &st) == 0 && S_ISDIR(st.st_mode) && st.st_uid == getuid()) { rc = chmod(temp_dir_path, S_IRWXU | S_IRWXG | S_IRWXO); } else { // 验证失败,清理目录并返回错误 rmdir(temp_dir_path); rc = -1; } } return rc; }
3. 使用mkdirat()(可选)
如果已打开父目录的文件描述符,可以使用mkdirat()替代mkdir(),确保创建的目录确实位于预期的父目录下,避免路径遍历攻击。结合粘滞位和0权限创建逻辑,同样能消除TOCTOU风险:
#include <fcntl.h> int create_subdir(char *parent_dir, char *subdir_name) { int rc = 0; int parent_fd = open(parent_dir, O_RDONLY | O_DIRECTORY); if (parent_fd == -1) { return -1; } // 以0权限在父目录下创建子目录 rc = mkdirat(parent_fd, subdir_name, 0); if (rc == 0) { // 拼接路径后设置权限 char full_path[256]; snprintf(full_path, sizeof(full_path), "%s/%s", parent_dir, subdir_name); rc = chmod(full_path, S_IRWXU | S_IRWXG | S_IRWXO); } close(parent_fd); return rc; }
关键前提
确保父目录(如/tmp)已正确设置粘滞位,可通过以下命令检查:
ls -ld /tmp
输出权限位包含t(如drwxrwxrwt)即表示粘滞位已启用。
内容的提问来源于stack exchange,提问作者MysteryGuy

