Почему strncpy не завершается null?
strncpy() якобы защищает от переполнения буфера. Но если он предотвращает переполнение без завершения null, то, скорее всего, последующая строковая операция будет переполняться. Поэтому, чтобы защитить себя от этого, я делаю:
strncpy( dest, src, LEN );
dest[LEN - 1] = '';
man strncpy выдает:
функция strncpy () аналогична, за исключением того, что копируется не более n байт src. Таким образом, если нет нулевого байта среди первых n байтов src, результат не будет нуль-завершается.
без нулевого завершения чего-то, казалось бы, невинного, как:
printf( "FOO: %sn", dest );
...может разбиться.
есть ли лучшие, более безопасные альтернативы strncpy()?
11 ответов:
strncpyне предназначен для использования в качестве безопаснееstrcpy, он должен использоваться для вставки одной строки в середине другой.все эти "безопасные" функции обработки строк, такие как
snprintfиvsnprintfисправления, которые были добавлены в более поздние стандарты для смягчения атакам переполнения буфера и т. д.Википедия упоминает
strncatв качестве альтернативы написанию собственного сейфаstrncpy:*dst = ''; strncat(dst, src, LEN);EDIT
я пропустил, что strncat превышает символы LEN, когда null завершает строку, если она длиннее или равна LEN char.
в любом случае, смысл использования strncat вместо любого доморощенного решения, такого как memcpy(..., strlen(...)) / независимо от того, что реализация strncat может быть оптимизирована для целевой/платформы в библиотеке.
конечно, вам нужно проверить, что dst содержит по крайней мере nullchar, поэтому правильное использование strncat будет что-то вроде:
if(LEN) { *dst = ''; strncat(dst, src, LEN-1); }Я также признаю, что strncpy не очень полезен для копирования подстроки в другую строку, если src короче n char, строка назначения будет усечена.
уже есть реализации с открытым исходным кодом, такие как strlcpy которые делают безопасное копирование.
http://en.wikipedia.org/wiki/Strlcpy
в ссылках есть ссылки на источники.
изначально 7-е издание UNIX файловая система(см. DIR (5)) имела записи каталога, которые ограничивали имена файлов до 14 байт; каждая запись в каталоге состояла из 2 байтов для номера индекса плюс 14 байтов для имени, null, дополненного до 14 символов, но не обязательно завершенного нулем. Это мое убеждение, что
strncpy()был разработан для работы с этими структурами каталогов - или, по крайней мере, он отлично работает для этого структура.считаем:
- 14-символьное имя файла не было завершено null.
- если имя было короче 14 байт, оно было заполнено null до полной длины (14 байт).
Это именно то, что будет достигнуто за счет:
strncpy(inode->d_name, filename, 14);и
strncpy()было идеально приспособлено к своему первоначально применению ниши. Это было только случайно о предотвращении переполнения в нули.(обратите внимание, что null заполнение до длины 14 не является серьезным накладным расходом - если длина буфера составляет 4 КБ, и все, что вы хотите, это безопасно скопировать в него 20 символов, то дополнительные 4075 нулей являются серьезным перебором и могут легко привести к квадратичному поведению, если вы неоднократно добавляете материал в длинный буфер.)
некоторые новые альтернативы указаны в ISO / IEC TR 24731 (Проверьте https://buildsecurityin.us-cert.gov/daisy/bsi/articles/knowledge/coding/317-BSI.html для информации). Большинство этих функций принимают дополнительный параметр, указывающий максимальную длину целевой переменной, гарантируют, что все строки завершаются нулем и имеют имена, которые заканчиваются на
_s(для "безопасного" ?) чтобы отличить их от их более ранних " небезопасных" версии.1к сожалению, они все еще получают поддержку и могут быть недоступны с вашим конкретным набором инструментов. Более поздние версии Visual Studio будут выдавать предупреждения, если вы используете старые небезопасные функции.
если ваши инструменты не поддержка новых функций, это должно быть довольно легко создать свои собственные обертки для старых функций. Вот пример:
errCode_t strncpy_safe(char *sDst, size_t lenDst, const char *sSrc, size_t count) { // No NULLs allowed. if (sDst == NULL || sSrc == NULL) return ERR_INVALID_ARGUMENT; // Validate buffer space. if (count >= lenDst) return ERR_BUFFER_OVERFLOW; // Copy and always null-terminate memcpy(sDst, sSrc, count); *(sDst + count) = ''; return OK; }вы можете изменить функцию в соответствии с вашими потребностями, для например, чтобы всегда копировать как можно больше строки без переполнения. Фактически, реализация VC++ может сделать это, если вы передадите
_TRUNCATEкакcount.
1конечно, вам все равно нужно быть точным о размере целевого буфера: если вы предоставляете 3-символьный буфер, но говоритеstrcpy_s()в нем есть место для 25 символов, вы все еще в беде.
Strncpy безопаснее против атак переполнения стека с помощью пользователей вашей программы, это не защищает вас от ошибок вы программист делает, например, печать строки с ненулевым завершением, как вы описали.
вы можете избежать сбоя от проблемы, которую вы описали, ограничив количество символов, напечатанных printf:
char my_string[10]; //other code here printf("%.9s",my_string); //limit the number of chars to be printed to 9
использовать
strlcpy(), указанный здесь: http://www.courtesan.com/todd/papers/strlcpy.htmlЕсли ваш libc не имеет реализации, то попробуйте это:
size_t strlcpy(char* dst, const char* src, size_t bufsize) { size_t srclen =strlen(src); size_t result =srclen; /* Result is always the length of the src string */ if(bufsize>0) { if(srclen>=bufsize) srclen=bufsize-1; if(srclen>0) memcpy(dst,src,srclen); dst[srclen]=''; } return result; }(написано мной в 2004 году-посвящено общественному достоянию.)
strncpy работает непосредственно со строковыми буферами, если вы работаете непосредственно с вашей памятью, теперь вы должны буферизировать размеры, и вы можете установить '\0' вручную.
Я считаю, что нет лучшей альтернативы в простом C, но это не так уж плохо, если вы так осторожны, как должны быть при игре с необработанной памятью.
вместо
strncpy(), вы могли бы использоватьsnprintf(buffer, BUFFER_SIZE, "%s", src);вот один лайнер, который копирует не более
size-1ненулевых символовsrcдоdestи добавляет значение null:static inline void cpystr(char *dest, const char *src, size_t size) { if(size) while((*dest++ = --size ? *src++ : 0)); }
Я всегда предпочитал:
memset(dest, 0, LEN); strncpy(dest, src, LEN - 1);чтобы исправить это после подхода, но это действительно просто вопрос предпочтения.
эти функции эволюционировали больше, чем были разработаны, поэтому на самом деле нет "почему". Вы просто должны научиться "как". К сожалению, справочные страницы linux по крайней мере лишенный общих примеров использования для этих функций, и я заметил много о злоупотреблениях в коде я уже говорил. Я сделал несколько заметок здесь: http://www.pixelbeat.org/programming/gcc/string_buffers.html
не полагаясь на новые расширения, я сделал что-то подобное в прошлом:
/* copy N "visible" chars, adding a null in the position just beyond them */ #define MSTRNCPY( dst, src, len) ( strncpy( (dst), (src), (len)), (dst)[ (len) ] = '')и, возможно, даже:
/* pull up to size - 1 "visible" characters into a fixed size buffer of known size */ #define MFBCPY( dst, src) MSTRNCPY( (dst), (src), sizeof( dst) - 1)почему макросы вместо более новых "встроенных" (?) функции? Потому что раньше было довольно много разных Юнисов, а также других не-unix (не-windows) сред, которые мне приходилось переносить, когда я делал C ежедневно.
Comments