我在阅读有关 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);
}
output:
a = 0123456789abcdef0123456789abcdef
b = 0123456789abcdef
c = 0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef
最佳答案
strncpy
中的n
与strncat
或snprintf 中的
; n
的含义不同strncpy
的诞生是为了操作固定大小的缓冲区字符串,例如目录条目,因此它最多复制 n 个字符,用 NULs ( = byte 0 = '\0'
= null character = ... ) 填充未使用的字符,但如果有没有备用的,它不添加任何 NUL。因此,strncpy
的目标不一定会以 NUL 结尾,因此如果您尝试将其作为 C 字符串进行操作,您会遇到一些意外。
这正是本例中发生的情况。您的 a
和 b
缓冲区与您在其中复制的字符串一样长; strncpy
不会以 NUL 终止它们,因此当第三个 strncpy
或后面的 printf
尝试从中读取时,结果是任何人的猜测(阅读:这是未定义的行为,所以任何事情都可能发生),因为没有 NUL 阻止它们继续读取不相关的内存。
至于你得到的特定输出,它取决于 a
、b
和 c
在内存中的布局(实际上,在我的机器上我得到不同的结果),从 strncpy
是如何写的(因为它不打算在重叠字符串上调用)以及优化器如何决定破坏你的代码(记住:阅读边界之外是未定义的行为,因此允许优化器假设它在重新排列代码时永远不会发生)。
您所看到的实际行为的一个可能解释是 c
、a
和 b
在内存中连续布局,在此顺序,堆栈的其余部分恰好是 NUL 填充的(这里我使用 °
作为 NUL1 的占位符):
°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°...
c a b ?
那么发生的事情应该是这样的:
0123456789abcdef
被复制到a
中,没有任何 NUL 终止,因为它达到了最大允许字符数 (16)。°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°0123456789abcdef°°°°°°°°°°°°°°°°°°°°... c a b ?
0123456789abcdef
被复制到b
中,没有任何 NUL 终止(与之前相同)。°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°0123456789abcdef0123456789abcdef°°°°... c a b ?
a
被复制到c
;因为a
不是 NUL 终止的,strncpy
继续愉快地直接读入b
的空间,复制允许复制的完整 32 个字符.因为它达到了 32 个字符,所以没有写入 NUL。0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef°°°°... c a b ?
a
被打印;因为它不是 NUL 终止的,printf
继续读取后面的内存,即b
,从而打印 32 个字符。b
被打印;它并没有明确地被NUL终止,但是它之后的内存恰好包含一个NUL,所以它在复制的16个字符之后停止;c
被打印;因为它不是 NUL 终止的,printf
继续读取c
、a
和b
的整个长度(在其末尾为 NUL,即停止打印),打印 64 个字符。
请记住:这只是对您显示的输出的可能解释。它不一定正确,当然也不是契约(Contract)规定的(在我的机器上,我根据编译标志获得不同的输出,甚至在不同的运行中,这取决于启动时堆栈上发生的情况)。
- 我喜欢使用 ␀ 符号,但即使在代码块中,它也经常以非固定宽度呈现,从而打破了 ASCII 艺术。
关于c-String 漏洞,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/55096882/