Почему strncpy не обнуляется? - PullRequest
71 голосов
/ 21 сентября 2009

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

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

man strncpy дает:

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

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

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

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


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

Ответы [ 11 ]

41 голосов
/ 21 сентября 2009

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

Все эти «безопасные» функции обработки строк, такие как snprintf и vsnprintf, являются исправлениями, которые были добавлены в более поздние стандарты для уменьшения эксплойтов переполнения буфера и т. Д.

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

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

EDIT

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

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

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

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

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

24 голосов
/ 21 сентября 2009

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

Рассмотрим:

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

Это именно то, чего можно достичь:

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

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

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

24 голосов
/ 21 сентября 2009

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

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

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

8 голосов
/ 22 сентября 2009

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

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

char my_string[10];
//other code here
printf("%.9s",my_string); //limit the number of chars to be printed to 9
8 голосов
/ 21 сентября 2009

Некоторые новые альтернативы указаны в ИСО / МЭК TR 24731 (см. Информацию https://buildsecurityin.us -cert.gov / daisy / bsi / Articles / Knowledge / Coding / 317-BSI.html ). Большинство из этих функций принимают дополнительный параметр, который задает максимальную длину целевой переменной, гарантирует, что все строки заканчиваются нулем, и имеют имена, оканчивающиеся на _s (для «safe»?), Чтобы отличать их от более ранних » небезопасные "версии. 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;
}

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




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

Использование 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 году - посвящено общественному достоянию.)

3 голосов
/ 21 сентября 2009

Вместо 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)); }
3 голосов
/ 21 сентября 2009

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

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

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

3 голосов
/ 21 сентября 2009

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

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

2 голосов
/ 22 сентября 2009

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

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

...