为什么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处理函数,如snprintf
和vsnprintf
都是在后来的标准中添加的修复,以缓解缓冲区溢出漏洞等。
维基百科提到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将被截断。
最初,第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);
这是一个从src
到dest
复制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的时候必须把它们放回去。