C - strncpy 并不总是 null 终止

  • 本文关键字:null 终止 strncpy strncpy
  • 更新时间 :
  • 英文 :


我使用下面的代码:

char filename[ 255 ];
strncpy( filename, getenv( "HOME" ), 235 );
strncat( filename, "/.config/stationlist.xml", 255 );

获取此消息:

(warning) Dangerous usage of strncat - 3rd parameter is the maximum number of characters to append.
(error) Dangerous usage of 'filename' (strncpy doesn't always null-terminate it).

我通常避免使用str*cpy()str*cat()。您必须处理边界条件、晦涩的API定义和意想不到的性能后果。

您可以使用snprintf()代替。您只需要考虑目标缓冲区的大小。而且,它更安全,因为它不会溢出,并且总是为您终止NUL。

char filename[255];
const char *home = getenv("HOME");
if (home == 0) home = ".";
int r = snprintf(filename, sizeof(filename), "%s%s", home, "/.config/stationlist.xml");
if (r >= sizeof(filename)) {
    /* need a bigger filename buffer... */
} else if (r < 0) {
    /* handle error... */
}

strncat呼叫可能溢出filename

使用:

strncat(filename, "/.config/stationlist.xml",
        sizeof filename - strlen(filename) - 1);

还要确保在strncpy调用后null终止缓冲区:

strncpy( filename, getenv( "HOME" ), 235 );
filename[235] = '';

as strncpy如果源的长度大于或等于要复制的最大字符数,则不会null终止其目标缓冲区。

man strncpy这个说:

Warning: If there is no null byte among the first n bytes
of src, the string placed in dest will not be null terminated.

如果在耗尽最大长度之前遇到源中的0字节,则将复制它。但是,如果在源的第一个0之前达到最大长度,则不会终止目标。最好确保在strncpy()回归后是你自己…

strncpy()和(甚至更多)strncat()都有不明显的行为,你最好不要使用它们。

strncpy ()

如果您的目标字符串为255字节长,则strncpy()将始终写入所有255字节。如果源字符串短于255字节,则将剩余部分置零。如果源字符串大于255字节,它将在255字节后停止复制,使目标字符串没有空终止符。

strncat ()

大多数'size '函数(strncpy(), memcpy(), memmove()等)的size参数是目标字符串(内存)中的字节数。对于strncat(),大小是目标对象中已经存在的字符串结束后剩余的空间。因此,只有在知道目标缓冲区有多大(S)和目标字符串当前有多长(L)时,才能安全地使用strncat()strncat()的安全参数是S-L(我们将在其他时间担心是否存在off-by- 1)。但是考虑到你知道L,让strncat()跳过L字符是没有意义的;您可以将target+L作为起始位置,然后简单地复制数据。你可以用memmove()memcpy(),也可以用strcpy(),甚至strncpy()。如果你不知道源字符串的长度,你必须确信截断它是有意义的。

问题代码分析

char filename[255];
strncpy(filename, getenv("HOME"), 235);
strncat(filename, "/.config/stationlist.xml", 255);

第一行是无例外的,除非大小被认为太小(或者您在没有设置$HOME的上下文中运行程序),但这超出了这个问题的范围。对strncpy()的调用不使用sizeof(filename)作为大小,而是使用一个任意小的数字。这并不是世界末日,但通常不能保证变量的最后20个字节是零字节(甚至不能保证它们中的任何一个都是零字节)。在某些情况下(filename是一个全局变量,以前未使用),可能会保证0。

strncat()调用尝试在filename中的字符串末尾追加24个字符,该字符串可能已经是232-234字节长,或者可能任意长于235字节。无论哪种方式,都可以保证缓冲区溢出。strncat()的使用也直接落入关于其大小的陷阱。您已经说过,可以在filename中已经包含的内容的末尾添加最多255个字符,这是明显错误的(除非getenv("HOME")中的字符串恰好为空)。

安全代码:

char filename[255];
static const char config_file[] = "/.config/stationlist.xml";
const char *home = getenv("HOME");
size_t len = strlen(home);
if (len > sizeof(filename) - sizeof(config_file))
    ...error file name will be too long...
else
{
    memmove(filename, home, len);
    memmove(filename+len, config_file, sizeof(config_file));
}

会有人坚持认为"memcpy()是安全的,因为字符串不能重叠",在一个层面上,他们是正确的,但重叠应该是一个没有问题的,与memmove(),这是一个没有问题。所以,我一直使用memmove()…但我还没有做过时间测量来看看这个问题有多大,如果它真的是个问题的话。也许其他人已经测量过了。

总结
  1. 不要使用strncat() .
  2. 谨慎使用strncpy()(注意它在非常大的缓冲区中的行为!)。
  3. 计划使用memmove()memcpy()代替;如果你可以安全地复制,你知道需要的大小,使这是合理的

1)您的strncpy不一定以空终止文件名。事实上,如果getenv("HOME")大于235个字符,并且getenv("HOME")[234]不是0,则不会。2) strncat()可能会尝试将文件名扩展到255个字符以上,因为,正如它所说的,

3rd parameter is the maximum number of characters to append. 

(不是dst的总长度)

strncpy(Copied_to,Copied_from,sizeof_input)在字符数组后输出垃圾值(不用于字符串类型)。使用遍历字符数组的for循环来解决输出问题,而不是简单地使用cout<<var;

for(i=0;i<size;i++){cout<<var[i]} 

我找不到在使用minGW编译器的windows系统上遍历的工作。Null终止并不能解决问题。

相关内容

  • 没有找到相关文章

最新更新