Почему strncpy не завершается null?

strncpy() якобы защищает от переполнения буфера. Но если он предотвращает переполнение без завершения null, то в любом случае последующая строковая операция будет переполняться. Поэтому, чтобы защитить себя от этого, я делаю:

strncpy( dest, src, LEN );
dest[LEN - 1] = '';

man strncpy выдает:

функция strncpy () аналогична, за исключением того, что копируется не более n байтов src. Таким образом, если среди первых n байтов src нет нулевого байта, результат не будет null-завершенной.

без null, заканчивающего что-то, казалось бы, невинное, как:

   printf( "FOO: %sn", dest );

...может разбиться.


есть ли лучшие, более безопасные альтернативы strncpy()?

11 ответов


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

все эти "безопасные" функции обработки строк, такие как snprintf и vsnprintf исправления, которые были добавлены в более поздние стандарты для смягчения атакам переполнения буфера и т. д.

Википедия упоминает strncat в качестве альтернативы написанию собственного сейфа strncpy:

*dst = ''; strncat(dst, src, LEN);

редактировать

я пропустил, что 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 символов не было завершено.
  • если имя было короче 14 байт, оно было null заполнено до полной длины (14 байт).

это именно то, что было бы достигнуто:

strncpy(inode->d_name, filename, 14);

и strncpy() было идеально приспособлено к своему первоначально применению ниши. Это было только случайно о предотвращении переполнения в нули.

(обратите внимание, что null заполнение до длины 14 не является серьезным накладным расходом - если длина буфера составляет 4 КБ, и все, что вы хотите, это безопасно скопировать в него 20 символов, то дополнительные 4075 нулей являются серьезным перебором и могут легко привести к квадратичному поведению, если вы неоднократно добавляете материал в длинный буфер.)


некоторые новые альтернативы указаны в ISO / IEC TR 24731 (Check 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);

исправить это после подхода, но это действительно просто вопрос предпочтения.


эти функции эволюционировали больше, чем были разработаны, поэтому действительно нет "почему". Вы просто должны научиться "как". К сожалению, man-страницы linux по крайней мере лишенный общих примеров использования для этих функций, и я заметил lots о злоупотреблениях в коде, который я просмотрел. Я тут кое-что записал.: 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.