Использование fseek и ftell для определения размера файла имеет уязвимость?

Я читал сообщения, которые показывают, как использовать fseek и ftell для определения размера файла.

FILE *fp;
long file_size;
char *buffer;

fp = fopen("foo.bin", "r");
if (NULL == fp) {
 /* Handle Error */
}

if (fseek(fp, 0 , SEEK_END) != 0) {
  /* Handle Error */
}

file_size = ftell(fp);
buffer = (char*)malloc(file_size);
if (NULL == buffer){
  /* handle error */
}

Я собирался использовать эту технику, но затем я столкнулся с этой ссылкой , которая описывает потенциальную уязвимость.

Вместо ссылки рекомендуется использовать fstat. Может кто-нибудь прокомментировать это?

Ответ 1

Ссылка является одной из многих бессмысленных частей C-кодирования совета CERT. Их обоснование основано на свободах, которые стандарт C позволяет реализовать реализацию, но которые не допускаются POSIX и, следовательно, не имеют значения во всех случаях, когда у вас есть fstat в качестве альтернативы.

POSIX требует:

  • что модификатор "b" для fopen не действует, т.е. что текстовый и двоичный режимы ведут себя одинаково. Это означает, что их беспокойство по поводу вызова UB в текстовых файлах является бессмыслицей.

  • что файлы имеют размер байтового разрешения, заданный операциями записи и усечением. Это означает, что их беспокойство о случайных числах нулевых байтов в конце файла является нонсенсом.

К сожалению, все эти глупости, которые они публикуют, трудно понять, какие публикации CERT следует воспринимать всерьез. Какой позор, потому что многие из них серьезны.

Ответ 2

Если ваша цель - найти размер файла, определенно вы должны использовать fstat() или его друзей. Это гораздо более прямой и выразительный метод - вы буквально просите систему рассказать вам статистику файлов, а не более обходной метод fseek/ftell.

Бонусный совет: если вы хотите узнать только, доступен ли файл, используйте access(), а не открывайте файл или даже статируйте его. Это еще более простая операция, о которой многие программисты не знают.

Ответ 3

Причиной не использовать fstat является то, что fstat является POSIX, но fopen, ftell и fseek являются частью стандарта C.

Может существовать система, которая реализует стандарт C, но не POSIX. На такой системе fstat не будет работать вообще.

Ответ 4

Я хотел бы согласиться с их основным заключением, что вы обычно не должны использовать код fseek/ftell непосредственно в основной части вашего кода, но вы, вероятно, не должны использовать fstat. Если вы хотите размер файла, большая часть вашего кода должна использовать что-то с явным прямым именем, например filesize.

Теперь лучше реализовать, используя fstat, где доступно, и (например) FindFirstFile в Windows (наиболее очевидная платформа, где fstat обычно не будет доступна).

Другая сторона истории заключается в том, что многие (большинство?) ограничений на fseek по отношению к двоичным файлам фактически возникли с помощью CP/M, которые явно не сохраняли размер файла в любом месте. Конец текстового файла был сигнализирован элементом управления-Z. Однако для двоичного файла все, что вы действительно знали, было тем, какие сектора были использованы для хранения файла. В последнем секторе у вас было некоторое количество неиспользуемых данных, которые часто (но не всегда) были заполнены нулями. К сожалению, могут быть нули, которые были значительными и/или ненулевыми значениями, которые не были значительными.

Если бы весь стандарт C был написан до утверждения (например, если он был запущен в 1988 году и закончен в 1989 году), они, вероятно, полностью проигнорировали бы CP/M. К лучшему или к худшему, однако, они начали работать над стандартом C примерно в 1982 году, когда CP/M все еще был достаточно широко использован, что его нельзя игнорировать. К тому времени, когда CP/M исчезло, многие из решений уже были сделаны, и я сомневаюсь, что кто-то хотел их пересмотреть.

Для большинства людей сегодня, однако, просто нет смысла - большинство кода не будет переносить на CP/M без массивной работы; это одна из относительно небольших проблем, с которыми приходится иметь дело. Сделать современную программу, выполняемую только в 48K (или около того) памяти как для кода, так и для данных, представляет собой гораздо более серьезную проблему (максимальная мегабайт или около того для массового хранения будет еще одной серьезной проблемой).

У CERT есть одна хорошая точка: вы, вероятно, не должны (как это часто бывает) находить размер файла, выделять столько места, а затем предполагать, что содержимое файла поместится там. Несмотря на то, что fseek/ftell предоставит вам правильные размеры с современными системами, эти данные могут быть устаревшими к тому времени, когда вы действительно прочитали данные, так что вы все равно можете перекрыть свой буфер.

Ответ 5

Согласно стандарт C, §7.21.3:

Установка индикатора фиксированного положения в конец файла, как и в случае с fseek(file, 0, SEEK_END), имеет неопределенное поведение для бинарного потока (из-за возможные конечные нулевые символы) или для любого потока с зависящее от состояния кодирование, которое не обязательно заканчивается начальным состояние сдвига.

Человек с буквой закона может подумать, что этого UB можно избежать, вычислив размер файла с помощью:

fseek(file, -1, SEEK_END);
size = ftell(file) + 1;

Но стандарт C также говорит следующее:

Двоичный поток не нуждается в значимой поддержке вызовов fseek с откуда значение SEEK_END.

В результате мы ничего не можем сделать, чтобы исправить это в отношении fseek/SEEK_END. Тем не менее, я бы предпочел fseek/ftell вместо вызовов API, специфичных для ОС.