8

标题说明了一切:是否可以在使用stat() fopen()避免检查时间到使用时间 (TOCTOU) 竞争条件?

一些细节:

我正在编写一个读取文件的 C 程序,但在被要求读取目录时需要正确出错。截至目前,它使用open()(with O_RDWR) 生成错误,然后检查errnoEISDIR如下所示:

int fd = open(path, O_RDWR);

if (fd == -1) {
    if (errno == EISDIR) return PATH_IS_DIR;
    else return FILE_ERR;
}

上述解决方案的问题是该程序只需要读取文件,因此通过打开文件O_RDWR,如果用户具有读取权限但没有写入权限,我可能会错误地得到权限错误。

是否可以执行以下操作来避免 TOCTOU 竞争条件?

struct stat pstat;

FILE *f = fopen(path, "r");

if (!f) return FILE_ERR;

if (stat(path, &pstat) == -1) {
    fclose(f);
    return FILE_ERR;
}

if (S_ISDIR(pstat.st_mode)) {
    fclose(f);
    return PATH_IS_DIR;
}

如果不可能,是否有其他解决方案可以防止 TOCTOU 错误以及错误的权限错误?

4

2 回答 2

9

不,问题中提供的代码并不能避免 TOCTOU 比赛

使用后的测试容易出现与使用前的测试完全相同的错误。在这两种情况下,名称在两个不同的时间被解析,结果可能不同。这是竞争的原因,无论哪个访问先发生,它都可能发生。

避免这种情况的唯一方法是打开文件一次,然后使用如此获得的文件描述符进行您需要的任何其他检查。fstat()现代操作系统为此提供了诸如此类的接口。

FILE*如果您想使用 C 的缓冲 I/O,您可以从using获取文件描述符fileno()- 或者您可以FILE*使用fdopen().

它需要对您的代码进行非常小的更改:

# Untested

struct stat pstat;

FILE *f = fopen(path, "r");

if (!f) return FILE_ERR;

if (fstat(fileno(f), &pstat) == -1) {
//  ^^^^^^^^^^^^^^^                         <-- CHANGED HERE
    fclose(f);
    return FILE_ERR;
}

if (S_ISDIR(pstat.st_mode)) {
    fclose(f);
    return PATH_IS_DIR;
}
于 2018-10-24T16:37:03.000 回答
6

编辑(2018-10-25):Toby Speight 的回答更好。

一个解决方案:使用open(),然后 fstat()

一个例子:

struct stat pstat;

int fd = open(path, O_RDONLY);

if (fd == -1) return FILE_ERR;

if (fstat(fd, &pstat) == -1) {
    close(fd);
    return FILE_ERR;
}

if (S_ISDIR(pstat.st_mode)) {
    close(fd);
    return PATH_IS_DIR;
}

我在问这个问题之前检查我是否已经涵盖了所有基础时发现了这一点。

于 2018-10-23T15:26:09.960 回答