为何启用ASLR后mmap匿名映射地址未随机化?(Ubuntu 16.04 x86_64)
先还原你的场景:
你在最新版Ubuntu 16.04 x86_64上运行这段代码:
void main(int argc, char *argv[]) { while(1) { char *x = (char *)mmap(0, 1000, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); printf("%p\n", x); munmap(x, 1000); } }
结果输出全是同一个地址:0x7f02ca14c000 0x7f02ca14c000 ...,但你预期在ASLR全局启用、传入0作为地址提示的情况下,应该得到随机地址。
为什么会出现这种情况?
这不是ASLR没生效,而是内核为了优化内存分配效率,会优先复用刚刚释放的地址空间块。你的代码里刚调用munmap释放了1000字节的匿名映射,紧接着又申请同样大小的匿名映射,内核会直接把刚释放的地址重新分配给你,完全跳过随机地址的选择逻辑——毕竟复用现成的空闲块比重新找一个随机地址要高效得多,还能减少地址空间碎片化。
怎么让mmap返回随机地址?
你可以通过以下几种方式打破这种复用逻辑:
在munmap和mmap之间插入其他内存操作
比如在释放后先做一次malloc/free,打乱内核的空闲地址空间列表,这样下一次mmap就不会优先拿到刚释放的地址:#include <stdio.h> #include <sys/mman.h> #include <stdlib.h> int main(int argc, char *argv[]) { while(1) { char *x = (char *)mmap(0, 1000, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); printf("%p\n", x); munmap(x, 1000); // 插入malloc/free打乱地址空间 char *tmp = malloc(1024); free(tmp); } return 0; }不要立刻释放已分配的映射
比如每次申请后保留映射,直到积累到一定数量再批量释放,这样每次新申请的地址就会从不同的空闲区域选择:#include <stdio.h> #include <sys/mman.h> #include <stdlib.h> #define MAX_MAPS 10 int main(int argc, char *argv[]) { char *maps[MAX_MAPS]; int count = 0; while(1) { maps[count] = (char *)mmap(0, 1000, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); printf("%p\n", maps[count]); count++; if (count >= MAX_MAPS) { // 批量释放 for(int i=0; i<MAX_MAPS; i++) { munmap(maps[i], 1000); } count = 0; } } return 0; }确认ASLR确实处于完全启用状态
先检查系统的ASLR配置,执行命令:cat /proc/sys/kernel/randomize_va_space如果输出是
2,说明ASLR完全启用;如果是1则是部分随机,0是关闭。如果不是2,可以通过以下命令临时开启(需要root权限):echo 2 > /proc/sys/kernel/randomize_va_space
补充说明
ASLR的作用是在进程启动时随机化整个地址空间的布局(比如栈、堆、库加载地址的起始位置),但它不会强制内核在每次内存分配时都选择完全随机的地址——内核的内存分配策略还是会优先考虑效率和减少碎片化,所以才会出现你看到的复用情况。上面的方法都是通过干扰这种复用逻辑,让内核不得不选择新的随机地址。
内容的提问来源于stack exchange,提问作者Whome

