避免统计和重命名之间的 TOCTOU(检查时间,使用时间)竞争条件

Avoid TOCTOU (time-of-check, time-of-use) race condition between stat and rename

如何避免 LOGFILE 的统计和重命名之间的竞争条件的 TOCTOU(检查时间,使用时间)竞争条件?

需要在其大小值超过最大大小后移动日志文件。

result = stat(LOGFILE, & data);
if (result != 0) {
  // stat failed
  // file probably does not exist
} else if (data.st_size > MAX_LOGSIZE) {
  unlink(PREV_LOGFILE);
  (void) rename(LOGFILE, PREV_LOGFILE);
}

避免文件操作出现 TOCTTOU 的标准方法是 open 文件 一次 然后通过文件描述符而不是文件名做你需要的一切.

但是,重命名和取消link文件需要它的路径(因为他们需要知道link重命名或删除什么),所以你不能在这里使用这种方法。另一种方法可能是将文件的内容复制到别处,然后将其截断为零字节,尽管您使用日志文件的场景可能需要原子操作,这可能很难实现。另一种方法是要求对目录进行严格的访问控制:如果攻击者无法写入目录,那么它就无法与您的进程玩 TOCTTOU 游戏。您可以使用 unlinkatrenameat 将路径限制为特定目录的文件描述符,这样您就不必担心目录本身会发生变化。

假设 POSIX-like 平台,像这样未经测试的代码可能会完成这项工作:

dirfd = open(LOGDIR, O_DIRECTORY);
// check for failure
res = fstatat(dirfd, LOGFILE, statbuf, AT_SYMLINK_NOFOLLOW);
if ((0 == res) && (S_ISREG(statbuf) && (data.st_size > MAX_LOGSIZE)) {
    unlinkat(dirfd, PREV_LOGFILE, 0);
    renameat(dirfd, LOGFILE, dirfd, PREV_LOGFILE);
}
close(dirfd);