Чому strlcpy і strlcat вважаються небезпечними?


80

Я розумію це strlcpyі strlcatбув розроблений як безпечна заміна для strncpyі strncat. Однак деякі люди досі дотримуються думки, що вони невпевнені в собі і просто спричиняють інший тип проблем .

Чи може хтось навести приклад того, як використання strlcpyабо strlcat(тобто функції, яка завжди закінчує свої рядки нулем) може призвести до проблем із безпекою?

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

Відповіді:


112

По-перше, strlcpyніколи не передбачалося як захищена версія strncpystrncpyніколи не передбачалося як захищена версія strcpy). Ці дві функції абсолютно не пов'язані. strncpy- це функція, яка взагалі не має відношення до C-рядків (тобто рядків із нульовим закінченням). Той факт, що str...в його назві є префікс, - це лише історичний промах. Історія та цілі компанії strncpyдобре відомі та добре задокументовані. Це функція, створена для роботи з так званими рядками "фіксованої ширини" (не з C-рядками), що використовується в деяких історичних версіях файлової системи Unix. Деякі програмісти сьогодні заплутані в його назві і припускають, що strncpyце якимось чином повинно виконувати функцію копіювання рядків з обмеженою довжиною ("безпечний" братstrcpy), що насправді є повною нісенітницею і веде до поганої практики програмування. Стандартна бібліотека C у своєму поточному вигляді не має жодної функції копіювання C-рядків обмеженої довжини. Саме тут strlcpyвписується. strlcpyСправді справжня функція копіювання обмеженої довжини, створена для роботи з C-рядками. strlcpyправильно робить все, що повинна робити функція копіювання обмеженої довжини. Єдина критика, на яку можна націлити, полягає в тому, що вона, на жаль, не є стандартною.

По-друге, strncatз іншого боку, це дійсно функція, яка працює з C-рядками та виконує конкатенацію обмеженої довжини (це справді "безпечний" брат чи сестра strcat). Щоб правильно використовувати цю функцію, програміст повинен проявити особливу обережність, оскільки параметр розміру, який ця функція приймає, насправді не є розміром буфера, що отримує результат, а розміром його решти частини (також символ закінчувача вважається неявно). Це може заплутати, оскільки для того, щоб прив'язати цей розмір до розміру буфера, програміст повинен пам'ятати про виконання деяких додаткових обчислень, що часто використовується для критики strncat.strlcatопікується цими проблемами, змінюючи інтерфейс, так що додаткові обчислення не потрібні (принаймні у телефонному коді). Знову ж таки, єдина підстава, на яку я можу критикувати це, - це те, що функція не є стандартною. Крім того, функції strcatгрупи - це те, чого ви не побачите в професійному коді дуже часто через обмежену зручність самої ідеї конкатенації рядків на основі повторного сканування.

Щодо того, як ці функції можуть призвести до проблем із безпекою ... Вони просто не можуть. Вони не можуть призвести до проблем безпеки в більшій мірі, ніж сама мова С може "призвести до проблем безпеки". Розумієте, досить давно існували сильні настрої, що мова С ++ повинна рухатись у напрямку розвитку в якийсь дивний смак Java. Це почуття іноді переходить і в область мови C, що призводить до досить безглуздої та вимушеної критики особливостей мови C та особливостей стандартної бібліотеки C. Я підозрюю, що ми могли б мати справу з чимось подібним і в цьому випадку, хоча я, безумовно, сподіваюся, що все насправді не так погано.


9
Я не повністю згоден. Було б непогано, якщо strlcpyі strlcatповідомили б про якусь помилку, якщо вони зіткнулися з обмеженням розміру буфера призначення. Хоча ви можете перевірити повернуту довжину, щоб перевірити це, це не очевидно. Але я думаю, що це незначна критика. Аргумент "вони заохочують використання рядків C, і тому вони погані" - дурний.
Всезначний

7
"як ці функції можуть призвести до проблем із безпекою" - fwiw, я думаю, тут справа в тому, що деякі функції С важче використовувати правильно, ніж інші. Деякі люди помилково вважають, що існує особливий поріг складності, нижче якого функція "безпечна", а вище "небезпечна". Такі люди зазвичай також переконання , що strcpyвище порога і , отже , «небезпечних», і їх кращою рядки копіювання функції (будь то strlcpy, strcpy_sабо навіть strncpy) нижче порога і , отже , «безпечний».
Steve Jessop

31
Існує безліч причин не подобатися strlcpy / strlcat, але ви не вказали жодної з них. Обговорення С ++ та Java не має значення. Ця відповідь просто не корисна для теми, про яку насправді задавали питання.
Джон Ріплі

24
@ Джон Ріплі: По-перше, я не "заявляю жодного з них" просто тому, що мені невідомі причини неприязні strlcpy/strlcat. Хтось може "не подобатися" загальній концепції нульового закінчення рядка, але не в цьому питання. Якщо ви знаєте "безліч причин, щоб не любити strlcpy/strlcat", вам, мабуть, слід написати власну відповідь, замість того, щоб очікувати, що я зможу прочитати чужі думки.
AnT

8
@ Джон Ріплі: По-друге, питання конкретно стосувалося деяких передбачуваних "проблем із безпекою" strlcpy/strlcat. Хоча я вважаю, що розумію, про що йдеться, я особисто відмовляюся визнавати це як "проблеми безпеки" у сфері традиційної мови С, як я її знаю. Що я зазначив у своїй відповіді.
AnT

31

Критика Ульріха базується на ідеї, що усічення рядків, яке не виявляється програмою, може призвести до проблем безпеки через неправильну логіку. Тому, щоб бути в безпеці, потрібно перевірити на усічення. Робити це для конкатенації рядків означає, що ви виконуєте перевірку за лініями цього:

Тепер strlcatефективно робить цю перевірку, якщо програміст пам’ятає перевірити результат - щоб ви могли безпечно використовувати її:

Суть Ульріха полягає в тому, що, оскільки вам доведеться мати destlenі sourcelenнавколо (або перерахувати їх, що саме strlcatефективно робить), ви можете в memcpyбудь-якому випадку просто використовувати більш ефективний :

(У наведеному вище коді dest_maxlen- це максимальна довжина рядка, який можна зберегти dest- на один менше, ніж розмір destбуфера. dest_bufferlenЦе повний розмір dest buffer).


13
Читаність коду Drepper погана. За допомогою strlcpy (або будь-якої функції str) я знаю прямо, що копіюю рядок C, який закінчується 0. З memcpyним може бути будь-який тип пам'яті, і я маю додатковий вимір, який потрібно перевірити, намагаючись зрозуміти код. У мене був застарілий додаток для налагодження, де все робилося за допомогою memcpy, це справжній ПІТА для виправлення. Після перенесення на виділену функцію String читати набагато легше (і швидше, оскільки багато непотрібного strlenможна видалити).
Патрік Шлютер,

3
@domen: Оскільки розмір для копіювання вже відомий, то memcpy()достатній (і потенційно ефективніший, ніж strcpy()).
кафе

1
Ну, це бентежить його в рядкових операціях. І наскільки я знаю, ефективність залежить від реалізації та не є стандартизованою.
домен

8
@domen: memcpy() це операція рядка - вона <string.h>, зрештою , оголошена в .
кафе

2
@domen Я погоджуюсь, що існує плутанина, але реальність така, що робота з рядками C в основному працює з необробленою пам'яттю. Можна стверджувати, що нам усім було б краще, якби люди взагалі перестали думати про С як про "струни" (на відміну від будь-яких інших суміжних шматків пам'яті).
mtraceur 02

19

Коли люди кажуть: " strcpy()небезпечно, використовуй strncpy()замість цього" (або подібні твердження щодо strcat()тощо, але я буду використовувати strcpy()тут свою увагу), вони означають, що немає меж для реєстрації strcpy(). Таким чином, занадто довгий рядок призведе до перевитрати буфера. Вони правильні. Використання strncpy()в цьому випадку запобіжить перевищення буфера.

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

Як програміст на С, ви повинні знати розмір призначення, перш ніж намагатись копіювати рядки. Це припущення 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 вказана точна кількість байтів, яку слід записати, тому ефективність теж не є такою великою проблемою.
Alok Singhal

5
І як ви отримуєте це n? Єдине, що ви можете знати заздалегідь, - це розмір буфера. Звичайно , якщо ви пропонуєте повторне використання strlcpyкожен раз , коли вам це потрібно , використовуючи memcpyі strlenце теж нормально, але тоді чому зупинка на strlcpy, вам не потрібно в memcpyфункцію або, ви можете скопіювати байти один на один. Посилальна реалізація лише один раз переглядає дані в звичайному випадку, і це краще для більшості архітектур. Але навіть якщо використовується найкраща реалізація strlen+ memcpy, це все одно не є підставою для того, щоб не потрібно повторно реалізовувати захищений strcpy знову і знову.

1
Alok, коли strlcpyви прокручуєте вхідний рядок лише один раз у хорошому випадку (а це повинно бути більшістю) і двічі у поганому, при цьому ваш strlen+ memcpyви завжди переглядаєте його двічі. Якщо це щось змінює на практиці, це вже інша історія.
Патрік Шлютер,

4
"* ((char *) mempcpy (dst, src, n)) = 0;" - Правильно - Так, очевидно правильно, ні. Не вдається переглянути код ......
mattnz

12

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


8
Наприклад, поштовий клієнт може скоротити ім'я файлу вкладення електронної пошти від malware.exe.jpgдо malware.exe.
Chris Peterson,

1
@ChrisPeterson Ось чому хороший розробник завжди перевіряє повернені значення, щоб у випадку функцій strl * знати, чи дані були усічені, і діяти відповідно.
Tom Lint

"Ульріх та інші вважають, що це дасть помилкове відчуття безпеки ...." - Лол ... тим часом Ульріх та його друзі регулярно виходять на BugTraq та "Повне розкриття" для своїх одноразових. Вони повинні використовувати безпечніші функції та уникати більшості своїх проблем. Тоді вони можуть почати розповідати іншим, як писати більш безпечний код ...
jww

7

Є дві "проблеми", пов'язані з використанням функцій strl:

  1. Вам потрібно перевірити повернені значення, щоб уникнути усічення.

Стандартні редактори чернеток c1x та Drepper стверджують, що програмісти не перевіряють повернене значення. Дреппер каже, що ми повинні якось знати довжину і використовувати 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. Помітним винятком є ​​стандартна бібліотека C GNU, glibc [12], супровідник якої наполегливо відмовляється включати ці вдосконалені API, позначаючи їх «жахливо неефективною BSD-лайною»[4], незважаючи на попередні докази того, що вони швидші, в більшості випадків перевершують замінені ними API [13]. Як результат, понад 100 програмних пакетів, наявних у дереві портів OpenBSD, підтримують власні замінники strlcpy та / або strlcat або еквівалентні API - не ідеальний стан справ.

Ось чому вони недоступні в glibc, але неправда, що вони недоступні в Linux. Вони доступні в Linux у libbsd:

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


3

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

Головне у всіх цих строкових функцій C, стандартні і не дуже стандартний, це рівень , до якого вони роблять безпечний / безпечне використання легко . strcpy () та strcat () не є тривіальними для безпечного використання; це доводиться тим, як багато років програмісти C помилялися протягом багатьох років, і виникали неприємні вразливості та подвиги. strlcpy () та strlcat (), і з цього приводу strncpy () та strncat (), strncpy_s () та strncat_s () трохи простіші у безпечному використанні, але все ж нетривіальні. Вони небезпечні / небезпечні? При неправильному використанні не більше, ніж memcpy ().

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.