Каких функций из стандартной библиотеки следует (следует) избегать?

Я читал о переполнении стека, что некоторые функции C "устарели"или" следует избегать". Не могли бы вы дать мне несколько примеров такого рода функции и причину, почему?

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

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

13 ответов


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

еще несколько примеров включают tmpfile() и mktemp().

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

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

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

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

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

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

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

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


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

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

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

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

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

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


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

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

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

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

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

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

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

некоторые ссылки с более


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

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

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


избежать

  • strtok для многопоточных программ, как его не потокобезопасна.
  • gets как это может вызвать переполнение буфера

стандартные библиотечные функции, которые должны никогда используется:

команду setjmp.h

  • setjmp(). Вместе с longjmp(), эти функции широко признаны невероятно опасными для использования: они приводят к программированию спагетти, они приходят с многочисленными формами неопределенного поведения, они могут вызывать непреднамеренные побочные эффекты в программной среде, такие как влияние значений, хранящихся в стеке. Ссылки на литературу: MISRA-C:2012 правило 21.4,СЕРТИФИКАТ C MSC22-C.
  • longjmp(). См.setjmp().

С stdio.h

  • gets(). Функция была удалена из языка C (согласно C11), поскольку она была небезопасной в соответствии с дизайном. Функция уже была помечена как устаревшая в C99. Использовать fgets() вместо. Литература: ISO 9899: 2011 K. 3.5.4.1, Также см. Примечание 404).

stdlib.h

  • atoi() семейство функций. Они не имеют обработки ошибок, но вызывают неопределенное поведение при возникновении ошибок. Совершенно лишние функции, которые можно заменить на strtol() семейство функций. Литература: Мисра-с: 2012 правило 21.7.

строку.h

  • strncat(). Имеет неудобный интерфейс, который часто используется неправильно. Это в основном лишняя функция. Также см. примечания для strncpy().
  • strncpy(). Цель этой функции никогда не была более безопасной версией strcpy(). Его единственной целью всегда была обработка древнего строкового формата в системах Unix, и то, что он был включен в стандартную библиотеку, является известной ошибкой. Эта функция опасна, потому что она может оставить строку без завершения null, и программисты часто используют ее неправильно. Литература:почему рассматриваются strlcpy и strlcat неуверенность?.

стандартные библиотечные функции, которые следует использовать с осторожностью:

assert.h

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

сигнал.h

включаемом файле stdarg.h

  • va_arg() семейство функций. Наличие функций переменной длины в программе C почти всегда является признаком плохого дизайна программы. Следует избегать, если у вас нет очень специфических требований.

С stdio.h
В общем,вся эта библиотека не рекомендуется для производственного кода, по мере того как оно приходит с многочисленными случаями плохо-определенного поведения и плохого типа безопасности.

  • fflush(). Отлично подходит для выходных потоков. Вызывает неопределенное поведение, если используется для входных потоков.
  • gets_s(). Безопасная версия gets() включено в интерфейс проверки границ C11. Предпочтительно использовать fgets() вместо этого, как в C стандартные рекомендации. Литература: ISO 9899: 2011 К. 3.5.4.1.
  • printf() семейство функций. Ресурсные тяжелые функции, которые поставляются с большим количеством неопределенного поведения и плохой безопасностью типов. sprintf() тоже есть уязвимые места. Этих функций следует избегать в производственном коде. Литература: Мисра-с: 2012 правило 21.6.
  • scanf() семейство функций. См. замечания о printf(). Кроме того, - scanf() уязвим для переполнения буфера, если не используется правильно. fgets() предпочтительно использовать, когда это возможно. Ссылки на литературу: СЕРТИФИКАТ C INT05-C, MISRA-C: 2012 правило 21.6.
  • tmpfile() семейство функций. Поставляется с различными проблемами уязвимости. Литература:СЕРТИФИКАТ C FIO21-C.

stdlib.h

  • malloc() семейство функций. Отлично подходит для использования в размещенных системах, хотя будьте в курсе известных проблем в C90 и, следовательно,не бросайте результат. The malloc() семья функции не должны использоваться в автономных приложениях. Литература: Мисра-с: 2012 правило 21.3.

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

  • system(). Поставляется с большим количеством накладных расходов и, хотя портативный, часто лучше использовать системные функции API вместо этого. Поставляется с различным плохо определенным поведением. Литература:свиду C ENV33-C.

строку.h

  • strcat(). См. примечания для strcpy().
  • strcpy(). Идеально подходит для использования, если размер копируемых данных неизвестен или больше целевого буфера. Если проверка размера входящих данных не выполняется,может произойти переполнение буфера. В чем нет вины strcpy() сам, но вызывающего приложения-то strcpy() небезопасно в основном миф, созданный Корпорация Microsoft.
  • strtok(). Изменяет строку вызывающего абонента и использует внутренние переменные состояния, что может сделать ее небезопасной в многопоточной среде.

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

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

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

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


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

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


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


Не забывайте о 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 (windows) возвращаемое значение является отрицательным числом в случае усечения вывода.

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

  1. buffer overflow safe (многие функции уже упоминаются здесь)

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

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


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

но я бы не сказал "не 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;
    }
}

во всех сценариях копирования/перемещения строк-strcat(), strncat(), strcpy(), strncpy () и т. д. - дела идут гораздо лучше (безопасное) если применяется пара простых эвристик:

   1. Всегда NUL-заполняйте буфер(ы) перед добавлением данных.
   2. Объявить персонажа-буферов в качестве [размер+1], с макро-константы.

Например, дано:

#define   BUFSIZE   10
char      Buffer[BUFSIZE+1] = { 0x00 };  /* The compiler NUL-fills the rest */

мы можем использовать следующий код:

memset(Buffer,0x00,sizeof(Buffer));
strncpy(Buffer,BUFSIZE,"12345678901234567890");

относительно безопасно. Функции memset() должен появляются перед strncpy (), хотя мы инициализировали буфер во время компиляции, потому что мы не знаем, какой мусор другой код помещал в него до вызова нашей функции. Strncpy () усечет скопированные данные до "1234567890" и будет не NUL-прекратить его. Однако, поскольку мы уже заполнили NUL весь буфер-sizeof (Buffer), а не BUFSIZE-там гарантированно будет окончательный" вне области", завершающий NUL в любом случае, пока мы ограничиваем наши записи, используя константа BUFSIZE вместо sizeof (буфер).

Buffer и BUFSIZE также отлично работают для snprintf ():

memset(Buffer,0x00,sizeof(Buffer));
if(snprintf(Buffer,BUFIZE,"Data: %s","Too much data") > BUFSIZE) {
    /* Do some error-handling */
}   /* If using MFC, you need if(... < 0), instead */

хотя snprintf () специально пишет только символы BUFIZE-1, а затем NUL, это работает безопасно. Таким образом, мы "тратим" посторонний байт NUL в конце буфера...мы предотвращаем переполнение буфера и unterminated условия строки, для довольно небольшой стоимости памяти.

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

#define strncat(target,source,bufsize) snprintf(target,source,"%s%s",target,source)

заманчиво создать раскрывающийся список strcat (), но это не очень хорошая идея:

#define strcat(target,source) snprintf(target,sizeof(target),"%s%s",target,source)

потому что target может быть указателем (таким образом, sizeof () не возвращает необходимую нам информацию). У меня нет хорошего " universal" решение для экземпляров strcat () в вашем коде.

проблема, с которой я часто сталкиваюсь от программистов "strFunc()-aware",-это попытка защитить от переполнения буфера с помощью strlen(). Это нормально, если содержимое гарантированно будет аннулировано. В противном случае strlen() сам может вызвать ошибку переполнения буфера (обычно приводящую к нарушению сегментации или другой ситуации дампа ядра), прежде чем вы когда-либо достигнете "проблемного" кода, который вы пытаетесь защитить.


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