Чому strncpy не закінчує null?


76

strncpy()нібито захищає від переповнення буфера. Але якщо це запобігає переповненню без нульового закінчення, швидше за все, наступна операція рядка буде переповнена. Тож для захисту від цього я роблю:


man strncpy дає:

strncpy()Функція аналогічна, за винятком того, що не більше ніж nбайти srcкопіюються. Таким чином, якщо серед перших nбайтів немає нульового байта src, результат не закінчується нулем.

Без нульового припинення чогось, здавалося б, невинного типу:

... може зірватися.


Чи є кращі, безпечніші альтернативи strncpy()?


1
Зверніть увагу, що на MacOS X (BSD) на сторінці користувача написано (про ' extern char *strncpy(char * restrict s1, const char * restrict s2, size_t n);'): Функція strncpy () копіює не більше n символів із s2 у s1. Якщо довжина s2 менша за n символів, залишок s1 заповнюється символами `\ 0 '. В іншому випадку s1 не припиняється.
Джонатан Леффлер

Чи не це dest [LEN-1] = '\ 0'; ?
codeObserver

1
Ось як би я думав, ми зробили б копію рядка: int LEN = src.len; str * dest = новий символ [LEN + 1]; strncpy (dest, src, LEN); dest [LEN] = '\ 0';
codeObserver

Завжди використовувати memset у цільовому рядку - найбезпечніший підхід, якщо ви впевнені, що розмір рядка не перевищуватиме довжину буфера призначення.
koolvcvc

напишіть свою власну функцію, я не думаю, що це має бути важким завданням
Меґхарадж

Відповіді:


46

strncpy()не призначений для використання як безпечніший strcpy(), він повинен використовуватися для вставки одного рядка в середину іншого.

Всі ті "безпечні" функції обробки рядків, такі як snprintf()і vsnprintf()є виправленнями, які були додані в пізніших стандартах для пом'якшення використання переповнення буфера тощо.

Вікіпедія згадує strncat()як альтернативу написанню власного сейфу strncpy():

РЕДАГУВАТИ

Я пропустив, що strncat()перевищує символи LEN при нульовому закінченні рядка, якщо він довший або дорівнює символу LEN.

У будь-якому випадку, сенс використання strncat()замість будь-якого доморощеного рішення, такого як memcpy(..., strlen(...))/ незалежно від того, що реалізація strncat()може бути цільовою / платформою, оптимізованою в бібліотеці.

Звичайно, вам потрібно перевірити, чи dst містить принаймні nullchar, тому правильне використання strncat()буде приблизно таким:

Я також визнаю, що strncpy()це не дуже корисно для копіювання підрядка в інший рядок, якщо src коротший за n char, рядок призначення буде усічений.


28
"він повинен використовуватися для вставки одного рядка в середину іншого" - ні, він призначений для запису рядка в поле фіксованої ширини, наприклад, у записі каталогу. Ось чому він накладає вихідний буфер на NUL, якщо (і лише якщо) вихідний рядок занадто короткий.
Steve Jessop

3
Як установка * dst = '\ 0' робить це безпечнішим? У нього все ще є початкова проблема - дозволити вам писати після закінчення цільового буфера.
Адам Лісс

3
звучить добре, але чи не слід це strncat (dst, src, LEN-1), враховуючи те, що він збирається написати додатковий символ?
Тімоті Пратлі,

3
@Jonathan: Насправді безпечним буде тип даних, що поєднує вказівник на буфер символів із довжиною цього буфера. Але всі ми знаємо, що цього не станеться. Особисто мене втомлюють усі ці зусилля, щоб зробити щось, що за своєю суттю небезпечно (програмісти намагаються точно поважати довжину буфера), дещо безпечніше. Це не так, ніби зараз у нас на 50% занадто багато перевитрат буфера, тому якби ми могли зробити обробку рядків на 50% безпечнішою, ми б у порядку :-(
Стів Джессоп,

1
+1 за те, що strncpy не повторює сміття, якимось чином є безпечною версією strcpy - перший має свої проблеми.
paxdiablo

26

Спочатку файлова система UNIX 7-го видання (див. DIR (5)) мала записи каталогів, що обмежували імена файлів 14 байтами; кожен запис у каталозі складався з 2 байтів для номера inode плюс 14 байт для імені, заповненого нулем до 14 символів, але не обов'язково закінчуваного нулем. Я вважаю, що він strncpy()був розроблений для роботи з цими структурами каталогів - або, принаймні, він ідеально підходить для цієї структури.

Розглянемо:

  • Ім'я файлу з 14 символів не припиняється з нулем.
  • Якщо ім'я було коротше 14 байт, воно було заповнене нулем до повної довжини (14 байт).

Це саме те, чого можна було б досягти, якщо:

Отже, strncpy()ідеально підійшов до своєї початкової нішевої програми. Йшлося лише випадково про запобігання переповненню рядків із нульовим закінченням.

(Зверніть увагу, що нульове заповнення до довжини 14 не є серйозними накладними витратами - якщо довжина буфера становить 4 КБ, і все, що вам потрібно, це безпечно скопіювати в нього 20 символів, то зайві 4075 нулів є серйозним надлишком, і їх легко можна призведе до квадратичної поведінки, якщо ви неодноразово додаєте матеріал у довгий буфер.)


2
Ця конкретна ситуація може бути неясною, але не зовсім рідко бувають структури даних із рядками полів фіксованої довжини, які заповнюються нулем, але не закінчуються нулем. Дійсно, якщо хтось зберігає дані фіксованого формату, це часто найефективніший спосіб це зробити.
supercat

24

Уже існують реалізації з відкритим кодом, такі як strlcpy які виконують безпечне копіювання.

http://en.wikipedia.org/wiki/Strlcpy

У посиланнях є посилання на джерела.


1
Не кажучи вже про портативний, швидкий і надійний. Ви все ще можете зловживати нею, але ризик на порядок нижче. IMO, strncpy слід припинити і замінити на ту саму функцію, яка називається dirnamecpy, або щось подібне. strncpy не є безпечною копією рядка і ніколи не був.

9

Strncpy безпечніший від атак переповнення стека користувачем вашої програми, він не захищає вас від помилок, які робить програміст, таких як друк рядка, що не закінчується нулем, як ви описали.

Ви можете уникнути збою описаної проблеми, обмеживши кількість символів, надрукованих printf:


Використання поля точності для обмеження кількості друкованих символів %sповинно бути однією з найбільш незрозумілих особливостей C.
Девід Торнлі,

@DavidThornley Це дуже чітко зафіксовано в K&R під sprintf.
weston

@weston: А в Harbison & Steele - це те, що я маю тут на роботі. Тепер, в яких популярних книгах С, окрім цих двох, це згадується? Кожна функція повинна згадуватися в K&R та H&S (і згадується в Стандарті), тому, якщо це стандарт невідомості, немає неясних особливостей.
Девід Торнлі,

@DavidThornley Я просто хотів збалансувати ваш коментар, тому що, додавши "одну з найбільш незрозумілих функцій", ця відповідь виглядає погано, і люди можуть бути відкладені від її використання. Що неправильно, оскільки це цілком допустима, добре задокументована функція, а також задокументована як і будь-яке інше використання поля точності. "Незрозуміле", здається, питання думки, оскільки особисто я бачу, що це дуже часто використовується.
weston

8

Деякі нові альтернативи вказані в ISO / IEC TR 24731 (перевірте https://buildsecurityin.us-cert.gov/daisy/bsi/articles/knowledge/coding/317-BSI.html ). Більшість із цих функцій приймають додатковий параметр, який визначає максимальну довжину цільової змінної, гарантує, що всі рядки мають нульове закінчення та мають імена, які закінчуються на _s(для "безпечного"?), Щоб відрізняти їх від попередніх "небезпечних" версій . 1

На жаль, вони все ще отримують підтримку і можуть бути недоступними з вашим набором інструментів. Пізніші версії Visual Studio видаватимуть попередження, якщо ви використовуєте старі небезпечні функції.

Якщо ваші інструменти не підтримують нові функції, створити власні обгортки для старих функцій має бути досить просто. Ось приклад:

Ви можете змінити функцію відповідно до своїх потреб, наприклад, завжди копіювати якомога більшу кількість рядків без переповнення. Насправді реалізація VC ++ може це зробити, якщо ви передасте _TRUNCATEяк count.




1 Звичайно, вам все одно потрібно бути точним щодо розміру цільового буфера: якщо ви надаєте 3-символьний буфер, але говорите, що в strcpy_s()ньому є місце для 25 символів, ви все ще маєте проблеми.


Ви не можете легально визначити функцію, назва якої починається з str *, тобто "простір імен" зарезервовано в C.
розкрутіть

2
Але комітет ISO C може - і зробив. Дивіться також: stackoverflow.com/questions/372980/…
Джонатан Леффлер

@ Джонатан: Дякую за перехресне посилання на ваше власне запитання, яке надає багато додаткової корисної інформації.
Адам Лісс,

5

Використання strlcpy(), зазначене тут:http://www.courtesan.com/todd/papers/strlcpy.html

Якщо у вашому libc немає реалізації, спробуйте це:

(Написано мною у 2004 р. - присвячено загальнодоступному надбанню.)


будь-ласка, просвіти мене, чому ти хочеш, щоб результат завжди був довжиною рядка src? на мою думку, повернення srclenбуде кращим, оскільки ми знали б, скільки символів дійсно скопійовано.
Ле Куанг Дуй,

@ LêQuangDuy, це відповідно до специфікації ( freebsd.org/cgi/man.cgi?query=strlcpy&sektion=3#end ): як snprintf , strlcat , він повертає розмір рядка він намагався писати, тому банки абонента забезпечити більший буфер і повторно викликати функцію, щоб зберегти все.
Джонатан Лідбек,

3

strncpy працює безпосередньо з доступними рядковими буферами. Якщо ви працюєте безпосередньо зі своєю пам’яттю, то зараз ПОВИННІ розміри буферів, і ви можете встановити '\ 0' вручну.

Я вважаю, що кращої альтернативи в простому C немає, але насправді це не так вже й погано, якщо ви будете настільки обережні, як повинні бути, граючи з необробленою пам'яттю.


3

Замість strncpy(), ви могли б використовувати

Ось однорядковий рядок, який копіює більшість size-1ненульових символів з srcдо destта додає нульовий термінатор:


Ми використовуємо макрос, еквівалентний snprintf(buffer, sizeof(buffer), "%s", src). Працює чудово, доки ви пам’ятаєте, що ніколи не використовуєте його на напрямках char *
че

3

Я завжди вважав за краще:

щоб виправити це згодом, але це насправді лише питання переваг.


1
Чи ініціалізувати всі буфери до нуля - це тема дискусії сама по собі. Особисто я вважаю за краще робити це під час розробки / налагодження, оскільки це, як правило, робить помилки більш очевидними, але існує безліч інших ("дешевших") варіантів.
Адам Лісс

7
вам потрібно лише встановити dest[LEN-1]значення 0- інші байти будуть заповнені strncpy()при необхідності (пам’ятайте: strncpy(s,d,n)ЗАВЖДИ пише nбайти!)
Крістоф

2

Ці функції розвивалися більше, ніж були розроблені, тому насправді немає "чому". Потрібно просто навчитися "як". На жаль, принаймні сторінки з інструкціями для Linux позбавлені загальноприйнятих прикладів використання цих функцій, і я помітив багато зловживань у коді, який я переглядав. Я зробив кілька приміток тут: http://www.pixelbeat.org/programming/gcc/string_buffers.html


Е-м-м, чому у вказаній вище URL-адресі _ спотворено%% F Підкреслення прекрасне відповідно до RFC 3548.
pixelbeat

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

2

Не покладаючись на новіші розширення, я зробив щось подібне раніше:

а можливо навіть:

Чому макроси замість новіших "вбудованих" (?) Функцій? Оскільки раніше існувало досить багато різних Unices, а також інших середовищ, не пов’язаних з unix (не Windows), які мені доводилося переносити назад, коли я щодня робив C.

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