地址空间布局随机化(ASLR)和mmap

5
由于地址空间布局随机化(ALSR),我预计从另一个进程派生的进程在调用时返回的地址将不同。但是,我发现并非如此。我为此编写了以下测试程序。父进程和子进程返回的所有地址都完全相同。 请注意,cl1cl2pl1pl2的内部使用,因为它们是大块。

那么,我的问题是,即使存在ALSR,为什么也没有返回不同的地址。也许这是因为随机化的种子在原始进程和派生进程中是相同的。还是有其他原因呢?
int main()
{
  pid = fork();

  if (pid == 0)                // child
  {
    void * c1 = malloc( 4096 );
    void * c2 = malloc( 4096 );

    void * cl1 = malloc( (long)512e3 ); // internally uses mmap
    void * cl2 = malloc( (long)512e3 ); // internally uses mmap

    printf( "c1 = %p, c2 = %p, cl1 = %p, cl2 = %p!\n", c1, c2, cl1, cl2 );
  }
  else
  {
    void * p1 = malloc( 4096 );
    void * p2 = malloc( 4096 );

    void * pl1 = malloc( (long)512e3 ); // internally uses mmap
    void * pl2 = malloc( (long)512e3 ); // internally uses mmap

    printf( "p1 = %p, p2 = %p, pl1 = %p, pl2 = %p!\n", p1, p2, pl1, pl2 );
  }

  return 0;
}

我不确定 ASLR 是否需要 mmap 返回不同的地址,它只是意味着它可能返回不同的地址。或许(只是猜测!)它更可能是由 execve 触发而不是 fork。如果我连续两次启动你的程序,我会得到不同的地址。这可能会随着未来的内核版本或启用 SELinux 而发生变化... - Basile Starynkevitch
2
你可能会发现这个链接有用:http://xorl.wordpress.com/2011/01/16/linux-kernel-aslr-implementation/ - Necrolis
@Basile:当然,每次运行您会得到不同的地址,但是对于一次运行,父进程和子进程的地址是否有所不同? - MetallicPriest
我只尝试了3次,但是父进程和子进程中的地址相同。 - Basile Starynkevitch
2个回答

6
ASLR主要随机化的是从用户空间地址顶部到栈的距离,以及从栈保留空间底部到第一个mmap(可能是动态链接器映射)的距离。任何进一步的随机化都会对虚拟内存空间产生严重的碎片效应,因此会破坏需要进行大型mmap的程序(例如在32位机器上进行1-2 GB的映射)。
我看到一些Linux发行版提供了已打补丁的内核,可以在由mmap返回的地址上执行更多的随机化。其中一些甚至会给您提供与用于扩展的堆栈预留空间重叠的映射,然后当堆栈增长时,它会破坏您的映射(导致一个巨大的安全漏洞,比非随机地址分配造成的任何问题都要大)。远离这些黑客行为。

那么我们可以假设,在一个派生的进程中,mmap 不会总是返回与父进程相同的地址?对吗? - MetallicPriest
今天这通常是正确的,但我不会假设未来的内核也是如此。没有什么规定,这只是当前的实现。而且内核开发人员可能希望改进它。 - Basile Starynkevitch
我会避免做出任何一方面的假设。例如,在非常大的映射之前添加少量的随机填充(未映射页面)可能是安全/合理的,因为百分比开销/碎片化必然非常小。 - R.. GitHub STOP HELPING ICE

4

您无法重新随机化子进程的地址空间 - 所有指针都必须被调整,而这在技术上是不可能的(运行时环境甚至不知道您的数据中哪部分是指针)。

因此,您看到的结果是预期的,fork出的子进程在fork时刻具有其父进程地址空间的完全副本,包括其虚拟地址布局。

您需要使用exec*调用来获取新的地址空间布局。

$ cat t.c
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>

int main(int argc, char **argv)
{
    printf("%p\n", malloc((long)512e3));
    if ((argc > 1) && fork()) {
        execl("./a.out", "./a.out", NULL);
    }
    return 0;
}
$ gcc -Wall t.c
$ ./a.out 1
0x7f5bf6962010
0x7f3483044010
$ ./a.out 1
0x7f1ce7462010
0x7feb2adc2010

(请确保/proc/sys/kernel/randomize_va_space不为零。)

OP似乎不希望在分叉之前创建的映射更改地址,只是希望在分叉后创建的新映射获得独立随机地址。这在技术上并非不可能,但正如我在答案中所描述的那样,这是一个不好的想法。 - R.. GitHub STOP HELPING ICE
那么,您的意思是每个未来的mmap、sbrk、malloc等函数都会为父进程和子进程返回相同的地址? - MetallicPriest
1
@MetallicPriest:如果你按照完全相同的顺序、相同的数量进行操作,并且没有外部因素干扰(资源限制、OOM等),那么在普通的Linux内核下可能是可以的。一些补丁可能存在于随机化动态分配方面,但不是基本内核。有关此问题,请参见R..的答案。 - Mat

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接