c-String 漏洞

标签 c string security

我在阅读有关 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 中的nstrncatsnprintf 中的n 的含义不同; strncpy 的诞生是为了操作固定大小的缓冲区字符串,例如目录条目,因此它最多复制 n 个字符,用 NULs ( = byte 0 = '\0' = null character = ... ) 填充未使用的字符,但如果有没有备用的,它不添加任何 NUL。因此,strncpy 的目标不一定会以 NUL 结尾,因此如果您尝试将其作为 C 字符串进行操作,您会遇到一些意外。

这正是本例中发生的情况。您的 ab 缓冲区与您在其中复制的字符串一样长; strncpy 不会以 NUL 终止它们,因此当第三个 strncpy 或后面的 printf 尝试从中读取时,结果是任何人的猜测(阅读:这是未定义的行为,所以任何事情都可能发生),因为没有 NUL 阻止它们继续读取不相关的内存。

至于你得到的特定输出,它取决于 abc 在内存中的布局(实际上,在我的机器上我得到不同的结果),从 strncpy 是如何写的(因为它不打算在重叠字符串上调用)以及优化器如何决定破坏你的代码(记住:阅读边界之外是未定义的行为,因此允许优化器假设它在重新排列代码时永远不会发生)。

您所看到的实际行为的一个可能解释是 cab 在内存中连续布局,在此顺序,堆栈的其余部分恰好是 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 继续读取 cab 的整个长度(在其末尾为 NUL,即停止打印),打印 64 个字符。

请记住:这只是对您显示的输出的可能解释。它不一定正确,当然也不是契约(Contract)规定的(在我的机器上,我根据编译标志获得不同的输出,甚至在不同的运行中,这取决于启动时堆栈上发生的情况)。


  1. 我喜欢使用 ␀ 符号,但即使在代码块中,它也经常以非固定宽度呈现,从而打破了 ASCII 艺术。

关于c-String 漏洞,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/55096882/

相关文章:

MySQL - 数据库安全

java - 使用 Google Guava 格式化字符串

java - 使用 [3 :0] substring in it 解析字符串

c - 在 C 中的 main() 函数之前打印 "Hello world"

c++ - 在数组中查找 y 的 x 个连续值的最有效方法是什么?

javascript - 使用 javascript 的替换函数时出现字符串语法错误

algorithm - 客户端服务请求的隐式 "Authentication"

security - 调用“unsafe.Pointer(&x)”不安全吗?

c++ - 两个相同的unordered_maps的顺序是否相同?

c - 指向 ANSI C 中定义的空指针的指针吗?