用户态缺页处理
iamorange 二进制安全 119浏览 · 2025-03-16 12:01

用户态缺页处理

本文档阐述了,如何利用 Linux 的 userfaultfd 机制,在用户空间处理缺页异常(page fault)。

1. 整体概述

代码主要流程如下:

1 内存映射 main() 中使用 mmap 分配了 2 页内存(每页 4KB)。

2 注册 userfaultfd 调用 register_uffd() 将映射区域注册到 userfaultfd,使得对这些区域的访问触发缺页异常。

3 缺页处理线程 启动一个专门处理缺页事件的线程(fault_handler_thread),该线程通过 poll()read() 捕获缺页事件,然后利用 UFFDIO_COPY 将预先准备的数据填充到缺页区域。

4 触发缺页 main() 中通过读取映射内存触发缺页事件,最终观察到填充的数据。

2. main() 函数解析

主要步骤

内存映射 使用 mmap 分配了 0x2000 字节(2 页)的内存,设置了读写权限。映射类型为匿名且私有。

打印映射地址 打印分配内存的起始地址,便于调试。

注册 userfaultfd 调用 register_uffd(page, 0x2000) 将分配的 2 页内存区域注册到 userfaultfd。

触发缺页 通过对映射区域内存的读取(使用 strcpy 拷贝数据到缓冲区),第一次访问将触发缺页异常。代码中读取两次每个页面,借此观察缺页处理线程的填充行为(根据缺页次数返回不同的字符串)。

暂停程序 使用 getchar() 阻塞程序退出,以便观察程序运行过程中的输出。

3. register_uffd() 函数解析

主要步骤

创建 userfaultfd 通过 syscall(__NR_userfaultfd, O_CLOEXEC | O_NONBLOCK) 创建一个 userfaultfd 文件描述符,设置了 O_CLOEXEC(子进程不继承)和 O_NONBLOCK(非阻塞模式)。

初始化 API 使用 ioctl(uffd, UFFDIO_API, &uffdio_api) 初始化 userfaultfd,设置 API 版本为 UFFD_API,不启用额外特性(features = 0)。

注册内存区域 设置 uffdio_register 结构体,指定要监控的内存区域起始地址和长度,模式设置为 UFFDIO_REGISTER_MODE_MISSING(只对缺页页面进行处理)。调用 ioctl 将内存区域注册到 userfaultfd。

启动缺页处理线程 使用 pthread_create 创建一个新的线程,线程函数为 fault_handler_thread,并将 userfaultfd 文件描述符作为参数传递给线程。

4. fault_handler_thread() 函数解析

主要步骤

1线程初始化

接收参数,将 arg 转换为 userfaultfd 文件描述符。

定义相关变量:dummy_page 用于存放数据,msg 用于接收缺页事件信息,copy 用于 UFFDIO_COPY 操作,pollfd 用于事件监听,fault_cnt 用于统计缺页次数。

1分配临时页面

使用 mmap 分配一个大小为 0x1000(4KB)的临时页面 dummy_page,用于在缺页事件时填充数据。

1等待缺页事件

设置 pollfd 监听 userfaultfd 文件描述符的可读事件(POLLIN)。

进入 while 循环,使用 poll() 阻塞等待缺页事件。

1处理缺页事件

poll() 返回后,通过 read() 读取 userfaultfd 的事件消息,并使用 assert 确认事件类型为 UFFD_EVENT_PAGEFAULT

打印出缺页的标志和地址信息。

1填充数据

根据 fault_cnt判断缺页次数:

第一次填充 "Hello, world! (1)"

后续填充 "Hello, world! (2)"

将数据写入 dummy_page

1复制数据到缺页区域

配置 copy结构体:

copy.src:设置为 dummy_page 的地址(数据来源)。

copy.dst:将缺页地址按页对齐(使用 & ~0xfff)。

copy.len:复制 0x1000 字节(即 4KB,一整页)。

使用 ioctl(uffd, UFFDIO_COPY, &copy)dummy_page 中的数据复制到触发缺页的内存页面,从而完成缺页处理。

1退出线程

当没有更多缺页事件时,线程将退出并返回 NULL

5. 总结

初始化与注册

main() 中调用 mmap 分配了 2 页内存,并通过 register_uffd() 注册到 userfaultfd。

缺页事件处理

注册后,对内存的读取触发缺页,缺页处理线程 fault_handler_thread() 捕获并响应,通过 UFFDIO_COPY 将预设数据写入缺页区域。

效果验证

通过重复读取内存页面,可以观察到缺页处理线程根据缺页次数填充了不同的字符串数据,从而验证 userfaultfd 机制的工作效果。

这种机制常用于延迟加载数据、实现用户态内存管理和内存调试等场景。上述的处理函数中,穿插的我们自己的处理代码,就可以帮助实现条件竞争。

poc

参考链接:

https://blog.wohin.me/posts/pawnyable-0303/

2 条评论
某人
表情
可输入 255
iamorange
2025-03-18 12:08 0 回复
<sript>1</sript>
Bpple
2025-03-18 08:19 0 回复
写的很好