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 文件的生成机制和匿名内存空间的释放原理。

一、核心结论

  1. 匿名空间释放后的可见性
    使用 munmap 系统调用释放匿名空间后,该空间不会出现在 /proc/self/maps 文件中。原因是内核会将对应的虚拟内存区域(vm_area_struct)从进程的虚拟内存区域链表中删除

  2. maps 文件生成机制
    /proc/self/maps 的内容根据进程的虚拟内存区域链表动态生成,相关代码位于 fs/proc/task_mmu.cshow_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 文件生成流程

  1. 用户空间执行 cat /proc/self/maps 命令
  2. 内核调用 proc_pid_maps_operations.open 函数
  3. 用户空间调用 read 系统调用读取内容
  4. 内核调用 seq_read 函数,进而调用 show_map 函数
  5. show_map 函数遍历虚拟内存区域链表,调用 show_map_vma 生成内容

三、匿名空间的申请与释放机制

3.1 匿名空间申请过程

匿名空间通过 mmap 系统调用分配,调用链如下:

  1. mmap_pgoff 函数(入口点)
  2. vm_mmap_pgoff 函数
  3. do_mmap_pgoff 函数(位于 mm/mmap.c
  4. do_mmap 函数
  5. mmap_region 函数(核心实现)

匿名映射特征vm_area_structvm_file 字段为 NULL,maps 文件中设备号和 inode 号均为 0

3.2 匿名空间释放过程

通过 munmap 系统调用释放,调用链如下:

  1. vm_munmap 函数
  2. 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 文件中,因为:

  1. maps 内容基于当前虚拟内存区域链表生成
  2. munmap 操作已将对应 VMA 从链表中删除
  3. 内核更新了页表和内存统计信息

4.2 内核处理流程

  1. 查找需要删除的虚拟内存区域
  2. 从进程的虚拟内存区域链表和红黑树中删除目标区域
  3. 更新进程页表,删除对应映射
  4. 更新虚拟内存区域统计信息

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_structvm_file 字段为 NULL
  • 内存管理:虚拟地址分配 → 物理内存延迟映射 → 内存回收

4. 隐藏 so 注入技术的实现

通过匿名空间映射 so 文件并执行后释放,使得 so 代码仍在内存中运行,但在 maps 文件中不可见,从而实现隐藏注入。

参考资料

  1. Linux 内核源码(4.9 版本)
  2. cirosantilli/linux-kernel-module-cheat (GitHub)
  3. Kernel.org 内存管理文档
  4. Linux 内核虚拟内存管理机制分析文章

通过本文分析,可以深入理解 Linux 内核中内存映射的机制和 maps 文件的生成原理,为安全分析和技术开发提供理论基础。

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 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 程序验证: 五、关键问题解答 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 文件的生成原理,为安全分析和技术开发提供理论基础。