我正在阅读有关C语言中字符串的漏洞的信息,然后遇到了这段代码。谁能给我一个解释为什么发生这种情况?提前致谢。
int main (int argc, char* argv[]) {
char a[16];
char b[16];
char c[32];
strncpy(a, "0123456789abcdef", sizeof(a));
strncpy(b, "0123456789abcdef", sizeof(b));
strncpy(c, a, sizeof(c));
printf("a = %s\n", a);
printf("b = %s\n", b);
printf("c = %s\n", c);
}
输出:
a = 0123456789abcdef0123456789abcdef
b = 0123456789abcdef
c = 0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef
答案 0 :(得分:1)
超出字符串末尾的读取是未定义的行为(UB)。使用UB不能保证代码以一种或另一种方式运行。在不同的系统,编译器,链接器,编译/链接标志上,其行为可能有所不同,具体取决于(看似)不相关的代码以及上述所有代码的版本。
在许多系统上,变量以相反的顺序连续地位于堆栈上。将您的printf替换为:
printf("a (%p) = %s\n", a, a);
printf("b (%p) = %s\n", b, b);
printf("c (%p) = %s\n", c, c);
它将打印数组的地址:
a (0x7fff559adad0) = 0123456789abcdef<F0>ښU<FF>
b (0x7fff559adac0) = 0123456789abcdef0123456789abcdef<F0>ښU<FF>
c (0x7fff559adaa0) = 0123456789abcdef<F0>ښU<FF>
从地址可以明显看出,b
的打印输出从地址0x7fff559adac0开始,但一直很好地到达a
的地址(从b
的开头开始16个字节)。 / p>
还请注意,字符串的末尾有垃圾。只是因为字符串中缺少'\ 0'终止符,并且printf
继续读取以下垃圾(UB本身就是这样)。
发生这种情况是因为:
strncpy(a, "0123456789abcdef", sizeof(a));
设置a []使其所有字节等于“ 0123456789abcdef”,而没有空终止符。没有'\ 0'的printf不知道在哪里停止,这将导致UB。
strncpy(b, "0123456789abcdef", sizeof(b));
还设置b []使其所有字节等于“ 0123456789abcdef”,而没有空终止符。同样在这里,任何printf都会导致UB。但是这次,它不是读取随机垃圾,而是读取下一个字符串。
要侮辱人格,就要行
strncpy(c, a, sizeof(c));
从16字节数组中读取32字节。这也是UB。在您(和我)的系统上,它显示为a
,并且后面有很多垃圾。从理论上讲,这可能会使您的程序因访问冲突或分段错误而崩溃。
某些病毒和蠕虫使用此类溢出来读取或写入不应有的数据。
答案 1 :(得分:1)
strncpy
是一个危险的函数,因为它仅在剩余空间时才添加空终止。这就是代码中发生的情况,您精确地复制了16个字节
实际上strncpy
函数从未打算用于C字符串,而是用于不使用空终止的古老Unix字符串格式。对于大多数目的,应避免使用此功能。特别是,它不是“ strcpy的安全版本”-而是比strcpy更危险的功能,如我们从此处的错误中所见。
解决方案是在复制之前预先检查要复制的尺寸。然后使用strcpy。例如:
char a[16];
const char to_copy[] = "0123456789abcdef";
_Static_assert(sizeof(to_copy) <= sizeof(a), "to_copy is too big");
strcpy(a, to_copy);
要修复当前程序,您需要为空终止符分配空间,如下所示:
#include <string.h>
#include <stdio.h>
int main (void)
{
char a[16+1];
char b[16+1];
char c[32+1];
const char to_copy[] = "0123456789abcdef";
_Static_assert(sizeof(to_copy) <= sizeof(a), "to_copy is too big");
_Static_assert(sizeof(to_copy) <= sizeof(b), "to_copy is too big");
strcpy(a, to_copy);
strcpy(b, to_copy);
strcpy(c, a);
printf("a = %s\n", a);
printf("b = %s\n", b);
printf("c = %s\n", c);
}