【问题标题】:Kernel zeroes memory?内核将内存归零?
【发布时间】:2011-08-25 16:16:09
【问题描述】:

我正在使用 Debian Squeeze,并注意到内存总是归零。这是 linux 发行版中的新功能吗?前段时间,我相信我可以使用 puts() 并且会输出垃圾。

我多次运行此测试程序,但注释结果始终相同。 (我在 sysctl.conf 中有 randomize_va_space=2,所以我知道每次运行都会使用不同位置的内存。)


char *a = malloc(50000000);
a[49999999] = '\0';
puts(a); // it outputs nothing since all are zeroes
printf("%p\n", a);
if(a[5000] == '\0') // this condition is always true
{
    puts("It is a nul char.");
}

是否可以让系统不为零内存?这个 Debian 挤压安装可以激活哪些选项,总是零内存?

【问题讨论】:

    标签: c memory-management linux-kernel


    【解决方案1】:

    在任何现代操作系统上,新获得的内存将包含非零值的唯一方法是,如果您的程序之前释放的内存被 malloc 重用。当从操作系统(内核)获得新内存时,它最初是纯虚拟的。它没有物理存在;相反,它被映射为一个充满 0 字节的共享内存页面的写时复制映射。第一次尝试写入时,内核会捕获写入,分配一个新的物理内存页面,将原始页面的内容(在这种情况下都是 0 字节)复制到新页面,然后恢复你的程序。如果内核知道新分配的物理内存已经被零填充,它甚至可以优化复制步骤。

    这个过程既必要又有效。这是必要的,因为将可能包含来自内核或其他用户进程的私有数据的内存移交给您的进程将是一个严重的安全漏洞。它很有效,因为在分配时不执行归零; “零填充”页面只是对共享零页面的引用。

    【讨论】:

    • Windows 中有一个线程,其工作是将未使用的物理页面清零,以提供可以安全地映射到用户空间的新页面池。 (相比之下,内核允许分配未归零的页面供自己使用。)
    • 但是,内核开发人员仍必须确保其“未归零”内存页面中的数据不会泄露给任何用户模式进程。此外,考虑到内存在后台清零,对系统的影响很小,除非存在显着的内存流失。但是,无论是否进行任何归零,在内存中搅动都可能是一个性能问题。
    【解决方案2】:

    根据我在 Linux Kernel Development 中读到的内容,内核执行零页,因为它可能包含用户程序可以解释并以某种方式获得系统访问权限的内核数据。

    malloc 向内核请求更多页面,因此内核负责您接收的内存。

    【讨论】:

    • 根据 brk/sbrk 功能上的这个 WP 页面:en.wikipedia.org/wiki/Sbrk 你是对的。但这对内核来说似乎是一件非常浪费的事情。
    • 为什么?对于程序来说,这似乎是一件聪明的事情。如果你有一个非常愚蠢的程序,它持有未加密的愚蠢数据,然后在没有释放它的情况下就死了,你可能会编写一个程序来利用它。我很确定你可以在编译内核时禁用它。
    • “禁用它”?绝对没有办法通过普通选项使内核泄漏数据到用户空间;你必须故意破坏它才能做到这一点。由于新页面是对零页面的 COW 引用,因此没有“默认情况”会泄漏。
    • 您可以禁用它(通常只针对只有我们运行您的软件的嵌入式系统才这样做)。在多用户系统上,内核清零绝对是正确的做法。
    【解决方案3】:

    第一次 malloc 块内存时,它很有可能为零,因为系统调用(sbrk,mmap)分配的内存被内核清零。但是如果你再次释放和 malloc 内存会被回收并且可能不包含零。

    【讨论】:

      【解决方案4】:

      您会发现,在大多数进程之间具有隔离的操作系统上,内存已归零。原因是不允许一个进程偷看另一个进程释放的内存,因此必须在某个进程释放内存页和另一个进程释放内存页之间擦除内存页。在实际应用中,erered 表示清零,内存通常在进程分配的时候清零。

      当你在你的玩具程序中调用malloc 时,内存还没有被用于其他任何事情。所以它仍然是来自内核的新鲜事物,充满了零。如果您在一个已经分配并释放大量堆块的真实程序中尝试,您会发现已被您的进程使用的内存仍然包含您(或内存管理系统)的任何垃圾可能已经放在那里了。

      【讨论】:

        【解决方案5】:

        如前所述,主要区别在于首次分配分配。如果你尝试:

        char *a, tst;
        do {
            a = malloc(50000000);
            a[49999999] = '\0';
            printf("%50s\n%p", a, a); // it outputs nothing 1st, but bbbb.... 2nd
            tst = a[5000]
            memset(a, 'b', 50000000);
            free(a);
        } while (tst == '\0');
        

        它会打印两行(很可能,至少在指针相同的情况下)。

        关键是malloc()返回的内存块有未定义的内容。它可能是也可能不是零,并且取决于程序过去是如何分配内存的(或者使用了哪些内存调试工具)。

        如果要保证内容,需要calloc()或者分配后显式初始化。

        另一方面,系统的完整性/数据分离保证意味着系统请求的任何初始地址空间 - 无论是通过sbrk() 还是mmap(MAP_ANON) - 都必须进行零初始化,就像任何此类的其他内容将构成安全漏洞。

        【讨论】:

          【解决方案6】:

          您的代码不会测试是否所有内存都归零 - 它会测试两个特定字节是否为零 - a[0] 和 a[5000]。此外, malloc() 与内核无关 - 它是 C 库函数,而不是系统调用。它的实现者极不可能将内存归零 - 您所看到的只是您的特定配置的一些随机怪癖。

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 2012-12-27
            • 1970-01-01
            • 2012-08-11
            • 2015-08-21
            • 1970-01-01
            • 1970-01-01
            • 2011-12-26
            • 1970-01-01
            相关资源
            最近更新 更多