Использование fseek и ftell для определения размера файла имеет уязвимость? - PullRequest
11 голосов
/ 11 мая 2011

Я читал посты, в которых показано, как использовать 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. Кто-нибудь может прокомментировать это?

Ответы [ 5 ]

14 голосов
/ 11 мая 2011

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

POSIX требует:

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

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

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

6 голосов
/ 11 мая 2011

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

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

4 голосов
/ 11 мая 2011

Причина, по которой не использует fstat, заключается в том, что fstat - это POSIX, но fopen, ftell и fseek являются частью стандарта С.

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

3 голосов
/ 11 мая 2011

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

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

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

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

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

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

2 голосов
/ 07 ноября 2012

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

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

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

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

Но стандарт C также говорит об этом:

Бинарный поток не обязательно должен поддерживать вызовы fseek с откуда значение SEEK_END.

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

...