Название говорит само за себя: можно ли использовать 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 ответа
Нет, код, представленный в вопросе, не предотвращает соревнования 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;
}
РЕДАКТИРОВАТЬ (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;
}
Я обнаружил это, проверяя, что я охватил все свои базы, прежде чем задавать этот вопрос.
Новые вопросы
c
C - это язык программирования общего назначения, используемый для системного программирования (ОС и встраиваемых), библиотек, игр и кроссплатформенности. Этот тег следует использовать с общими вопросами, касающимися языка C, как это определено в стандарте ISO 9899 (последняя версия 9899: 2018, если не указано иное, а также для запросов, специфичных для версии, с c89, c99, c11 и т. Д.). C отличается от C ++ и не должен сочетаться с тэгом C ++ без разумной причины.