【问题标题】:Microsoft's strncat reads bytes beyond source buffer boundaries微软的 strncat 读取超出源缓冲区边界的字节
【发布时间】:2013-08-30 03:36:57
【问题描述】:

我发现 Microsoft 实现 strncat 存在一个有趣的问题。它触及源缓冲区之外的 1 个字节。考虑以下代码:

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

void main()
{
    char dstBuf[1024];
    char* src = malloc(112);
    memset(src, 'a', 112);
    dstBuf[0] = 0;
    strncat(dstBuf, src, 112);
}

strncat 在 112 字节块之后读取 1 个字节。因此,如果您不幸在无效页面边界上获得分配,您的应用程序就会崩溃。大型应用程序可能会在这些地方间歇性地崩溃。 (请注意,可以使用 gflags PageHeap 设置来模拟这种情况;块大小必须能被指针大小整除才能正确对齐。)

这是预期的行为还是错误?有任何链接可以确认吗? (我阅读了一些关于strncat 的描述,但它们可以根据你最初的想法来解释两种方式......)

更新(回答有关证据的问题): 如果从上面的文字中不清楚,我很抱歉,但这是一个实验事实。我在strncat 读取地址 src+srcBufSize 的应用程序中观察到间歇性崩溃。在这个使用 gflags PageHeap 运行的小示例中,崩溃时重现一致 (100%)。所以据我所知,证据非常确凿。

Update2(编译器信息) MS Visual Studio 2005 版本 8.0.50727.867。 构建平台:64 位版本(32 位无复制)。 用于重现崩溃的操作系统:Windows Server 2008 R2。

更新 3 使用 MS Visual Studio 2012 11.0.50727.1 中内置的二进制文件也会重现该问题

更新 4 Link to issue on Microsoft Connect; link to discussion on MSDN Forums

Update 5 该问题将在下一个 VS 版本中修复。没有计划对旧版本进行修复。请参阅上面的“Microsoft Connect”链接。

【问题讨论】:

  • 你有什么证据证明这个说法?
  • 因此,如果 src 指向一个非 NUL 终止的指针数组,其大小是 8 的倍数而不是 0,则读取数组之外的 dword,尽管读取的值未使用. mov (%rdx),%rax; sub $0x8,%r8; jb 0x400011a7 ← 我可以想出几种方法来改变这一点,但会牺牲性能。
  • jb 0x400011a7 之后下沉mov (%rdx),%rax 也可以,但是下面的一些指令依赖于%rax,这可能会导致性能下降。总之,我同意这是一个错误。
  • 微软不是在 2008 年或 2009 年就已经解决了这个问题吗?请参阅support.microsoft.com/kb/956420connect.microsoft.com/VisualStudio/Downloads/…。那么为什么 VS 2012 中仍然存在这个 bug?
  • @JosephQuinsey:请注意,该修复是针对 strncpy,而不是 strncat。这个问题是关于 strncat 的。

标签: c pageheap


【解决方案1】:

documentation for strncat 声明:

src - 指向要从中复制的以 null 结尾的字节字符串的指针

因此,实现可以假定src 输入参数实际上是NUL 终止的,即使它比count 字符长。

如需进一步确认,Microsoft's own documentation 声明:

strSource

以 Null 结尾的源字符串。

另一方面,actual C standard 声明如下:

strncat 函数追加不超过n 个字符(一个空字符和 后面的字符不附加)从s2指向的数组到末尾 s1指向的字符串。

正如下面的 cmets 所指出的,这将第二个参数 s2 标识为一个 数组,而不是一个以 NUL 结尾的字符串。但是,对于原始问题,这仍然是模棱两可的,因为该文档描述了对 s1 的最终影响,而不是从 s2 读取时函数的行为。

这当然可以通过查阅 C 运行时库源代码来解决特定 Microsoft 实现。

【讨论】:

  • 不确定我是否要买这个。它说“strncat 函数最多将 strSource 的前 count 个字符附加到 strDest。”
  • @JonathonReinhart:这是产生的行为,但输入的src 字符串仍应以空值结尾。
  • 如果输入字符串短于count.,则该字符串应以空字符结尾
  • @glagolig:您的链接显示“C 字符串”。 C 字符串始终以 NUL 结尾。
  • @GregHewgill:这与将源操作数视为数组的 C 标准冲突:strncat() 函数应附加不超过 n 个字节(空字节和其后的字节)不附加)从 s2 指向的数组到 s1 指向的字符串的末尾。
【解决方案2】:

s2 不是strncat(s1, s2, n) 中的“字符串”。

因此,如果 Microsoft 正在读取 pass n 字节,则它不符合 C11。

C11 7.24.2.3.1 strcat() 提及
"将 s2 指向的 字符串 的副本(包括终止的空字符)附加到 s1 指向的字符串的末尾。

C11 7.24.2.3.2 strncat
“strncat 函数从 s2 指向的 array 中追加不超过 n 个字符(一个空字符和后面的字符不追加)到 s1 指向的字符串的末尾。.. . 一个终止的空字符总是附加到结果"

显然在strncat 的情况下,s2 被视为一个“数组”,对附加到s1 的数量有类似字符串的限制。因此,在连接过程中,不需要检查s2 而不是绝对需要的。最后写的\0来自代码,不是s2

不知道旧的 C99 标准。

【讨论】:

    【解决方案3】:

    英语是一种不完美的语言,比 C 语言更不完美。

    文档说“最多 n 个字符”(我的重点)。没有证据表明 strncat 复制了超过 112 个字符。是什么让你相信它确实如此?

    strncat 的代码可能会索引超过 112 的偏移量,但实际上并未引用偏移量 113,这可能会导致存储错误。这种 ptr 行为在 K&R 中被定义为可接受的。

    最后,这又是一个英语/推理问题,文档可能确实说空终止字符串。但实际上,说一个字符串是空终止的不是多余的吗?它们是根据定义的,否则它们将是一个字符数组。因此,文档模糊且不具体。程序员可以在字里行间阅读。软件文档不是合法的书籍,它们是旨在由本领域技术人员理解的描述。

    【讨论】:

    • 软件文档不是合法书籍 ← 但标准确实有点像合法书籍,C 标准将源参数视为一个数组。 strn* 函数设计用于处理固定大小的记录,因此它们的行为并不像它们的主题只是字符串一样。
    • @ninjalj,我同意你的观点,它们是固定大小的记录,n 决定了该记录的长度。允许/定义 C 在数组末尾之后索引 1,这就是 for(etc; i++) 起作用的原因!
    猜你喜欢
    • 2021-06-15
    • 2015-07-26
    • 2011-11-10
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-09-02
    • 2020-07-30
    相关资源
    最近更新 更多