MMAP_PAGE_ZERO personality标志工作原理及正确用法
问题原因
你写的测试代码触发段错误的核心原因是personality()的调用时机错误。MMAP_PAGE_ZERO标志只会影响标志设置完成后,内核新加载的进程的内存映射规则,不会修改已经处于运行状态的当前进程的已有内存布局。你在main()函数里才调用这个接口时,当前进程的地址空间早已完成初始化,零页默认被内核标记为不可访问状态,后续修改personality标志不会回溯调整已经建立的内存映射,因此解引用空指针依然会触发段错误。
另外还有一个容易踩的坑:直接传入MMAP_PAGE_ZERO作为参数调用personality()会覆盖进程默认的personality配置,清除系统默认开启的兼容性标志,容易引发其他运行异常。
正确使用方式
MMAP_PAGE_ZERO必须在目标程序被内核加载前就完成设置,有两种常见的正确用法:
- 命令行直接使用:通过
setarch工具在启动程序前预置标志,不需要修改程序代码,执行命令setarch -Z ./your_program即可,其中-Z参数对应开启MMAP_PAGE_ZERO特性,setarch会先完成personality设置,再exec加载目标程序,内核加载程序时就会自动将零页映射为只读。 - 代码内自实现:在代码中设置完标志后,通过
exec族函数重新加载自身进程镜像,让内核在加载阶段应用标志,参考代码如下:
#include <stdio.h> #include <sys/personality.h> #include <unistd.h> int main(int argc, char *argv[]) { // 首次启动时先设置personality再重载自身 if (argc == 1) { // 先读取当前personality值,叠加标志避免覆盖默认配置 int cur_persona = personality(0xffffffff); personality(cur_persona | MMAP_PAGE_ZERO); // 重新执行自身,传入参数标记已完成配置 execl(argv[0], argv[0], "loaded", NULL); perror("execl failed"); return 1; } // 重载后的进程零页已被映射为只读,可以正常读取 int *null_ptr = 0; printf("零页起始值:%d\n", *null_ptr); return 0; }
注意事项
- 该特性属于SVr4兼容层功能,仅在x86等少数架构上生效,且只映射地址0开始的第一个标准内存页(通常为4KB)为只读,访问该范围外的低地址依然会触发段错误。
- 部分开启了强制SMAP、特殊安全加固策略的内核版本可能会禁用这个特性,测试前需要确认对应内核配置是否支持。
内容的提问来源于stack exchange,提问作者mrn
相关产品推荐
相关产品推荐

