为什么strncpy不能终止?

strncpy()可以防止缓冲区溢出。 但是,如果它阻止了没有null终止的溢出,那么所有的可能性都会导致后续的string操作溢出。 所以为了防止这个,我发现自己在做:

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

man strncpy给出:

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

没有null终止一些看起来像无辜的东西:

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

…可能会崩溃。


有没有比strncpy()更安全的替代方法?

strncpy不是用来作为一个更安全的strcpy ,它应该被用来插入一个string在另一个中间。

所有这些“安全的”string处理函数,如snprintfvsnprintf都是在后来的标准中添加的修复,以缓解缓冲区溢出漏洞等。

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

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

编辑

我错过了strncat超过LEN字符的时候,如果null结束string,如果它长于或等于LEN char的。

无论如何,使用strncat而不是任何本地解决scheme,如memcpy(…,strlen(…))/什么是执行strncat可能是目标/平台库优化。

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

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

我也承认,strncpy对于将子string复制到另一个string中并不是非常有用,如果src短于n个字符,目标string将被截断。

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

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

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

最初,第7版UNIX文件系统(参见DIR(5))具有将文件名限制为14个字节的目录条目; 目录中的每个条目由2个字节组成,inode数字加14个字节作为名称,null填充为14个字符,但不一定以null结尾。 我相信strncpy()被devise用来处理这些目录结构 – 至less,它对于这个结构来说是完美的。

考虑:

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

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

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

所以, strncpy()理想地适合于它原来的小众应用程序。 只是巧合的是防止以空字符结尾的string溢出。

(请注意,填充长度为14的null不是一个严重的开销 – 如果缓冲区的长度是4 KB,并且所有你想要的是安全地复制20个字符,那么额外的4075个空值是严重的过度消耗,并且可以很容易导致二次行为,如果你反复添加材料到长缓冲区。)

ISO / IEC TR 24731中规定了一些新的备选scheme(请查阅https://buildsecurityin.us-cert.gov/daisy/bsi/articles/knowledge/coding/317-BSI.html了解详情)。; 这些函数中的大多数都带有一个额外的参数,它指定了目标variables的最大长度,确保所有的string都是以null结尾的,并且以_s结尾的名字(用于“安全”?)来区分它们早期的“unsafe”版本。 1

不幸的是,他们仍然获得支持,可能无法使用您的特定工具集。 如果使用旧的不安全函数,Visual Studio的更高版本将引发警告。

如果你的工具支持新的function,那么为旧的function创build自己的包装应该相当容易。 这是一个例子:

 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; } 

您可以更改函数以适合您的需要,例如,始终尽可能多地复制string而不会溢出。 实际上,如果您传递_TRUNCATE作为count ,VC ++实现可以做到这一点。


1当然,对于目标缓冲区的大小仍然需要精确:如果你提供一个3字符的缓冲区,但是告诉strcpy_s()它有25个字符的空间,那么你仍然有麻烦。

Strncpy对程序用户的堆栈溢出攻击更为安全,它不能保护程序员所做的错误,比如按照你描述的方式打印一个非null结尾的string。

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

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

使用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年写的 – 致力于公共领域。)

strncpy直接与可用的string缓冲区一起工作,如果直接使用内存,则必须现在缓冲区大小,并且可以手动设置'\ 0'。

我相信在纯C中没有更好的select,但是如果你在处理原始记忆的时候一样小心,那也不是那么糟糕。

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

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

这是一个从srcdest复制size-1非空字符并添加空终止符的单行程序:

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

我一直首选:

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

到后来解决办法,但这只是一个偏好问题。

这些function不仅仅是被devise出来的,所以没有“为什么”。 你只需要学习“如何”。 不幸的是,Linux的手册页至less没有这些函数的常见用例,而且我注意到在我所查阅的代码中存在大量的误用。 我在这里做了一些说明: http : //www.pixelbeat.org/programming/gcc/string_buffers.html

在不依赖更新的扩展的情况下,我曾经做过这样的事情:

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

为什么macros而不是更新的“内置”(?)函数? 因为过去有很多不同的unice,以及其他非unix(非windows)的环境,我每天都在做C的时候必须把它们放回去。