如何处理access()和unlink()之间的TOCTOU问题?

ki1q1bka  于 2023-03-01  发布在  其他
关注(0)|答案(1)|浏览(319)

静态分析工具(Coverity)将以下代码中的unlink()语句标记为在access()unlink()之间存在检查时间/使用时间(TOCTOU)问题:

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>

void del(const char* file) {
  if (! file) { return; }

  {
    struct stat ss;

    if (stat(file, &ss)) { return; }
    if (! S_ISREG(ss.st_mode)) { return; }
    if (ss.st_uid != getuid()) { return; }
    if (access(file, W_OK)) { return; }

    unlink(file);
  }
}

int main(int argc, char* argv[]) {
  const char* file = "./foo.txt";
  int fd = open(file, O_WRONLY | O_CREAT);

  close(fd);
  del(file);

  return 0;
}

它是否标记了在调用access()unlink()之间,设备上的任何其他进程可能沿着并更改目标文件的权限,从而导致unlink()失败的可能性?

是否有办法处理这种情况以避免TOCTOU?

我不知道如何通过当前进程“锁定”文件以获得独占删除权限。
TBH,我想我以前没有见过这样的代码,它会检查删除文件是否“可以”--我遇到的大多数这类代码只是无条件地调用unlink()并继续前进。

3pvhb19x

3pvhb19x1#

那些支票对吗?

  • !S_ISREG(ss.st_mode)

这确保了文件是一个普通文件。这不是一个多余的检查,因为你可以删除一些不是普通文件的文件。
如果您试图确保它不会删除非纯文本文件,那么代码会遇到TOCTOU争用条件,这不容易解决。
否则,如果您不关心文件的类型,那么执行检查是错误的,因为它错误地限制了哪些文件可以删除,它应该被删除。

  • ss.st_uid != getuid()

这确保了文件是一个普通文件。这不是一个多余的检查,因为你可以删除不属于你的文件。
如果您试图确保它不会删除其他人拥有的文件,则代码具有TOCTOU争用条件,这不容易解决。
否则,如果您不关心文件的所有者,则执行检查是错误的,因为它错误地限制了哪些文件可以删除,应该将其删除。

  • access(file, W_OK)

这使得进程具有写入文件的权限。此检查不是多余的,因为删除文件不需要写入文件的权限。(需要写入包含文件的目录的权限。)
如果您试图确保进程具有写入文件的权限,则代码会出现TOCTOU争用条件,这不容易解决。
否则,如果您不关心这一点,那么执行检查是错误的,因为它错误地限制了哪些文件可以删除,它应该被删除。
我怀疑当你修复了所有的错误,你只剩下

void del( const char* file ) {
   unlink( file );
}

如果是,则不存在TOCTOU争用条件。
如果我错了,而你真的想检查这些东西,那么我看不到一个好的解决方案。没有合作,竞争条件实际上是不可避免的。

相关问题