Почему strncpy не завершает нуль?

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

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

man strncpy дает:

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

Без нулевого завершения что-то вроде невинного, как:

   printf( "FOO: %s\n", dest );

... может произойти сбой.


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

Ответ 1

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

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

Wikipedia упоминает strncat как альтернативу написанию собственного безопасного strncpy:

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

ИЗМЕНИТЬ

Я пропустил, что strncat превышает LEN-символы, когда null завершает строку, если она длиннее или равна LEN char.

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

Конечно, вам нужно проверить, что dst содержит хотя бы nullchar, поэтому правильное использование strncat было бы чем-то вроде:

if(LEN) { *dst = '\0'; strncat(dst, src, LEN-1); }

Я также допускаю, что strncpy не очень полезен для копирования подстроки в другую строку, если src короче, чем n char, строка назначения будет усечена.

Ответ 2

Есть уже версии с открытым исходным кодом, такие как strlcpy, которые делают безопасное копирование.

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

В ссылках есть ссылки на источники.

Ответ 3

Первоначально файловая система 7th UNIX (см. DIR (5)) имела записи каталога, ограничивающие имена файлов до 14 байтов; каждая запись в каталоге состояла из 2 байтов для номера индексного дескриптора плюс 14 байтов для имени, ноль заполнялся до 14 символов, но не обязательно заканчивался нулем. Я полагаю, что strncpy() был разработан для работы с этими структурами каталогов - или, по крайней мере, он отлично работает для этой структуры.

Рассмотрим:

  • 14-символьное имя файла не было завершено нулем.
  • Если имя было короче 14 байтов, оно было заполнено нулями до полной длины (14 байтов).

Это именно то, что можно было бы достичь:

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

Итак, strncpy() идеально подобрался к своему оригинальному нишу. Это было только совпадение о предотвращении переполнения строк с нулевым завершением.

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

Ответ 4

Некоторые новые альтернативы указаны в 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) = '\0';

   return OK;
}

Вы можете изменить функцию в соответствии с вашими потребностями, например, чтобы всегда копировать как можно больше строки без переполнения. Фактически реализация VС++ может сделать это, если вы передадите _TRUNCATE как count.




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

Ответ 5

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

Вы можете избежать сбоев из описанной проблемы, ограничив количество символов, напечатанных printf:

char my_string[10];
//other code here
printf("%.9s",my_string); //limit the number of chars to be printed to 9

Ответ 6

Используйте 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]='\0';
  }
  return result;
}

(Написано мной в 2004 году - посвящено общественному достоянию.)

Ответ 7

strncpy работает напрямую с доступными буферами строк, если вы работаете непосредственно с вашей памятью, вы ДОЛЖНЫ теперь размер буфера, и вы можете вручную установить "\ 0".

Я считаю, что в простой C нет лучшей альтернативы, но это не так уж плохо, если вы так же осторожны, как и при игре с необработанной памятью.

Ответ 8

Я всегда предпочитал:

 memset(dest, 0, LEN);
 strncpy(dest, src, LEN - 1);

чтобы исправить это позже, но это действительно вопрос предпочтения.

Ответ 9

Вместо strncpy() вы можете использовать

snprintf(buffer, BUFFER_SIZE, "%s", src);

Здесь однострочный, который копирует не более size-1 ненулевые символы от src до dest и добавляет нулевой ограничитель:

static inline void cpystr(char *dest, const char *src, size_t size)
{ if(size) while((*dest++ = --size ? *src++ : 0)); }

Ответ 10

Эти функции эволюционировали больше, чем разрабатывались, поэтому на самом деле нет "почему". Вам просто нужно узнать "как". К сожалению, страницы руководства Linux, по крайней мере, лишены примеров совместного использования для этих функций, и я заметил много неправильного использования в коде, который я рассмотрел. Я сделал несколько заметок здесь: http://www.pixelbeat.org/programming/gcc/string_buffers.html

Ответ 11

Не полагаясь на более новые расширения, в прошлом я сделал что-то вроде этого:

/* copy N "visible" chars, adding a null in the position just beyond them */
#define MSTRNCPY( dst, src, len) ( strncpy( (dst), (src), (len)), (dst)[ (len) ] = '\0')

и, возможно, даже:

/* 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 на ежедневной основе.