【问题标题】:Is malloc deterministic?malloc 是确定性的吗?
【发布时间】:2011-12-31 13:02:18
【问题描述】:

ma​​lloc 是确定性的吗?假设我有一个分叉的进程,即另一个进程的副本,并且在某些时候它们都调用了 ma​​lloc 函数。两个进程分配的地址是否相同?假设执行的其他部分也是确定性的。

注意:这里我说的是虚拟内存,不是物理内存。

【问题讨论】:

    标签: c linux gcc x86 malloc


    【解决方案1】:

    完全没有理由让它是确定性的,事实上它不是确定性的可能有一些好处,例如increasing the complexity of exploiting bugs(另见this paper)。

    这种随机性有助于使漏洞更难编写。要成功利用缓冲区溢出,您通常需要做两件事:

    1. 将有效负载传送到可预测/已知的内存位置
    2. 导致执行跳转到该位置

    如果内存位置不可预测,那么跳转会变得非常困难。

    标准§7.20.3.3/2中的相关引用:

    malloc 函数为大小为 由大小指定,其值不确定

    如果打算使其具有确定性,那么将明确说明。

    即使它今天看起来是确定性的,我也不会打赌它会在更新的内核或更新的 libc/GCC 版本上保持不变。

    【讨论】:

    • 我认为安全性与这个问题没有太大关系。不过,您写的关于利用的内容是正确的。
    • @jweyrich - C 标准明确声明它不是确定性的。它不是确定性的这一事实可能对以多种方式实现有用 - 我使用地址空间随机化作为一个明显的现代示例,但还有其他不太明显的原因(不存在虚拟内存之类的实现) )。
    • 我没有说别的。你的答案是好的,尽管 IMO 引用标准的那部分将是明确的答案。
    • @SimonRichter - 我不得不承认我一直认为 fork() 复制了所有线程,但你是对的,规范也非常清楚 - pubs.opengroup.org/onlinepubs/009695399/functions/fork.html - malloc() 不是'不是异步安全的,如果我没记错的话,这意味着你不能在 fork() 之后合法地在孩子中调用它,因为它似乎在多线程 fork() 之后对孩子强加了异步安全要求。跨度>
    • malloc 在vfork 之后不安全——但在fork 之后就完全没问题了。
    【解决方案2】:

    C99 规范(至少在其 final public draft 中)在“J.1 未指定行为”中声明:

    以下未指定: ... 连续调用分配的存储顺序和连续性 calloc、malloc 和 realloc 函数 (7.20.3)。

    因此,malloc 似乎不必是确定性的。因此,假设它是不安全的。

    【讨论】:

    • 我不认为你错了,但那句话严格地讲了存储的连续性,这与确定性无关。一个实现可能是确定性的,但不会在连续调用中分配连续的内存。还是我错了?
    • jweyrich,我同意你的看法。确定性与分配存储的顺序和连续性无关。
    • 它谈到了连续性和秩序。所以我的阅读是规范没有指定返回存储顺序的必需行为。因此,除其他外,它没有指定它必须是确定性的。因此,一个有效的实现可能是确定性的,也可能不是确定性的。
    【解决方案3】:

    这完全取决于malloc 的实现。特定的malloc 实现会引入非确定性并没有内在的原因(可能作为应用程序模糊测试除外,但即便如此,它也应该默认禁用)。例如,Doug Lea's malloc 不使用rand(3) 或其中的任何类似方法。

    但是,由于malloc 会调用内核,例如 Linux 上的 sbrk(2)mmap(2) 或 Windows 上的 VirtualAlloc,因此这些系统调用可能并不总是确定性的,即使在其他相同的进程中也是如此。无论出于何种原因,内核都可能决定在不同的进程中故意提供不同的mmap'ed 地址。

    因此,对于通常在没有系统调用的用户空间中进行服务的小型分配,很可能在fork() 之后生成的指针将是相同的;由系统服务的大型分配一次调用可以是相同的。

    不过,一般来说,不要依赖它。如果您确实需要在不同的进程中使用相同的指针,请在分叉之前创建它们,或者使用共享内存并适当地共享它们。

    【解决方案4】:

    这取决于malloc的详细实现。一个典型的 malloc 实现(例如,dlmalloc)曾经是确定性的。这仅仅是因为算法本身是确定性的。

    但是,由于堆溢出攻击等许多安全攻击,malloc,即堆管理器,在其实现中引入了一些随机性。 (但是,它的熵相对较小,因为堆管理器必须考虑速度和空间)因此,您不应该在堆管理器中假设严格的确定性是安全的。

    此外,当您分叉一个进程时,有多种随机来源,包括 ASLR

    【讨论】:

    • 据我所知,ASLR 不是在 fork 进程上执行的,而是在父进程上执行的,还是这样?
    • @MetallicPriest:ASLR 无法更改分叉时已经存在的堆栈/堆地址,但它会影响未来用于服务 malloc 请求的 mmap 调用。
    【解决方案5】:

    是的,它在某种程度上是确定性的,但这并不一定意味着它会在一个进程的两个分支中给出相同的结果。

    例如,单一 Unix 规范说:“[...] 为避免错误,子进程只能执行异步信号安全操作,直到调用其中一个 exec 函数。”

    无论好坏,malloc 不在“异步信号安全”函数列表中。

    此限制在讨论多线程程序的部分中,但未指定该限制是仅适用于多线程程序,还是也适用于单线程程序。

    结论:您不能指望malloc 在父子节点中产生相同的结果。如果程序是多线程的,你不能指望malloc 在子进程中工作,直到它调用exec - 并且存在合理的问题,即使在单线程子进程中它实际上也能保证工作在孩子打电话给exec之前。

    参考资料:

    1. fork specification
    2. async-signal safe functions

    【讨论】:

    • POSIX fork(2) 确实完美地再现了整个虚拟地址空间。当fork() 返回时,所有指针在父子节点中仍然有效。 (这当然需要虚拟内存有两个使用相同虚拟地址的进程,这使得高效的写时复制实现成为可能。)最后我读到,Windows 本身并不提供 fork() 系统调用。 cygwin必须模仿它,这并不容易。所以如果你习惯了 Windows 系统调用,我猜 fork() 会显得很奇怪。
    • 是的,mmap 当然是不确定的,所以整个问题的答案是肯定的“否”。我特别评论了您的说法,即“fork 必须以相同的方式复制堆(包括所有空闲块)。”,确实如此,所以这不是障碍。
    • “尽管是一个分叉,一个进程的克隆的地址空间布局可能与原始进程大不相同。”是错的。无论如何,已经发布了正确的答案,所以我建议删除您 6 年前发布的错误答案:P
    • @PeterCordes:我更喜欢改进帖子,特别是因为以前没有一个是非常明确的。
    • 是的,这也有效,因为显然有一些有趣的东西要说,其他人还没有说过:) 很好的研究。
    【解决方案6】:

    您不会得到相同的物理地址。如果您有进程 A 和 B,则每次调用 malloc 都会返回一个空闲块的地址。 A 和 B 调用 malloc 的顺序是不可预测的。但它永远不会“同时”发生。

    【讨论】:

      【解决方案7】:

      从技术上讲,如果分叉的进程都请求相同大小的块,它们应该分配相同的地址,但每个地址将指向不同的物理/实际内存位置。

      Linux 为 fork 使用写时复制,因此 fork 的子代共享其父代的内存,直到任一进程中的某些内容发生更改。此时,内核会通过内存复制序列为分叉的子节点提供其内存空间的专用/唯一副本。

      【讨论】:

      • 我说的不是真实内存,只是虚拟内存。我知道写时复制和虚拟内存管理。
      • “从技术上讲,如果分叉的进程都请求相同大小的块,它们应该分配相同的地址” - 这根本不是真的 - 给定地址的值malloc 的作者是未指定的,并且随机性通常是由内核调用本身引入的,而不是用户空间中的任何内容,因此 libc 实现不必调用 rand() 或任何类似的疯狂来使其具有不确定性。
      • malloc 将尝试防止地址空间碎片,并且不会随机分配块。但是当内核将进程的虚拟内存空间映射到物理空间时,该映射可以随机化。
      • @MarcB - malloc 可能只是对sbrkmmap 的调用(因为我们正在寻找确定性,我们不能安全地假设剩余的内存足够来自free 或之前的sbrk/mmap 调用来处理请求),这些调用都没有承诺比尝试满足请求本身更多。调用其中任何一个的新指针可以明智地在可用的虚拟地址空间内随机化
      • @Flexo:sbrk 是确定性的。父母和孩子都在同一个地方休息,因为fork() 复制了父母的地址空间。在 Linux 上,底层系统调用是 void *sys_brk(void*) 并且只是设置中断,返回新值。 glibc 会跟踪旧的中断,以了解为 sbrk 增量函数调用传递什么值。
      猜你喜欢
      • 1970-01-01
      • 2012-05-13
      • 2016-10-20
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2016-05-12
      • 2021-05-26
      相关资源
      最近更新 更多