【问题标题】:Can `snprintf()` read out of bounds when a string argument is not null-terminated?当字符串参数不是以空值结尾时,`snprintf()` 是否可以越界读取?
【发布时间】:2020-11-18 22:20:06
【问题描述】:

我有以下一段代码,一位同事声称其中可能包含越界读取,我不同意。你能帮忙解决这个争论并解释原因吗?

char *test_filename = malloc(Size + 1);
sprintf(test_filename, "");

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

其中Dataconst uint8_t *Data 类型的非空终止字符串,SizeData 的大小,即Datasize_t 类型的字节数。

可能因为格式字符串为%s而读取越界?

【问题讨论】:

  • 如果未终止的源字符串的长度为 4,而您传递了 5,snprintf 怎么知道在 4 处停止?
  • if (Size > 0 && !Data) -->> snprintf 仅在 Size 大于零且 Data IS NULL 时执行。
  • 一位同事声称可能包含越界读取而且您无能为力,因为您正在进行字符串处理假设字符串正确终止。问题在于在将非 NUL 终止字符串传递给您的函数的任何代码中
  • @Peter-ReinstateMonica 你误解了我的评论。 当然 如果“字符串参数不是NUL-终止”,snprintf() 将读取多余的字节。但是 问题 是由传入一个不正确终止的字符串引起的。请注意,未正确终止的参数是不是字符串。一个字符串——根据定义——必须有一个正确的'\0' 终止符。如果调用代码不能确保这一点,则问题出在 调用 代码中。 “这是我处理字符串的函数。” “好的,这是不是字符串的东西!
  • 不仅如此,如果示例中的Data 是标准的[unsigned] char * 用于引用字符串,那么如果没有正确终止,函数就无法判断它应该在哪里结束.再次 - 问题出在调用代码中。

标签: c string printf


【解决方案1】:

你的同事是对的。也许不直观,snprintf(test_filename, Size + 1, "%s", Data) 保证读取从Data 开始的字节,直到遇到 0 字节,在您的情况下通常会导致越界读取。

它只会写入这些字节中的Sizetest_filename 并且null 终止它们,尊重目的地的大小限制;但它会继续阅读。这样做的原因是一种设计选择,它使调用者能够在实际写入任何内容之前确定动态分配所需的 destination 大小:snprintf() 返回字节数 ,如果目的地有无限的空间。 此功能应该与目的地大小为 0 (并且可能将空指针作为目的地)一起使用。此功能对于不是字符串的参数很有用:对于数字等,输出的大小很难预测(例如取决于语言环境),最好在运行时留给函数​​。

同时返回值表示输出是否被截断:如果大于等于size参数,则不是所有的输入都用在了输出中。在您的情况下,遗漏的是从 Data[Size] 开始并以第一个 0 字节结尾的字节,或者是分段错误;-)。

修复建议:首先不清楚为什么要使用printf 系列来打印字符串;简单地复制它。然后 Andrew 在他的 cmets 中有一个观点,即由于 Data 不是以 null 结尾的,因此它并不是真正的字符串(即使所有字节都是可打印的);所以不要开始摆弄strcpy 和朋友,而只是摆弄memcpy() 字节,然后手动终止null。

哦,前面的sprintf(test_filename, ""); 没有任何明显的用途。如果您想将空字节写入*Data,只需这样做;但由于您没有使用strcat,它会依赖终止的目标字符串来扩展,因此完全没有必要。

【讨论】:

    【解决方案2】:

    来自snprintf() 的 MAN 页面

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

    注意at most size bytes

    这意味着snprintf()将在参数Size字节传输后停止传输字节。

    这句话;

    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() 是为这种工作而设计的。建议用

    替换对snprintf()的调用
    memcpy( test_filename, Data, Size );
    

    【讨论】:

    • 使用memcpy()的问题是你需要知道复制的长度。如果在假设它引用字符串的情况下仅通过地址将值传递给函数,则函数无法在不搜索 NUL 字符的情况下知道长度。
    • 一些嵌入的 NUL 字符在源字符串中的位置无关紧要。 Size 字节将被复制,并且目标缓冲区中有空间用于所有 Size 字节。
    • 如果源地址只能作为地址使用,因为它应该是一个字符串,没有办法使用memcpy()
    • @AndrewHenle,问题指出参数Size 是要复制的字节数。 Size 是 Data 的大小,即 Data 中的字节数,类型为 size_t。 嗯,size_t 中的字节数与 long unsigned int 相同,在我的 64位 ubuntu linux 是 8 个字节。所以你问一些8字节单位的倍数。请澄清
    猜你喜欢
    • 2011-06-06
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多