Название говорит само за себя: можно ли использовать stat() после fopen(), чтобы избежать условий гонки "время проверки - время использования" (TOCTOU)?

Некоторые детали:

Я пишу программу на языке C, которая только читает файлы, но должна правильно выдавать ошибку, когда ее просят прочитать каталог. На данный момент он использует open()O_RDWR) для генерации ошибки, а затем проверяет errno на наличие EISDIR, например:

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, а также ошибок неправильного разрешения?

5
Gavin D. Howard 23 Окт 2018 в 18:26

2 ответа

Лучший ответ

Нет, код, представленный в вопросе, не предотвращает соревнования TOCTOU .

Тестирование после использования подвержено точно таким же ошибкам, как и тестирование перед использованием. В обоих случаях имя разрешается в два разных момента, возможно, с разными результатами. Это причина гонки, и это может произойти в зависимости от того, какой доступ произойдет раньше.

Единственный способ избежать этого - открыть файл один раз и использовать полученный таким образом дескриптор файла для любых других необходимых проверок. Современные операционные системы предоставляют такие интерфейсы, как fstat() именно для этой цели.

Если вы хотите использовать буферизованный ввод-вывод C, вы можете получить дескриптор файла из FILE*, используя fileno(), или вы можете создать FILE* из файлового дескриптора, используя {{X3} } .

Это требует очень небольшого изменения в вашем коде:

# 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;
}
6
Toby Speight 24 Окт 2018 в 16:37

РЕДАКТИРОВАТЬ (25.10.2018): лучше Тоби Спейта.

Есть решение: используйте 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;
}

Я обнаружил это, проверяя, что я охватил все свои базы, прежде чем задавать этот вопрос.

6
Gavin D. Howard 25 Окт 2018 в 17:19
52952712