Что делает функцию стандартной библиотеки C опасной и какова альтернатива? - PullRequest
64 голосов
/ 10 августа 2009

Во время изучения C я регулярно сталкиваюсь с ресурсами, которые рекомендуют никогда не использовать некоторые функции (например, gets()), поскольку их сложно или невозможно безопасно использовать.

Если стандартная библиотека C содержит ряд этих «никогда не используемых» функций, может показаться необходимым изучить их список, что делает их небезопасными, и что делать вместо этого.

До сих пор я изучал функции, которые:

  • Нельзя предотвратить перезапись памяти
  • Не гарантируется нулевое завершение строки
  • Поддержание внутреннего состояния между вызовами

обычно считается небезопасным для использования. Есть ли список функций, которые демонстрируют такое поведение? Существуют ли другие типы функций, которые невозможно безопасно использовать?

Ответы [ 9 ]

45 голосов
/ 10 августа 2009

В старые времена у большинства строковых функций не было проверки границ. Конечно, они не могли просто удалить старые функции или изменить свои подписи для включения верхней границы, что нарушило бы совместимость. Теперь почти для каждой из этих функций есть альтернативная «n» версия. Например:

strcpy -> strncpy
strlen -> strnlen
strcmp -> strncmp
strcat -> strncat
strdup -> strndup
sprintf -> snprintf
wcscpy -> wcsncpy
wcslen -> wcsnlen

И еще.

См. Также https://github.com/leafsr/gcc-poison, который является проектом для создания файла заголовка, который заставляет gcc сообщать об ошибке, если вы используете небезопасную функцию.

34 голосов
/ 10 августа 2009

Да, fgets(..., ..., STDIN) - хорошая альтернатива gets(), поскольку она принимает параметр размера (gets() фактически полностью удалено из стандарта C в C11). Обратите внимание, что fgets() не является точной заменой для gets(), поскольку первый будет включать в себя завершающий символ \n, если в буфере было место для полной строки для чтения.

scanf() считается проблематичным в некоторых случаях, а не просто «плохим», потому что, если входные данные не соответствуют ожидаемому формату, может быть невозможно восстановить разумно (это не позволяет вам перематывать введите и попробуйте еще раз). Если вы можете просто отказаться от плохо отформатированного ввода, это полезно. «Лучшей» альтернативой здесь является использование функции ввода, такой как fgets() или fgetc(), для чтения фрагментов ввода, а затем сканирования ее с помощью sscanf() или анализа ее с помощью функций обработки строк, таких как strchr() и strtol(). Также см. Ниже конкретную проблему со спецификатором преобразования "%s" в scanf().

Это не стандартная функция C, но функции BSD и POSIX mktemp(), как правило, невозможно безопасно использовать, поскольку всегда существует условие состязания TOCTTOU между проверкой существования файла и последующим его созданием. mkstemp() или tmpfile() являются хорошими заменами.

strncpy() - немного хитрая функция, потому что она не завершает нулевое назначение, если для этого не было места. Несмотря на внешне общее имя, эта функция была разработана для создания определенного стиля строки, который отличается от обычных строк C - строк, хранящихся в известном поле фиксированной ширины, где нулевой терминатор не требуется, если строка заполняет поле точно (оригинальный каталог UNIX записи были в этом стиле). Если у вас нет такой ситуации, вам, вероятно, следует избегать этой функции.

atoi() может быть плохим выбором в некоторых ситуациях, потому что вы не можете сказать, когда произошла ошибка при выполнении преобразования (например, если число превысило диапазон int). Используйте strtol(), если это важно для вас.

strcpy(), strcat() и sprintf() страдают от проблемы, аналогичной gets() - они не позволяют указывать размер буфера назначения. По-прежнему возможно, по крайней мере, теоретически, использовать их безопасно - но вам намного лучше использовать strncat() и snprintf() (вы можете использовать strncpy(), но см. Выше). Обратите внимание, что, хотя n для snprintf() является размером буфера назначения, n для strncat() является максимальным количеством добавляемых символов и не включает нулевого терминатора. Другой вариант, если вы уже рассчитали соответствующие строки и размеры буфера, это memmove() или memcpy().

В той же теме, если вы используете семейство функций scanf(), не используйте просто "%s" - укажите размер адресата, например. "%200s".

20 голосов
/ 10 августа 2009

strtok() обычно считается злом, поскольку хранит информацию о состоянии между вызовами.Не пытайтесь запускать ЭТО в многопоточной среде!

11 голосов
/ 10 августа 2009

Строго говоря, есть одна действительно опасная функция. Это gets(), потому что его ввод не контролируется программистом. Все остальные функции, упомянутые здесь, безопасны сами по себе. «Хорошие» и «плохие» сводятся к защитному программированию, а именно к предусловиям, постусловиям и стандартному коду.

Давайте возьмем strcpy() например. У него есть некоторые предварительные условия, что программист должен выполнить перед вызовом функции. Обе строки должны быть действительными, указатели, отличные от NULL, должны содержать строки с нулевым символом в конце, а в месте назначения должно быть достаточно места для конечной длины строки в диапазоне size_t. Кроме того, строки не могут перекрываться.

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

n = DST_BUFFER_SIZE;
if ((dst != NULL) && (src != NULL) && (strlen(dst)+strlen(src)+1 <= n))
{
    strcpy(dst, src);
}

Уже молча предполагая, что строки не перекрываются и оканчиваются нулем.

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

strncpy(dst, src, n);
if (n > 0)
{
    dst[n-1] = '\0';
}

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

Или даже спорить с этим. Возьмите printf() семью. Эти функции возвращают статус, который указывает на ошибку и успех. Кто проверяет, был ли успешным вывод в stdout или stderr? С аргументом, что вы ничего не можете сделать, когда стандартные каналы не работают. Ну, а как насчет спасения пользовательских данных и завершения программы с кодом ошибки, указывающим на ошибку? Вместо возможной альтернативы сбой и запись позже с поврежденными пользовательскими данными.

В условиях ограниченного времени и денег всегда возникает вопрос, сколько сетей безопасности вы действительно хотите и каков будет наихудший сценарий? Если это переполнение буфера, как в случае с str-функциями, то имеет смысл запретить их и, возможно, обеспечить функции-оболочки с уже существующими сетями безопасности.

Последний вопрос по этому поводу: почему вы уверены, что ваши «хорошие» альтернативы действительно хороши ?

7 голосов
/ 10 августа 2009

Любая функция, которая не принимает параметр максимальной длины и вместо этого полагается на наличие маркера конца (например, многие функции обработки 'string').

Любой метод, который поддерживает состояние между вызовами.

6 голосов
/ 10 августа 2009
  • sprintf плохо, не проверяет размер, используйте snprintf
  • gmtime, localtime - use gmtime_r, localtime_r
4 голосов
/ 22 октября 2009

Чтобы добавить что-то о strncpy, большинство людей здесь забыли упомянуть. strncpy может привести к проблемам с производительностью, поскольку он очищает буфер до заданной длины.

char buff[1000];
strncpy(buff, "1", sizeof buff);

скопирует 1 символ и перезапишет 999 байтов с 0

Еще одна причина, по которой я предпочитаю strlcpy (я знаю, что strlcpy - это BSDism, но его так легко реализовать, что нет оправдания его не использовать).

3 голосов
/ 10 августа 2009

Просмотр страницы 7 (PDF, страница 9) SAFECode Dev Practices

Редактировать: со страницы -

семья strcpy
Семья Стрнчи
семья strcat
семейство сканф
семья спринтф
заводит семью

2 голосов
/ 10 августа 2009

strcpy - снова!

Большинство людей сходятся во мнении, что strcpy опасен, но strncpy редко используется для замены. Обычно важно, чтобы вы знали, когда вам в любом случае нужно было обрезать строку, и по этой причине вам обычно нужно проверять длину исходной строки в любой момент. В этом случае обычно лучше использовать memcpy, поскольку вы точно знаете, сколько символов вы хотите скопировать.

например. усечение является ошибкой:

n = strlen( src );

if( n >= buflen )
    return ERROR;

memcpy( dst, src, n + 1 );

усечение разрешено, но количество символов должно быть возвращено, чтобы вызывающий знал:

n = strlen( src );

if( n >= buflen )
    n = buflen - 1;

memcpy( dst, src, n );
dst[n] = '\0';

return n;
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...