为什么strncpy不为null终止?

时间:2009-09-21 10:38:41

标签: c strncpy

据推测,

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

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

man strncpy给出:

  

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

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

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

......可能会崩溃。


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

11 个答案:

答案 0 :(得分:41)

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

所有这些“安全”字符串处理函数(如snprintfvsnprintf)都是在以后的标准中添加的修复程序,用于缓解缓冲区溢出漏洞等。

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字符短,则目标字符串将被截断。

答案 1 :(得分:24)

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

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

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

答案 2 :(得分:24)

最初,7th Edition UNIX文件系统(参见DIR(5))具有将文件名限制为14个字节的目录条目;目录中的每个条目由2个字节组成,用于inode编号加上14个字节用于名称,null填充为14个字符,但不一定是以null结尾。我相信strncpy()旨在与这些目录结构一起工作 - 或者至少,它适用于该结构。

考虑:

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

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

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

所以,strncpy()理想地适合其原始的利基应用。巧合的是,它只是防止以空字符结尾的字符串溢出。

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

答案 3 :(得分: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;
}

您可以更改功能以满足您的需要,例如,始终尽可能多地复制字符串而不会溢出。事实上,如果您将_TRUNCATE作为count传递,VC ++实现可以执行此操作。



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

答案 4 :(得分:8)

Strncpy对您的程序的用户的堆栈溢出攻击更安全,它不能保护您免受程序员的错误,例如打印非以null结尾的字符串,就像你描述的那样。

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

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

答案 5 :(得分: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;
}

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

答案 6 :(得分:3)

strncpy直接使用可用的字符串缓冲区,如果你直接使用你的内存,你必须现在缓冲大小,你可以手动设置'\ 0'。

我相信在简单的C中没有更好的选择,但如果你在玩原始内存的过程中一样小心,那就不是那么糟糕了。

答案 7 :(得分:3)

我一直偏好:

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

以后修复它,但这只是一个偏好问题。

答案 8 :(得分:3)

而不是strncpy(),您可以使用

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

这是一个单行,最多可以复制从size-1src的{​​{1}}个非空字符,并添加一个空终结符:

dest

答案 9 :(得分:2)

这些功能的发展不仅仅是设计,所以确实没有“为什么”。 你只需要学习“如何”。不幸的是,linux手册页至少是 没有这些函数的常见用例示例,我注意到 lot 在我审查过的代码中滥用我在这里做了一些笔记: http://www.pixelbeat.org/programming/gcc/string_buffers.html

答案 10 :(得分:2)

不依赖于更新的扩展,我过去做过类似的事情:

/* 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)

为什么宏而不是更新的“内置”(?)函数?因为曾经有很多不同的unices,以及其他非unix(非windows)环境,当我每天做C时,我不得不移植回来。