Почему strlcpy и strlcat считаются небезопасными?

Я понимаю, что strlcpy и strlcat были созданы как надежные замены для strncpy и strncat. Однако некоторые люди по-прежнему считают, что они небезопасны и просто вызывают разные проблемы.

Может ли кто-нибудь привести пример использования strlcpy или strlcat (т.е. функция, которая всегда возвращает нуль в своих строках) может привести к проблемам безопасности?

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

Ответ 1

Во-первых, strlcpy никогда не предназначался как безопасная версия strncpystrncpy никогда не предназначалась как безопасная версия strcpy). Эти две функции полностью не связаны. strncpy - это функция, которая вообще не имеет отношения к C-строкам (т.е. ничтожные строки). Тот факт, что он имеет префикс str... в его имени, является лишь исторической ошибкой. История и цель strncpy хорошо известны и хорошо документированы. Это функция, созданная для работы с так называемыми строками с фиксированной шириной (не с C-строками), используемыми в некоторых исторических версиях файловой системы Unix. Некоторые программисты сегодня путаются по имени и предполагают, что strncpy каким-то образом предполагается использовать функцию C-string с ограниченной длиной ( "безопасный" брат из strcpy), который на самом деле является полным бессмыслицей и ведет к плохому практика программирования. Стандартная библиотека C в текущей форме не имеет функции для копирования C-строк с ограниченной длиной. Здесь strlcpy подходит. strlcpy действительно является настоящей функцией копирования с ограниченной длиной, созданной для работы с C-строками. strlcpy правильно выполняет все функции копирования с ограниченной длиной. Единственная критика, на которую можно обратить внимание, заключается в том, что она, к сожалению, не стандартная.

Во-вторых, strncat, с другой стороны, действительно является функцией, которая работает с C-строками и выполняет конкатенацию с ограниченной длиной (это действительно "безопасный" брат из strcat). Чтобы правильно использовать эту функцию, программист должен проявлять особую осторожность, поскольку параметр размера, который принимает эта функция, на самом деле не является размером буфера, который получает результат, а размером его оставшейся части (также символом терминатора подсчитывается неявно). Это может сбивать с толку, поскольку для того, чтобы связать этот размер с размером буфера, программист должен помнить о выполнении дополнительных вычислений, которые часто используются для критики strncat. strlcat заботится об этих проблемах, меняя интерфейс, чтобы дополнительные вычисления не требовались (по крайней мере, в вызывающем коде). Опять же, единственная основа, которую я вижу, может критиковать это потому, что функция не является стандартной. Кроме того, функции из группы strcat - это то, что вы не увидите в профессиональном коде очень часто из-за ограниченности использования самой идеи конкатенации строк на основе повторного сканирования.

Что касается того, как эти функции могут привести к проблемам безопасности... Они просто не могут. Они не могут привести к проблемам безопасности в большей степени, чем сам язык C может "привести к проблемам безопасности". Видите ли, довольно долгое время было сильное чувство, что язык С++ должен двигаться в направлении превращения в какой-то странный аромат Java. Это чувство иногда распространяется и на область языка C, что приводит к довольно бессловесной и вынужденной критике особенностей языка C и особенностей стандартной библиотеки C. Я подозреваю, что мы можем иметь дело с чем-то подобным и в этом случае, хотя я, конечно же, надеюсь, что все не так уж плохо.

Ответ 2

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

if (destlen + sourcelen > dest_maxlen)
{
    /* Bug out */
}

Теперь strlcat действительно выполняет эту проверку, если программист помнит, чтобы проверить результат - чтобы вы могли безопасно использовать его:

if (strlcat(dest, source, dest_bufferlen) >= dest_bufferlen)
{
    /* Bug out */
}

Точка Ульриха заключается в том, что, поскольку вы должны иметь destlen и sourcelen вокруг (или пересчитывать их, что эффективно делает strlcat), вы можете просто использовать более эффективный memcpy в любом случае:

if (destlen + sourcelen > dest_maxlen)
{
    goto error_out;
}
memcpy(dest + destlen, source, sourcelen + 1);
destlen += sourcelen;

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

Ответ 3

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

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

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

Почему мне не нравится strncpy()?

  • strncpy() - это плохое решение в большинстве случаев: ваша строка будет усечена без какого-либо уведомления — я бы предпочел написать дополнительный код, чтобы понять это сам, а затем взять курс действий, который я хочу взять, а чем пусть какая-то функция решит для меня, что делать.
  • strncpy() очень неэффективен. Он записывает каждый байт в буфер назначения. Вам не нужны те тысячи '\0' в конце вашего пункта назначения.
  • Он не записывает завершающий '\0', если назначение недостаточно велико. Итак, вы должны сделать это сами. Сложность этого не стоит проблем.

Теперь мы переходим к strlcpy(). Изменения от strncpy() делают это лучше, но я не уверен, что конкретное поведение strl* гарантирует их существование: они слишком специфичны. Вы все еще должны знать размер назначения. Он более эффективен, чем strncpy(), потому что он не обязательно записывается в каждый байт в пункте назначения. Но он решает проблему, которую можно решить, выполнив: *((char *)mempcpy(dst, src, n)) = 0;.

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

Основная проблема здесь: сколько байтов копировать? Программист должен это знать, а если нет, strncpy() или strlcpy() не сохранит его.

strlcpy() и strlcat() не являются стандартными, ни ISO C, ни POSIX. Таким образом, их использование в переносных программах невозможно. Фактически, strlcat() имеет два разных варианта: реализация Solaris отличается от других для случаев краев, связанных с длиной 0. Это делает ее даже менее полезно, чем в противном случае.

Ответ 4

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

Ответ 5

Существуют две проблемы, связанные с использованием функций strl:

  • Вы должны проверить возвращаемые значения чтобы избежать усечения.

Стандартные авторы сценариев c1x и Drepper утверждают, что программисты не будут проверять возвращаемое значение. Drepper говорит, что мы должны каким-то образом знать длину и использовать memcpy и вообще избегать строковых функций. Комитет по стандартам утверждает, что защищенная strcpy должна возвращать ненулевое значение при усечении, если не указано иначе флагом _TRUNCATE. Идея состоит в том, что люди с большей вероятностью будут использовать if (strncpy_s (...)).

  1. Нельзя использовать для не-строк.

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

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

Ответ 6

Я не думаю, что strlcpy и strlcat считают небезопасным, или, по крайней мере, это не причина, почему они не включены в glibc - в конце концов, glibc включает strncpy и даже strcpy.

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

В соответствии с документом "Безопасная переносимость" Дэмиеном Миллером:

API strlcpy и strlcat правильно проверяют границы целевых буферов, nul-terminate во всех случаях и возвращает длину исходной строки, позволяя обнаружить усечение. Этот API был принят большинством современные операционные системы и множество автономных программных пакетов, включая OpenBSD (где он был создан), Sun Solaris, FreeBSD, NetBSD, ядро Linux, rsync и проект GNOME. Заметное исключение является стандартной библиотекой C GNU, glibc [12], сопровождающей неуклонно отказывается включать эти усовершенствованные API, маркируя их "ужасно неэффективное дерьмо BSD" [4], несмотря на предыдущие доказательства того, что они быстрее всего в большинстве случаев, чем API, которые они заменяют [13]. В результате, более 100 пакетов программного обеспечения, присутствующих в дереве портов OpenBSD поддерживать свои собственные strlcpy и/или strlcat замены или эквивалентные API - не идеальное положение дел.

Вот почему они недоступны в glibc, но неверно, что они недоступны в Linux. Они доступны в Linux в libbsd:

Они упакованы в Debian и Ubuntu и другие дистрибутивы. Вы также можете просто взять копию и использовать в своем проекте - это короткий и под разрешительной лицензией: