为什么strncpy不为null终止?

Tim*_*ley 71 c strncpy

strncpy()据说可以防止缓冲区溢出.但是如果它在没有null终止的情况下防止溢出,那么随后的字符串操作将会溢出.所以为了防止这种情况,我发现自己在做:

strncpy( dest, src, LEN );
dest[LEN - 1] = '\0';
Run Code Online (Sandbox Code Playgroud)

man strncpy 得到:

strncpy()函数类似,只是复制了不超过n个字节的src.因此,如果src的前n个字节中没有空字节,则结果将不会以空值终止.

没有null终止看似无辜的东西,如:

   printf( "FOO: %s\n", dest );
Run Code Online (Sandbox Code Playgroud)

......可能会崩溃


是否有更好,更安全的替代品strncpy()

Ern*_*lli 41

strncpy不打算用作更安全的strcpy,它应该用于在另一个中间插入一个字符串.

所有那些"安全"的字符串处理函数,例如snprintf并且vsnprintf是在以后的标准中添加的修复,以减轻缓冲区溢出漏洞等.

维基百科提到strncat作为编写自己的安全的替代方法strncpy:

*dst = '\0'; strncat(dst, src, LEN);
Run Code Online (Sandbox Code Playgroud)

编辑

我错过了strncat超过LEN字符,当null终止字符串时,如果它长于或等于LEN char的话.

无论如何,使用strncat代替任何自行开发的解决方案,例如memcpy(...,strlen(...))/无论strncat的实现可能是库中的目标/平台优化.

当然你需要检查dst是否至少保存了nullchar,所以正确使用strncat会是这样的:

if(LEN) { *dst = '\0'; strncat(dst, src, LEN-1); }
Run Code Online (Sandbox Code Playgroud)

我还承认strncpy对于将子字符串复制到另一个字符串并不是很有用,如果src比n字符短,则目标字符串将被截断.

  • "它应该用于在另一个字符串的中间插入一个字符串" - 不,它打算将字符串写入固定宽度字段,例如在目录条目中.这就是为什么如果(并且仅当)源字符串太短,它用NUL填充输出缓冲区. (25认同)
  • 设置*dst ='\ 0'如何使这更安全?它仍然存在允许您在目标缓冲区末尾写入的原始问题. (3认同)
  • 听起来不错但是不应该是strncat(dst,src,LEN-1),因为它要写一个额外的字符? (3认同)
  • @Jonathan:实际上,safe是一种数据类型,它将指向char缓冲区的指针与该缓冲区的长度相结合.但我们都知道这不会发生.就个人而言,我厌倦了所有这些努力来制造本质上不安全的东西(程序员试图准确地尊重缓冲区的长度),有些分数更安全.这并不是说我们目前有50%的缓冲区溢出太多,所以如果我们能让50%更安全的字符串处理我们就可以了:-( (3认同)
  • +1 因为没有重复 strncpy 在某种程度上是 strcpy 的安全版本的垃圾 - 前者有其自己的一系列问题。 (2认同)

Sta*_*eXV 24

已经有像strlcpy这样的开源实现可以进行安全复制.

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

在参考文献中有链接到源.

  • +1 strlcpy是真正安全的strcpy,而不是strncpy (5认同)

Jon*_*ler 24

最初,第7版UNIX文件系统(参见DIR(5))具有将文件名限制为14个字节的目录条目; 目录中的每个条目由2个字节组成,用于inode编号加上14个字节用于名称,null填充为14个字符,但不一定是以null结尾.我的信念strncpy()是设计用于那些目录结构 - 或者至少,它适用于该结构.

考虑:

  • 14个字符的文件名未终止.
  • 如果名称短于14个字节,则将其填充为全长(14个字节).

这正是通过以下方式实现的目标:

strncpy(inode->d_name, filename, 14);
Run Code Online (Sandbox Code Playgroud)

因此,strncpy()理想地适合其原始的利基应用.巧合的是,防止以null结尾的字符串溢出.

(注意,长度为14的空填充不是一个严重的开销 - 如果缓冲区的长度是4 KB并且你想要的只是安全地复制20个字符,那么额外的4075个空值是严重的过度杀伤,并且可以很容易如果您反复向长缓冲区添加材料,则会导致二次行为.)

  • 这种特殊情况可能是模糊的,但具有固定长度字符串字段的数据结构并不少见,这些字段填充空值但不以空值终止。事实上,如果要存储固定格式的数据,这通常是最有效的方法。 (3认同)

Ada*_*iss 8

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;
}
Run Code Online (Sandbox Code Playgroud)

您可以更改功能以满足您的需要,例如,始终尽可能多地复制字符串而不会溢出.事实上,如果你通过VC++实现可以做到这一点_TRUNCATEcount.




1当然,您仍然需要准确了解目标缓冲区的大小:如果您提供3个字符的缓冲区,但告诉strcpy_s()它有25个字符的空间,那么您仍然遇到麻烦.

  • 但ISO C委员会可以 - 并且做到了.另见:http://stackoverflow.com/questions/372980/do-you-use-the-tr-24731-safe-functions-in-your-c-code (2认同)

Lir*_*evi 8

strncpy()函数是通过对栈溢出攻击的安全用户程序的,它并不能保护你避免发生差错,你的程序员做的,比如打印一个非空结尾的字符串,你所描述的方式.

您可以通过限制printf打印的字符数来避免因您所描述的问题而崩溃:

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


ale*_*gle 5

使用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;
}
Run Code Online (Sandbox Code Playgroud)

(2004年由我撰写 - 致力于公共领域.)


Chr*_*oph 5

strncpy()您可以使用

snprintf(buffer, BUFFER_SIZE, "%s", src);
Run Code Online (Sandbox Code Playgroud)

这是一个单行代码,它从to复制最多size-1非空字符并添加一个空终止符:srcdest

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