C:使用 sprintf 溢出的危险以及如何避免它们

问题描述 投票:0回答:1

我一直在使用 IDE 调用编译器,而无需进行太多配置,但从选项中我可以看到,我的项目似乎设置为使用 gnu99 作为 C 语言标准,使用 gnu++11 作为 C++ 语言标准,同时使用 gcc 版本 4.2.1。

该项目正在物联网的嵌入式设备上运行,我需要模拟它的某些部分,而我懒惰地使用了 onlinegdb。在那里,我注意到我可能正在不安全地构建字符串,我担心该项目可能会受到损害并需要紧急更新。我很确定我设计的字符串大小永远不会被我传递给它们的数据溢出,但前提是我正确实现了它,而我恐怕没有这样做。

这是一个可运行的示例:

#include <stdio.h>
#include <string.h>

int main()
{
    // example 1 works as expected initially - I think it is unsafe
    char testString1[6];
    sprintf(testString1, "123");
    sprintf(testString1, "%s456", testString1);
    printf("%s\n", testString1); // 123456
    
    // example 2 doesn't work as I expected - I think it is intrinsically safe though
    char testString2[6];
    snprintf(testString2, 6, "123");
    snprintf(testString2, 6, "%s456", testString2);
    printf("%s\n", testString2); // 456
    
    // example 3 what I found works and I think is safe
    char testString3[6];
    snprintf(testString3, 6, "123");
    snprintf(testString3 + strlen(testString3), 6, "456");
    printf("%s\n", testString3); // 123456

    return 0;
}

这是输出:

main.c: In function ‘main’:
main.c:9:29: warning: ‘456’ directive writing 3 bytes into a region of size between 1 and 6 [-Wformat-overflow=]
    9 |     sprintf(testString1, "%s456", testString1);
      |                             ^~~
main.c:9:5: note: ‘sprintf’ output between 4 and 9 bytes into a destination of size 6
    9 |     sprintf(testString1, "%s456", testString1);
      |     ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
main.c:15:33: warning: ‘456’ directive output may be truncated writing 3 bytes into a region of size between 1 and 6 [-Wformat-truncation=]
   15 |     snprintf(testString2, 6, "%s456", testString2);
      |                                 ^~~
main.c:15:5: note: ‘snprintf’ output between 4 and 9 bytes into a destination of size 6
   15 |     snprintf(testString2, 6, "%s456", testString2);
      |     ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
123456
456
123456

示例 1 是我到目前为止构建琴弦的方式。不幸的是,我的 IDE/编译器没有警告我这一点,但 onlinegdb 却警告了我,可能是因为它使用了不同的编译器或语言标准。

示例 2 是我在注意到 onlinegdb 上的警告后第一次尝试使其更安全。我用 snprintf 替换了 sprintf,给了它一个大小限制,并继续用与示例 1 相同的模式填充它。这不起作用,只用新数据填充它。事实上,如果我删除示例 2 中额外的“456”字符,testString2 最终会被打印为空白。

示例 3 是我发现它效果很好并且我认为它很安全的方法。

我想问:

  • 示例 1 安全吗?我认为这不是由于示例 1 和 2 的警告所致。
  • 为什么示例 2 没有按我的预期工作?
  • 例3真的安全吗?我可以安全地开始构建这样的字符串吗?
c buffer-overflow
1个回答
0
投票

出于同样的原因,您的前两个示例是错误的:输出字符串与输入之一重叠。这样做会触发未定义的行为

关于 sprintf 函数,

C 标准
的第 7.21.6.6p2 节明确提到了这一点:

sprintf
函数等同于
fprintf
,不同之处在于输出 写入数组(由参数指定)而不是 一条溪流。字符末尾写入空字符 书面;它不计入返回值的一部分。 如果复印 发生在重叠的对象之间,行为未定义。

有关

snprintf
功能的第 7.21.6.5p2 节中也有相同的语言。

您的第三个示例不会遇到此问题,但是目的地不够大,无法存储所需的字符串。它需要空间容纳您要输入的 6 个字符,以及终止空字节。

您实际上不需要

sprintf
snprintf
来做您想做的事。您可以简单地使用
strcpy
strcat
:

char testString[7];
strcpy(testString, "123");
strcat(testString, "456");

您还可以使用

strncpy
strncat
,但是
strncpy
不会在所有情况下自动以 null 终止目标字符串。

© www.soinside.com 2019 - 2024. All rights reserved.