То, которое функционирует из стандартной библиотеки, должно (должен) избежаться?

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

Какие альтернативы тем функциям существуют?

Мы можем использовать их безопасно - какие-либо хорошие методы?

87
задан Jonathan Leffler 7 September 2018 в 16:42
поделиться

11 ответов

Устаревшие функции
Небезопасные
Прекрасным примером такой функции является gets ( ) , поскольку невозможно определить размер целевого буфера. Следовательно, любая программа, считывающая ввод с помощью gets (), имеет уязвимость переполнения буфера . По аналогичным причинам следует использовать strncpy () вместо strcpy () и strncat () вместо strcat () .

Еще несколько примеры включают tmpfile () и функция mktemp () из-за потенциальных проблем безопасности при перезаписи временных файлов , которые заменяются более безопасной функцией mkstemp () .

Не реентерабельность
К другим примерам относятся gethostbyaddr () и gethostbyname () , которые не реентерабельны (и, следовательно, не гарантируют безопасность потоков) и были заменено повторно используемыми getaddrinfo () и freeaddrinfo () .

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

Устаревшие, непереносимые
Некоторые другие функции просто устаревают, потому что они дублируют функциональные возможности и не так переносимы, как другие варианты. Например, bzero () устарел в пользу memset () .

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

  • Она реентерабельна (т. Е. Не разделяет какое-либо состояние между вызовами) или:
  • Она не реентерабельна, но использует синхронизацию / блокировку по мере необходимости для общего состояния.

В общем, в Single UNIX Specification и IEEE 1003.1 (то есть «POSIX»), любая функция, которая не гарантирована для повторного входа, не гарантированно будет потокобезопасной.Другими словами, в многопоточных приложениях можно переносимо использовать только функции, которые гарантированно реентерабельны (без внешней блокировки). Однако это не означает, что реализации этих стандартов не могут сделать нереентерабельную функцию потокобезопасной. Например, Linux часто добавляет синхронизацию к функциям без повторного входа, чтобы добавить гарантию (помимо единой спецификации UNIX) безопасности потоков.

Строки (и буферы памяти в целом)
Вы также спросили, есть ли какой-то фундаментальный недостаток со строками / массивами. Кто-то может возразить, что это так, но я бы сказал, что нет, в языке нет фундаментального недостатка. C и C ++ требуют, чтобы вы передавали длину / емкость массива отдельно (это не свойство ".length", как в некоторых других языках). Само по себе это не недостаток. Любой разработчик C и C ++ может написать правильный код, просто передав длину в качестве параметра там, где это необходимо. Проблема в том, что несколько API-интерфейсов, которым требовалась эта информация, не смогли указать ее в качестве параметра. Или предположил, что будет использоваться некоторая константа MAX_BUFFER_SIZE. Такие API-интерфейсы устарели и заменены альтернативными API-интерфейсами, которые позволяют указывать размеры массива / буфера / строки.

Scanf (в ответ на ваш последний вопрос)
Лично я использую библиотеку iostreams C ++ (std :: cin, std :: cout, операторы << и >>, std :: getline, std :: istringstream, std :: ostringstream и т. д.), поэтому я обычно не занимаюсь этим.Если бы мне пришлось использовать чистый C, я бы лично просто использовал fgetc () или getchar () в сочетании с strtol () , strtoul () и т. д. и разбираю вещи вручную, так как я не большой поклонник varargs или форматных строк. Тем не менее, насколько мне известно, нет проблем с [f] scanf () , [f] printf () и т. Д., Если вы создаете формат струны сами,вы никогда не передаете произвольные строки формата и не разрешаете использовать вводимые пользователем данные в качестве строк форматирования, и вы используете макросы форматирования, определенные в , где это необходимо. (Обратите внимание, что snprintf () следует использовать вместо sprintf () , но это связано с невозможностью указать размер целевого буфера, а не с использованием строк формата ). Я также должен отметить, что в C ++ boost :: format обеспечивает форматирование, подобное printf, без varargs.

56
ответ дан 24 November 2019 в 07:50
поделиться

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

Однако легко использовать strncat () в качестве замены для strcpy () :

if (dest_size > 0)
{
    dest[0] = '\0';
    strncat(dest, source, dest_size - 1);
}

(Тест if , очевидно, может быть в общем случае, когда вы знаете, что dest_size определенно ненулевой).

5
ответ дан 24 November 2019 в 07:50
поделиться

Не забывайте о sprintf - это причина многих проблем. Это правда, потому что альтернатива snprintf иногда имеет разные реализации, которые могут сделать ваш код непереносимым.

  1. Linux: http://linux.die.net/man/3/snprintf

  2. Windows: http://msdn.microsoft.com/en-us/library/2ts7cx93%28VS .71% 29.aspx

В случае 1 (linux) возвращаемое значение - это объем данных, необходимый для хранения всего буфера (если он меньше размера данного буфера, вывод был обрезан)

В случае 2 (окна) возвращаемое значение - отрицательное число, если вывод усечен.

Как правило, вам следует избегать функций, которые не:

  1. безопасны при переполнении буфера (многие функции уже упомянуты здесь)

  2. потокобезопасны / не реентерабельны (например, strtok)

В руководстве каждого функции, вы должны искать такие ключевые слова, как: safe, sync, async, thread, buffer, bugs

2
ответ дан 24 November 2019 в 07:50
поделиться

И снова люди повторяют, как мантру, смехотворное утверждение о том, что "n" версия функций str - безопасные версии.

Если бы они были предназначены для этого, они всегда завершали бы строки нулевым символом.

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

Если массив, на который указывает s2, является строкой , которая короче чем n байтов, нулевых байтов добавляются к копии в массиве, на который указывает s1, пока не будет записано n байтов.

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

Если вы хотите «предположительно» безопасные версии, получите - или напишите свои собственные - процедуры strl (strlcpy, strlcat и т. Д.), Которые всегда завершают строки nul и не имеют побочных эффектов.Однако обратите внимание, что это не совсем безопасно, поскольку они могут тихо обрезать строку - это редко лучший способ действий в любой реальной программе. Бывают случаи, когда это нормально, но есть также много обстоятельств, когда это может привести к катастрофическим результатам (например, распечатка медицинских рецептов).

23
ответ дан 24 November 2019 в 07:50
поделиться

Избегайте

  • strtok для многопоточных программ, поскольку это не многопоточные программы.
  • получает , поскольку это может вызвать переполнение буфера
6
ответ дан 24 November 2019 в 07:50
поделиться

atoi не является потокобезопасным. Вместо этого я использую strtol по рекомендации на странице руководства.

-1
ответ дан 24 November 2019 в 07:50
поделиться

Некоторые люди утверждают, что следует избегать strcpy и strcat в пользу strncpy и strncat . На мой взгляд, это несколько субъективно.

Их определенно следует избегать при работе с пользовательским вводом - здесь нет сомнений.

В коде «далеко» от пользователя, когда вы просто знаете , что буферы достаточно длинные, strcpy и strcat могут быть немного более эффективными, потому что вычисление n для передачи их кузенам может быть излишним.

7
ответ дан 24 November 2019 в 07:50
поделиться

Практически любая функция, которая имеет дело со строками, завершающимися NUL, потенциально небезопасна. Если вы получаете данные из внешнего мира и манипулируете ими с помощью функций str * (), вы настраиваетесь на катастрофу

2
ответ дан 24 November 2019 в 07:50
поделиться

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

Вы можете не согласиться со всеми из них, но все они заслуживают внимания. Они добавляют API в список, когда его неправильное использование привело к ряду ошибок безопасности.

5
ответ дан 24 November 2019 в 07:50
поделиться

Очень трудно безопасно использовать scanf. Правильное использование scanf позволяет избежать переполнения буфера, но вы все еще уязвимы к неопределенному поведению при чтении чисел, которые не вписываются в запрашиваемый тип. В большинстве случаев fgets с последующим самоанализом (используя sscanf, strchr и т.д.) является лучшим вариантом.

Но я бы не сказал "избегайте scanf все время". scanf имеет свое применение. В качестве примера, допустим, вы хотите прочитать пользовательский ввод в массиве char длиной 10 байт. Вы хотите удалить идущую следом новую строку, если она есть. Если пользователь вводит более 9 символов до новой строки, вы хотите сохранить первые 9 символов в буфере и отбросить все до следующей новой строки. Вы можете сделать:

char buf[10];
scanf("%9[^\n]%*[^\n]", buf));
getchar();

Когда вы привыкнете к этой идиоме, она будет короче и в некотором смысле чище, чем:

char buf[10];
if (fgets(buf, sizeof buf, stdin) != NULL) {
    char *nl;
    if ((nl = strrchr(buf, '\n')) == NULL) {
        int c;
        while ((c = getchar()) != EOF && c != '\n') {
            ;
        }
    } else {
        *nl = 0;
    }
}
2
ответ дан 24 November 2019 в 07:50
поделиться

Несколько ответов здесь предлагают использовать strncat() вместо strcat(); я бы предложил избегать strncat()strncpy()). У него есть проблемы, которые затрудняют его правильное использование и приводят к ошибкам:

  • параметр length в strncat() связан (но не совсем точно - см. 3-й пункт) с максимальным количеством символов, которые могут быть скопированы в место назначения, а не с размером буфера назначения. Это делает strncat() более сложной в использовании, чем следовало бы, особенно если несколько элементов будут конкатенированы в место назначения.
  • может быть трудно определить, был ли результат усечен (что может быть или не быть важным)
  • легко получить ошибку off-by-one. Как отмечает стандарт C99, "Таким образом, максимальное количество символов, которое может оказаться в массиве, на который указывает s1, равно strlen(s1)+n+1" для вызова, который выглядит как strncat( s1, s2, n)

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

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

Вот немного из того, что Тодд Миллер и Тео де Раадт могли сказать о проблемах с strncat() и strncpy():

Существует несколько проблем, возникающих при использовании strncpy() и strncat() в качестве безопасных версий strcpy() и strcat(). Обе функции работают с NUL-терминацией и параметром длины разными и неинтуитивными способами, которые сбивают с толку даже опытных программистов. Они также не дают простого способа определить, когда происходит усечение. ... Из всех этих проблем путаница, вызванная параметрами длины, и связанная с ними проблема NUL-терминации являются наиболее важными. Когда мы проверяли дерево исходников OpenBSD на наличие потенциальных дыр в безопасности, мы обнаружили массовое злоупотребление strncpy() и strncat(). Хотя не все из них привели к появлению эксплуатируемых дыр в безопасности, они ясно показали, что правила использования strncpy() и strncat() в безопасных строковых операциях понимаются очень плохо.

Аудит безопасности OpenBSD обнаружил, что ошибки с этими функциями были "безудержными". В отличие от gets(), эти функции можно использовать безопасно, но на практике возникает множество проблем из-за запутанного, неинтуитивного и сложного для правильного использования интерфейса. Я знаю, что компания Microsoft также провела анализ (хотя я не знаю, сколько данных они опубликовали), и в результате запретила (или, по крайней мере, очень настоятельно не рекомендовала - "запрет" может не быть абсолютным) использование strncat() и strncpy() (среди прочих функций).

Некоторые ссылки с дополнительной информацией:

19
ответ дан 24 November 2019 в 07:50
поделиться
Другие вопросы по тегам:

Похожие вопросы: