当一个字符串参数没有以空字符结尾时,`snprintf()`函数是否可能越界读取?

3
我有以下一段代码,我的同事声称其中可能包含越界读取,而我不同意。你能帮忙解决这个争论并解释原因吗?
char *test_filename = malloc(Size + 1);
sprintf(test_filename, "");

if (Size > 0 && Data)
  snprintf(test_filename, Size + 1, "%s", Data);

其中Data是一个非空结尾的字符串,类型为const uint8_t *Data,而SizeData中字节数量,即Data的大小,类型为size_t

它可能会越界读取,因为格式化字符串是%s,也许是这个原因吗?


4
如果未终止的源字符串长度为4,而您传递了5,那么 snprintf 如何知道在4处停止? - Weather Vane
3
如果(Size > 0 && !Data),则执行snprintf,其中Size大于零且Data为NULL。 - wildplasser
2
@Peter-ReinstateMonica 你误解了我的评论。当然,如果“字符串参数没有以NUL结尾”,snprintf()会读取多余的字节。但是问题是由于传递了一个未正确终止的字符串而引起的。请注意,未正确终止的参数不是字符串。按照定义,字符串必须具有适当的'\0'终止符。如果调用代码没有确保这一点,则问题在于调用代码。 “这是我处理字符串的函数。” “好的,这里有一些不是字符串! - Andrew Henle
1
我并没有误解。只是很难看出 snprintf() 实际上读取的字节数比 n 更多,因为它没有对它们进行复制。当然,你可以向不执行额外读取的函数提供非终止字符串,为什么不呢? - Peter - Reinstate Monica
2
不仅如此,如果在示例中Data是用于引用字符串的标准[unsigned] char *,那么如果它没有被正确终止,函数就无法确定它应该在哪里结束。再次强调 - 问题出现在调用代码中。 - Andrew Henle
显示剩余4条评论
2个回答

6
你的同事是正确的。也许不太直观,snprintf(test_filename, Size + 1, "%s", Data)保证从Data开始读取字节,直到遇到0字节为止,在你的情况下通常导致越界读取。
它只会将这些字节中的Size个写入test_filename并在其后加上空字符,以尊重目标的大小限制;但是它会继续进行读取。这是一种设计选择,使调用者可以在实际写入任何内容之前确定所需的目标大小:snprintf()返回如果目标具有无限空间,则将写入的字节数。该功能应该使用目标大小为0(可能是目标为空指针)的情况下。对于非字符串参数,此功能很有用:对于数字等,输出大小难以预测(例如与区域设置相关),最好在运行时由函数处理。
同时,返回值指示输出是否被截断:如果它大于或等于大小参数,则未将所有输入用于输出。在您的情况下,剩下的字节是从Data[Size]开始,直到第一个0字节结束,或发生了分段错误;-)。
修复建议:首先,不清楚为什么要使用printf系列来打印字符串;只需复制它。然后,Andrew 在他的评论中有一点是正确的,即Data没有以空字符结尾,因此它实际上不是一个字符串(即使所有字节都可打印);所以不要开始动用strcpy等函数,而是简单地使用memcpy()拷贝字节,并手动加上空字符。
哦,前面的sprintf(test_filename, "");没有任何明显的目的。如果你想向*Data写入空字节,只需这样做;但是,由于你没有使用依赖于终止目标字符串扩展的strcat,这是完全没有必要的。

-1

来自snprintf()的MAN页面

The  functions  snprintf()  and  vsnprintf()  write  at most size bytes (including the terminating null byte ('\0')) to str.

请注意最多大小字节 这意味着在传输了参数Size字节后,snprintf()将停止传输字节。
这是一个语句;
sprintf(test_filename, "");

完全不必要,对第二次调用 snprintf() 的操作没有影响。

如果你想要一个“正确”的字符串结果,建议使用以下方法:

char *test_filename = calloc( sizeof( char ), Size + 1);

if (Size > 0 && Data)
    snprintf(test_filename, Size, "%s", Data);

然而,函数:snprintf() 会一直读取,直到遇到 NUL 字节。这可能会导致问题,甚至包括段错误事件。

函数:memcpy() 就是为这种工作而设计的。建议用 memcpy() 替换对 snprintf() 的调用。

memcpy( test_filename, Data, Size );

在使用 memcpy() 的问题是你需要知道要复制的长度。如果假设将值按地址传递到函数中,认为它是一个字符串,则函数无法知道长度,除非去搜索 NUL 字符。 - Andrew Henle
无论源字符串中有嵌入的NUL字符在哪里,都没有关系。将会复制Size个字节,并且目标缓冲区有足够的空间来容纳所有Size个字节。 - user3629249
如果源地址只能作为地址而不是字符串来使用,那么就无法使用memcpy() - Andrew Henle
@AndrewHenle,问题陈述中指出参数“Size”是要复制的字节数。Size是Data的大小,即Data中字节的数量,类型为size_t。在我的64位ubuntu linux系统上,size_t中的字节数与long unsigned int相同,即8个字节。因此,您正在询问某些8字节单位的倍数。请澄清。 - user3629249

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接