86

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

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

man strncpy给出:

功能类似,只是复制的字节数strncpy()不超过。因此,如果 的第一个字节中没有空字节,则结果将不会以空值结尾。nsrcnsrc

没有 null 终止看似无辜的事情,例如:

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

...可能会崩溃。


是否有更好、更安全的替代方案strncpy()

4

11 回答 11

50

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

所有那些“安全”的字符串处理函数,例如snprintf()vsnprintf()是在后来的标准中添加的修复程序,以减轻缓冲区溢出漏洞等。

维基百科提到strncat()作为编写自己的保险箱的替代方法strncpy()

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

编辑

strncat()如果字符串长于或等于 LEN 字符,我错过了当 null 终止字符串时超出 LEN 字符的字符。

无论如何,使用strncat()而不是使用任何本土解决方案(例如memcpy(..., strlen(...))/whatever)的意义在于,它的实现strncat()可能是库中的目标/平台优化。

当然,您需要检查 dst 是否至少包含 nullchar,因此正确使用strncat()如下:

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

我也承认这strncpy()对于将子字符串复制到另一个字符串不是很有用,如果 src 比 n char 短,则目标字符串将被截断。

于 2009-09-21T11:12:29.863 回答
27

最初,第7 版 UNIX文件系统(参见 DIR(5))具有将文件名限制为 14 个字节的目录条目;目录中的每个条目由 2 个字节的 inode 编号加上 14 个字节的名称组成,空填充到 14 个字符,但不一定以空结尾。我相信它strncpy()被设计为与这些目录结构一起工作——或者,至少,它完美地适用于那种结构。

考虑:

  • 14 个字符的文件名不是以空值结尾的。
  • 如果名称短于 14 字节,则将其填充为全长(14 字节)。

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

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

因此,strncpy()非常适合其原始的利基应用。这只是巧合地防止以空结尾的字符串溢出。

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

于 2009-09-21T11:33:54.713 回答
26

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

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

在参考文献中有指向来源的链接。

于 2009-09-21T11:32:00.543 回答
9

Strncpy 对程序用户的堆栈溢出攻击更安全,它不能保护您免受程序员所做的错误影响,例如按照您描述的方式打印非空终止字符串。

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

char my_string[10];
//other code here
printf("%.9s",my_string); //limit the number of chars to be printed to 9
于 2009-09-21T22:33:42.567 回答
8

ISO/IEC TR 24731 中指定了一些新的替代方案(有关信息,请查看https://buildsecurityin.us-cert.gov/daisy/bsi/articles/knowledge/coding/317-BSI.html)。这些函数中的大多数都带有一个附加参数,该参数指定目标变量的最大长度,确保所有字符串都以 null 结尾,并具有以_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;
}

您可以更改函数以满足您的需要,例如,始终复制尽可能多的字符串而不会溢出。事实上,如果您_TRUNCATEcount.




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

于 2009-09-21T10:57:03.687 回答
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 年撰写 - 致力于公共领域。)

于 2009-09-21T12:18:59.560 回答
4

我一直喜欢:

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

事后修复它的方法,但这实际上只是一个偏好问题。

于 2009-09-21T10:59:04.773 回答
4

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

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

这是一个单行,它最多将size-1非空字符从srcto复制dest并添加一个空终止符:

static inline void cpystr(char *dest, const char *src, size_t size)
{ if(size) while((*dest++ = --size ? *src++ : 0)); }
于 2009-09-21T14:41:41.387 回答
3

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

我相信在纯 C 语言中没有更好的选择,但如果你在使用原始内存时尽可能小心,它并不是那么糟糕。

于 2009-09-21T10:45:31.800 回答
2

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

于 2009-09-21T13:33:52.823 回答
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)

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

于 2009-09-21T23:04:27.320 回答