Использование stat () после fopen (), чтобы избежать проблем TOCTOU? - PullRequest
0 голосов
/ 23 октября 2018

Название говорит само за себя: можно ли использовать 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, а также ошибочных разрешений?

Ответы [ 2 ]

0 голосов
/ 24 октября 2018

Нет, код, представленный в вопросе, не исключает гонки TOCTOU .

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

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

Если вы хотите использовать буферизованный ввод / вывод C, вы можете получить дескриптор файла из FILE*, используя 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;
}
0 голосов
/ 23 октября 2018

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

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

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

...