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

80

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

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

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

Анонимный
источник

Ответы:

112

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

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

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

Муравей
источник
9
Я не совсем согласен. Было бы неплохо , если бы strlcpyи strlcatхотел сообщить какие - то условие ошибки , если они торкались ограничением размера буфера назначения. Хотя вы можете проверить возвращенную длину, чтобы проверить это, это не очевидно. Но я думаю, что это незначительная критика. Аргумент «они поощряют использование строк C, поэтому они плохие» - глупый.
Omnifarious
7
«Как эти функции могут привести к проблемам с безопасностью» - fwiw Я думаю, проблема в том, что некоторые функции C труднее использовать правильно, чем другие. Некоторые люди ошибочно полагают, что существует особый порог сложности, ниже которого функция считается «безопасной», а выше - «небезопасной». Такие люди обычно также убеждение , что strcpyвыше порога и , следовательно , «небезопасных», и их предпочтительной строки копирования функции (будь то strlcpy, strcpy_sили даже strncpy) ниже порога и , следовательно , «безопасный».
Стив Джессоп,
31
Есть множество причин, по которым strlcpy / strlcat не нравится, но вы не указываете ни одной из них. Обсуждение C ++ и Java неуместно. Этот ответ просто бесполезен для предмета, о котором на самом деле задан вопрос.
Джон Рипли
24
@John Ripley: Во-первых, я не «заявляю ни о каком из них» просто потому, что мне не известны причины для неприязни strlcpy/strlcat. Можно "не любить" общую концепцию строки с нулевым завершением, но вопрос не в этом. Если вы знаете «множество причин не любить strlcpy/strlcat», вам, вероятно, следует написать свой собственный ответ вместо того, чтобы ожидать, что я смогу читать чужие мысли.
AnT
8
@John Ripley: Во-вторых, вопрос конкретно касался некоторых предполагаемых "проблем безопасности" с strlcpy/strlcat. Хотя я считаю, что понимаю, о чем идет речь, я лично отказываюсь признать это «проблемами безопасности» в области традиционного языка C, как я его знаю. Об этом я сказал в своем ответе.
AnT
31

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

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

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

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

кафе
источник
13
Читаемость кода Дреппера плохая. С помощью strlcpy (или любой функции str) я напрямую знаю, что копирую строку C. С memcpyним может быть любым типом памяти , и у меня есть дополнительное измерение , чтобы проверить, пытаясь понять код. У меня было устаревшее приложение для отладки, где все делалось с помощью memcpy, это была настоящая PITA, которую нужно было исправить. После переноса на выделенную строковую функцию ее намного легче читать (и быстрее, потому что strlenможно удалить много ненужного ).
Патрик Шлютер
3
@domen: поскольку размер для копирования уже известен, этого memcpy()достаточно (и потенциально более эффективно strcpy()).
caf
1
Это сбивает с толку в строковых операциях. И насколько мне известно, эффективность зависит от реализации и не стандартизирована.
domen
8
@domen: memcpy() это операция строки - это декларируется в <string.h>конце концов.
caf
2
@domen Я согласен с тем, что есть вероятность путаницы, но на самом деле работа со строками C в любом случае в значительной степени работает с необработанной памятью. Возможно, нам всем было бы лучше, если бы люди вообще перестали думать о C как о «строках» (в отличие от любых других непрерывных фрагментов памяти).
mtraceur 02
19

Когда люди говорят: « 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. Это делает ее даже менее полезной, чем в противном случае.

Алок Сингхал
источник
3
strlcpyработает быстрее, чем memcpyна многих архитектурах, особенно если memcpyкопируются ненужные конечные данные. strlcpyтакже возвращает, сколько данных вы пропустили, что может позволить вам восстановить быстрее и с меньшим количеством кода.
1
@jbcreix: я хочу сказать, что не должно быть данных, которые можно пропустить, и nв моем вызове memcpy указано точное количество байтов, которые нужно записать, поэтому эффективность тоже не является большой проблемой.
Алок Сингхал,
5
А как ты это понял? Единственное, что вы можете знать заранее, - это размер буфера. Конечно , если вы предлагаете повторное внедрение strlcpyкаждый раз , когда вам это нужно , используя memcpyи strlenэто тоже нормально, но тогда почему остановка на strlcpy, вам не нужно в memcpyфункцию либо, вы можете скопировать байты один на один. Эталонная реализация в обычном случае просматривает данные только один раз, и это лучше для большинства архитектур. Но даже если лучшая реализация использовала strlen+ memcpy, это все еще не причина, чтобы не повторно реализовывать безопасный strcpy снова и снова.
1
Алок, когда strlcpyвы проходите входную строку только один раз в хорошем случае (которых должно быть большинство) и два раза в плохом случае, с вашим strlen+ memcpyвы всегда проходите над ним дважды. Другое дело, если это имеет значение на практике.
Патрик Шлютер
4
"* ((char *) mempcpy (dst, src, n)) = 0;" - Правильно - Да, очевидно,
верно, Нет. Проверка
12

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

Jamesdlin
источник
8
Например, почтовый клиент может обрезать имя файла вложения электронной почты с malware.exe.jpgдо malware.exe.
Крис Петерсон
1
@ChrisPeterson Вот почему хороший разработчик всегда проверяет возвращаемые значения, чтобы, в случае функций strl *, знать, были ли данные усечены, и действовать соответствующим образом.
Tom Lint
«Ульрих и другие думают, что это создаст ложное чувство безопасности…» - Лол… тем временем Ульрих и его друзья регулярно появляются на BugTraq и Full Disclosure для своих разовых сообщений. Им следует использовать более безопасные функции и избегать большинства своих проблем. Тогда они смогут начать рассказывать другим, как писать более безопасный код ...
jww 04
7

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

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

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

  1. Не может использоваться с нестроковыми.

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

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


источник
обратите внимание, что strncpy_sэто не безопасная версия, strncpyа в основном strlcpyзамена.
6

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

Их критиковали за то, что они якобы неэффективны, а не небезопасны .

Согласно статье Дэмиена Миллера о безопасной переносимости :

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

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

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

rsp
источник
3

Безопасность не является логическим. Функции C не являются полностью «безопасными» или «небезопасными», «безопасными» или «небезопасными». При неправильном использовании простая операция присваивания в C может быть «небезопасной». strlcpy () и strlcat () можно использовать безопасно (надежно) так же, как strcpy () и strcat () можно безопасно использовать, когда программист предоставляет необходимые гарантии правильного использования.

Основным моментом всех этих строковых функций C, стандартных и нестандартных, является уровень, до которого они упрощают безопасное / надежное использование . Безопасное использование strcpy () и strcat () нетривиально; это подтверждается количеством раз, когда программисты C ошибались за эти годы, и в результате возникали опасные уязвимости и эксплойты. strlcpy () и strlcat () и, в этом отношении, strncpy () и strncat (), strncpy_s () и strncat_s () немного проще в использовании, но все же нетривиально. Они небезопасны / небезопасны? Не более чем memcpy () при неправильном использовании.

rswindell
источник