You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

使用remap_pfn_range()映射内核空间到用户空间时模块崩溃问题

内核内存映射到用户空间的崩溃问题修复

核心问题分析

你的代码崩溃源于以下几个致命问题:

  • 未校验指针有效性:find_process_info中获取的task->mm可能为NULL(比如进程处于退出阶段,或是内核线程),后续read_from_process直接访问mm->mmap会触发空指针解引用。
  • 无并发保护:遍历进程和访问mm_struct时未加锁,进程可能在遍历过程中退出,导致访问已释放的内存。
  • remap_pfn_range使用错误:该函数用于将内核物理页映射到当前进程的用户VMA,而非直接修改其他进程的VMA。直接操作目标进程的VMA会破坏其地址空间完整性,触发崩溃。

修复方案

1. 指针校验与锁保护

遍历进程时必须校验task->mm不为NULL,同时加锁保护task_struct和mm_struct的访问:

  • 使用get_task_struct获取进程引用,防止进程被释放。
  • 访问mm_struct前加mmap_sem读锁,避免并发修改。

2. 正确实现跨进程内存映射

要实现无拷贝读取其他进程内存,正确方式是:

  • 在当前进程创建新的用户VMA,将目标进程的物理页映射到这个VMA中。
  • 通过get_user_pages获取目标进程用户地址对应的物理页帧,再用remap_pfn_range映射到当前进程的VMA。

修改后的示例代码

#include <linux/sched.h>
#include <linux/mm.h>
#include <linux/vmalloc.h>
#include <linux/fs.h>
#include <linux/uaccess.h>

// 读取目标进程内存,替代原read_from_process
void read_target_process_memory(pid_t target_pid)
{
    struct task_struct *task = NULL;
    struct mm_struct *mm = NULL;
    unsigned long target_addr = 0x0; // 替换为你要读取的目标进程用户地址
    unsigned long len = 4096; // 读取长度,需按PAGE_SIZE对齐
    struct page **pages;
    int i, ret;
    unsigned long pfn;

    // 查找目标进程并获取引用
    task = pid_task(find_vpid(target_pid), PIDTYPE_PID);
    if (!task) {
        printk(KERN_ERR "Failed to find task with pid %d\n", target_pid);
        return;
    }
    get_task_struct(task);

    // 获取进程mm_struct,校验非空并加锁
    mm = get_task_mm(task);
    if (!mm) {
        printk(KERN_ERR "Task %d has no mm_struct\n", target_pid);
        put_task_struct(task);
        return;
    }
    down_read(&mm->mmap_sem);

    // 分配页指针数组
    pages = vmalloc_array(len / PAGE_SIZE, sizeof(struct page *));
    if (!pages) {
        printk(KERN_ERR "Failed to allocate pages array\n");
        goto unlock_out;
    }

    // 获取目标地址对应的物理页
    ret = get_user_pages(mm, target_addr, len / PAGE_SIZE, FOLL_FORCE, pages);
    if (ret < 0) {
        printk(KERN_ERR "get_user_pages failed with %d\n", ret);
        goto free_pages_out;
    }

    // 核心映射逻辑:在当前进程的VMA中映射物理页
    // 实际开发需通过字符设备的mmap接口实现,此处仅展示核心步骤
    for (i = 0; i < ret; i++) {
        pfn = page_to_pfn(pages[i]);
        // 假设当前进程已创建VMA,起始地址为current_vma_start
        // ret = remap_pfn_range(current_vma, current_vma_start + i*PAGE_SIZE, pfn, PAGE_SIZE, PAGE_SHARED);
        if (ret < 0) {
            printk(KERN_ERR "remap_pfn_range failed for page %d\n", i);
        }
        put_page(pages[i]);
    }

free_pages_out:
    vfree(pages);
unlock_out:
    up_read(&mm->mmap_sem);
    mmput(mm);
    put_task_struct(task);
}

// 按进程名查找并触发读取,替代原find_process_info
void find_process_by_comm(const char *comm)
{
    struct task_struct *task;
    pid_t target_pid = -1;

    for_each_process(task) {
        if (!strcmp(task->comm, comm)) {
            target_pid = task->pid;
            printk(KERN_INFO "Found %s with pid %d\n", comm, target_pid);
            break;
        }
    }

    if (target_pid != -1) {
        read_target_process_memory(target_pid);
    } else {
        printk(KERN_ERR "Process %s not found\n", comm);
    }
}

关键注意事项

  • 权限控制:跨进程内存读取需要CAP_SYS_PTRACE权限,否则get_user_pages会执行失败。
  • 页对齐:所有地址和长度必须按PAGE_SIZE对齐,否则会触发内存访问错误。
  • 资源释放:必须正确释放task_struct、mm_struct和页引用,避免内存泄漏。
  • VMA创建:实际开发中需通过字符设备或proc文件系统提供mmap接口,让用户进程发起映射请求,内核在mmap回调中完成物理页的映射。

内容的提问来源于stack exchange,提问作者EA.01

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.11 12:17:44