Подтвердить что ты не робот

Какие функции в стандартной библиотеке C обычно поощряют плохую практику?

Это вдохновлено этим вопросом и комментариями к одному конкретному ответу, в котором я узнал, что strncpy не очень безопасная функция обработки строк в C и что он заполняет нули, пока не достигнет n, чего я не знал.

В частности, для цитирования R..

strncpy не завершает нуль, и делает null-pad весь остаток буфер назначения, который является огромная трата времени. Вы можете работать вокруг первого, добавив свой собственный нулевое заполнение, но не последнее. Это никогда не предназначалась для использования в качестве "безопасного string handling", но для работа с полями фиксированного размера в Unix таблицы каталогов и файлы базы данных. snprintf (dest, n, "% s", src) - это только правильный "безопасный strcpy" в стандартном C, но это, вероятно, будет намного медленнее. Кстати, усечение само по себе может быть серьезной ошибкой, а в некоторых случаях может приводят к повышению привилегий или DoS, поэтому бросая "безопасные" строковые функции, которые усечь их вывод при задаче не способ сделать его "безопасным" или "Безопасный". Вместо этого вы должны обеспечить что буфер назначения является правильный размер и просто используйте strcpy (или еще лучше, memcpy, если вы уже знаете длина строки источника).

И из Джонатан Леффлер

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

Итак, я явно немного ржавый в стандартной библиотеке C. Поэтому я хотел бы задать вопрос:

Какие C стандартные функции библиотеки используются ненадлежащим образом/способами, которые могут вызвать/привести к проблемам безопасности/дефектам кода/неэффективности?

В интересах объективности у меня есть ряд критериев для ответа:

  • Пожалуйста, если можете, укажите причины, лежащие в основе рассматриваемой функции, то есть ее цель.
  • Пожалуйста, обратите внимание на неправомерное использование кода.
  • Укажите, почему это неправильное использование может привести к проблеме. Я знаю, что это должно быть очевидно, но оно предотвращает мягкие ответы.

Пожалуйста, избегайте:

  • Дебаты о соглашениях об именах функций (кроме случаев, когда это однозначно вызывает путаницу).
  • "Я предпочитаю x over y" - предпочтение в порядке, мы все имеем их, но меня интересуют реальные неожиданные побочные эффекты и способы защиты от них.

Поскольку это, скорее всего, будет считаться субъективным и не имеет определенного ответа, я сразу же помещаю вики сообщества.

Я также работаю по C99.

4b9b3361

Ответ 1

Общая ошибка с функцией strtok() заключается в том, чтобы считать, что анализируемая строка остается неизменной, в то время как она фактически заменяет символ разделителя на '\0'.

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

CERT C Secure Coding Standard перечисляет многие из этих ошибок, о которых вы просили.

Ответ 2

Какие стандартные функции библиотеки C используются ненадлежащим образом/способами, которые могут вызвать/привести к проблемам безопасности/дефектам кода/неэффективности?

Я собираюсь пойти с очевидным:

char *gets(char *s);

С его замечательной особенностью, что просто невозможно использовать его соответствующим образом.

Ответ 3

Почти во всех случаях atoi() не следует использовать (это также относится к atof(), atol() и atoll()).

Это связано с тем, что эти функции вообще не обнаруживают ошибок вне диапазона - стандарт просто говорит: "Если значение результата не может быть представлено, поведение undefined.". Таким образом, единственный раз, когда они могут быть безопасно использованы, можно доказать, что вход, конечно, будет в пределах диапазона (например, если вы передаете строку длиной 4 или менее на atoi(), она не может быть вне диапазона).

Вместо этого используйте одно из семейств функций strtol().

Ответ 4

Расширим вопрос до интерфейсов в более широком смысле.

errno:

технически даже не понятно, что это такое, переменная, макрос, вызов неявной функции? На практике в современных системах это макрос, который превращается в вызов функции, чтобы иметь конкретное состояние ошибки потока. Это зло:

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

Предстоящий стандарт получает определение errno немного более прямолинейно, но эти уродства остаются

Ответ 5

Часто существует strtok_r.

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

Ответ 6

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

Ответ 7

Любая функция, управляющая глобальным состоянием, например gmtime() или localtime(). Эти функции просто не могут безопасно использоваться в нескольких потоках.

EDIT: rand() находится в той же категории, что и казалось бы. По крайней мере, нет гарантий безопасности потоков, а в моей Linux-системе man-страница предупреждает, что она не является реентерабельной и не-потоковой.

Ответ 8

Один из моих bêtes noire strtok(), потому что он не реентерабелен и потому что он обрабатывает строку, обрабатываемую на куски, вставляя NUL в конец каждого токена он изолирует. Проблемы с этим - легион; его часто огорчают, как решение проблемы, но часто это проблема. Не всегда - его можно использовать безопасно. Но только если вы будете осторожны. То же самое относится к большинству функций, за исключением gets(), которые нельзя использовать безопасно.

Ответ 9

Уже есть один ответ о realloc, но у меня другое дело. Много времени я видел, как люди пишут realloc, когда они означают free; malloc - другими словами, когда у них есть буфер, полный мусора, который должен изменить размер перед сохранением новых данных. Это, конечно же, приводит к потенциально большому, кэшированию memcpy мусора, который должен быть перезаписан.

Если правильно использовать растущие данные (таким образом, чтобы избежать худшего случая O(n^2) для роста объекта с размером n, т.е. увеличивая буфер геометрически, а не линейно, когда вы закончите свободное пространство), realloc имеет сомнительную выгоду, просто выполняя свой новый цикл malloc, memcpy и free. Единственный способ realloc всегда избегать этого делать внутренне - это когда вы работаете с одним объектом в верхней части кучи.

Если вам нравится нулевое заполнение новых объектов с помощью calloc, легко забыть, что realloc не будет нулевым заполнять новую часть.

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

Я не уверен, скажу ли я, что realloc поощряет плохую практику, но это функция, которую я бы наблюдал.

Ответ 10

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

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

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

Ответ 11

В совершенно другом ключе, я никогда не понимал преимуществ atan(), когда есть atan2(). Разница в том, что atan2() принимает два аргумента и возвращает угол в любом месте диапазона -π.. + π. Кроме того, он избегает деления на нулевые ошибки и потери ошибок точности (деление очень маленького числа на очень большое число или наоборот). Напротив, функция atan() возвращает только значение в диапазоне -π/2.. + π/2, и вам нужно выполнить деление заранее (я не помню сценарий, в котором atan() можно было бы использовать без существует разделение, за исключением просто создания таблицы арктангенсов). Предоставление 1.0 в качестве делителя для atan2() при заданном простом значении не приводит к ограничениям.

Ответ 12

Другой ответ, так как они не связаны друг с другом, rand:

  • это неуказанное случайное качество
  • это не повторный вход

Ответ 13

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

Ответ 14

basename() и dirname() не являются потокобезопасными.