使用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
相关产品推荐
相关产品推荐

