Что делает функцию стандартной библиотеки C опасной и какова альтернатива?

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

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

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

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

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

Ответ 1

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

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

И больше.

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

Ответ 2

Да, 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".

Ответ 3

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

Ответ 4

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

Давайте возьмем, например, strcpy(). У него есть некоторые предварительные условия, которые программист должен выполнить перед вызовом функции. Обе строки должны быть действительными, ненулевые указатели на нулевые завершенные строки, и место назначения должно обеспечивать достаточно места с конечной длиной строки в диапазоне 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? С аргументом, что вы ничего не можете сделать, когда стандартные каналы не работают. Ну, а как насчет спасения пользовательских данных и завершения программы с кодом ошибки, указывающим на ошибку? Вместо возможной альтернативы сбой и запись позже с поврежденными пользовательскими данными.

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

И последний вопрос: почему вы уверены, что ваши "хорошие" альтернативы действительно хороши?

Ответ 5

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

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

Ответ 6

  • sprintf Плохо, не проверяет размер, используйте snprintf
  • gmtime, localtime - use gmtime_r, localtime_r

Ответ 7

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

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

скопирует 1 char и перезапишет 999 байт с помощью 0

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

Ответ 8

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

Изменение: со страницы -

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

Ответ 9

strcpy - снова!

Большинство людей согласны с тем, что strcpy опасен, но strncpy редко бывает полезной заменой. Как правило, важно знать, когда вам нужно было усечь строку в любом случае, и по этой причине вам обычно нужно исследовать длину исходной строки anwyay. Если это так, обычно 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;

Ответ 11

Бах... Вафли. Эти функции небезопасны, потому что программисты являются головками костей. Что плохого в этом?

char msg[100] = {'\0'};
int num = 10; //obtain num however
sprintf(msg, "There are %d items for sale", num);

Пока строка может принимать длину значения min/max для подписанного int, я не вижу, как это плохо или небезопасно. Программисты небезопасны, а не функции....

Ответ 12

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

Ответ 13

Я бы сказал, что scanf хорош иногда, более конкретно, когда вам действительно нужно что-то читать FAST. Это величины выше, чем cin <

Я помню задачу о международной олимпиаде по информатике (IOI), где вам нужно было использовать scanf, так как cin заняло слишком много времени.