在 Linux 系统中,使用 mmap 是可以把内存映射到 0 地址,也就是空指针上的,mmap 函数的第一个参数 addr 用于指定期望映射的起始地址,如果你希望强制映射到某个地址,需要使用 MAP_FIXED 标志。下面的代码就利用 mmap 的这个特点,把一段内存映射到虚拟地址 0 的位置上,如果该代码能够执行成功,我们就可以正常的访问空指针了。

编译这段代码,运行,效果如下,mmap 失败,原因是没权限。

这是出于安全考虑,默认情况下你是无法通过 mmap 映射到 0 地址的。Linux 内核有一个安全参数 vm.mmap_min_addr,它规定了用户空间进程允许映射的最低虚拟地址。这个参数的默认值通常是 65536(0x10000)。任何低于此值的地址(包括 0 地址)都不允许被映射,以防止内核空指针解引用漏洞被用户态程序利用来提权。你可以 cat 一下伪文件 /proc/sys/vm/mmap_min_addr 来查看 mmap_min_addr 参数的默认值。

要想让上面的程序成功执行,有两种方法,第一种方法就是以 root 的身份运行这个程序。

第二种方法就是修改 vm.mmap_min_addr 这个内核参数。

可以看到上面这两种方法都可以让 mmap 把一段内存映射到 0 地址处,让我们实现访问空指针的想法。
正因为 mmap 是可以映射到 0 地址处,所以判断这个函数是否执行成功不是去对其返回值进行判空,而是判断其返回值是否等于 MAP_FAILED,MAP_FAILED 是一个宏定义,通常被定义为 (void *)-1。
最后需要注意的是允许 mmap 映射到 0 地址会极大降低系统安全性,历史上很多提权漏洞(如内核函数指针未检查就调用,而指针为 NULL)都是依赖攻击者先 mmap 到 0 地址放置恶意代码来完成的,这也是为什么内核引入并默认启用了 vm.mmap_min_addr 参数。所以在你做完上面的实验之后,最好把 vm.mmap_min_addr 的值恢复回去。

Have Fun!