Linux 内核中/proc/self/maps 的实现与匿名空间释放机制探究
字数 2114 2025-10-01 14:05:45
Linux 内核中 /proc/self/maps 的实现与匿名空间释放机制
引言
在 Linux 系统安全领域,/proc/self/maps 文件的分析对检测隐藏 so 注入等技术至关重要。本文基于 Linux 内核 4.9 版本源码,深入分析 /proc/self/maps 文件的生成机制和匿名内存空间的释放原理。
一、核心结论
-
匿名空间释放后的可见性:
使用munmap系统调用释放匿名空间后,该空间不会出现在/proc/self/maps文件中。原因是内核会将对应的虚拟内存区域(vm_area_struct)从进程的虚拟内存区域链表中删除 -
maps 文件生成机制:
/proc/self/maps的内容根据进程的虚拟内存区域链表动态生成,相关代码位于fs/proc/task_mmu.c的show_map_vma函数
二、/proc/self/maps 的内核实现
2.1 实现位置
- 主要文件:
fs/proc/task_mmu.c - 辅助文件:
fs/proc/root.c(/proc 文件系统基础实现)
2.2 关键函数:show_map_vma
static void show_map_vma(struct seq_file *m, struct vm_area_struct *vma, int is_pid)
{
struct mm_struct *mm = vma->vm_mm;
struct file *file = vma->vm_file;
struct proc_maps_private *priv = m->private;
vm_flags_t flags = vma->vm_flags;
unsigned long ino = 0;
unsigned long long pgoff = 0;
unsigned long start, end;
dev_t dev = 0;
const char *name = NULL;
// 文件映射处理
if (file) {
struct inode *inode = file_inode(vma->vm_file);
dev = inode->i_sb->s_dev;
ino = inode->i_ino;
pgoff = ((loff_t)vma->vm_pgoff) << PAGE_SHIFT;
}
// 堆区域识别
if (vma->vm_start <= mm->brk && vma->vm_end >= mm->start_brk) {
name = "[heap]";
goto done;
}
// 输出格式:起始地址-结束地址 权限标志 文件偏移 设备号 inode号
seq_printf(m, "%08lx-%08lx %c%c%c%c %08llx %02x:%02x %lu ",
start, end,
flags & VM_READ ? 'r' : '-',
flags & VM_WRITE ? 'w' : '-',
flags & VM_EXEC ? 'x' : '-',
flags & VM_MAYSHARE ? 's' : 'p',
pgoff, MAJOR(dev), MINOR(dev), ino);
}
2.3 maps 文件生成流程
- 用户空间执行
cat /proc/self/maps命令 - 内核调用
proc_pid_maps_operations.open函数 - 用户空间调用
read系统调用读取内容 - 内核调用
seq_read函数,进而调用show_map函数 show_map函数遍历虚拟内存区域链表,调用show_map_vma生成内容
三、匿名空间的申请与释放机制
3.1 匿名空间申请过程
匿名空间通过 mmap 系统调用分配,调用链如下:
mmap_pgoff函数(入口点)vm_mmap_pgoff函数do_mmap_pgoff函数(位于mm/mmap.c)do_mmap函数mmap_region函数(核心实现)
匿名映射特征:vm_area_struct 的 vm_file 字段为 NULL,maps 文件中设备号和 inode 号均为 0
3.2 匿名空间释放过程
通过 munmap 系统调用释放,调用链如下:
vm_munmap函数do_munmap函数(核心函数)
do_munmap 函数的三个关键步骤:
1. detach_vmas_to_be_unmapped 函数
- 作用:将待删除的 VMA 从进程的虚拟内存区域链表和红黑树中移除
- 效果:切断 VMA 与进程地址空间的逻辑关联
2. unmap_region 函数
- 作用:解除虚拟内存映射并回收页表
- 功能:在进程页表中删除映射,从处理器页表缓存中删除映射
3. remove_vma_list 函数
- 作用:释放 VMA 结构体内存,修正进程内存统计信息
- 功能:完成 VMA 生命周期的最后清理工作
四、/proc/self/maps 与匿名空间释放的关系
4.1 释放后的可见性
释放后的匿名空间不会出现在 maps 文件中,因为:
- maps 内容基于当前虚拟内存区域链表生成
munmap操作已将对应 VMA 从链表中删除- 内核更新了页表和内存统计信息
4.2 内核处理流程
- 查找需要删除的虚拟内存区域
- 从进程的虚拟内存区域链表和红黑树中删除目标区域
- 更新进程页表,删除对应映射
- 更新虚拟内存区域统计信息
4.3 验证方法
使用以下 C 程序验证:
#include <sys/mman.h>
#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
int main() {
// 分配匿名空间
void *addr = mmap(NULL, 4096, PROT_READ|PROT_WRITE,
MAP_ANONYMOUS|MAP_PRIVATE, -1, 0);
printf("分配后查看 maps 文件(应有匿名空间)\n");
getchar(); // 暂停,查看 maps
// 释放匿名空间
munmap(addr, 4096);
printf("释放后查看 maps 文件(应无匿名空间)\n");
getchar(); // 再次查看 maps
return 0;
}
五、关键问题解答
1. maps 文件如何形成?
- 形成机制:动态生成的虚拟文件,基于进程的虚拟内存区域链表
- 生成过程:用户触发 → 内核遍历 → 信息格式化 → 数据返回
- 核心文件:
fs/proc/task_mmu.c中的相关函数
2. 匿名空间是否显示在 maps 文件中?
- 未释放时:显示在 maps 中(表现为无文件关联、设备号和 inode 号为 0)
- 释放后:不再显示(对应的 VMA 已从内存链表中移除)
3. 匿名空间的原理是什么?
- 本质:无文件关联的虚拟内存机制
- 创建方式:
mmap(MAP_ANONYMOUS | MAP_PRIVATE, ...) - 核心特征:
vm_area_struct的vm_file字段为 NULL - 内存管理:虚拟地址分配 → 物理内存延迟映射 → 内存回收
4. 隐藏 so 注入技术的实现
通过匿名空间映射 so 文件并执行后释放,使得 so 代码仍在内存中运行,但在 maps 文件中不可见,从而实现隐藏注入。
参考资料
- Linux 内核源码(4.9 版本)
- cirosantilli/linux-kernel-module-cheat (GitHub)
- Kernel.org 内存管理文档
- Linux 内核虚拟内存管理机制分析文章
通过本文分析,可以深入理解 Linux 内核中内存映射的机制和 maps 文件的生成原理,为安全分析和技术开发提供理论基础。