Android驱动中的remap_pfn_range()校验漏洞(CVE-2013-2596)

登山则情满于山,观海则意溢于海。这篇文章主要讲述Android驱动中的remap_pfn_range()校验漏洞(CVE-2013-2596)相关的知识,希望能为你提供帮助。
简单介绍当然类似函数还有io_remap_pfn_range()。
remap_pfn_range() 为用户态提供了一种手段访问内核地址空间。它通过新页表,将一块内核物理内存映射到用户态进程空间。
remap_pfn_range() 函数的原型如下:

int remap_pfn_range(struct vm_area_struct *vma, unsigned long virt_addr, unsigned long pfn, unsigned long size, pgprot_t prot);

其中
unsigned long pfn  表示映射的物理起始地址
unsigned long size  表示映射的内存大小
remap_pfn_range() 函数内部没有对这两个参数进行控制。可以想象,当pfn传入内核态物理起始地址(0xc0000000),size传入内核空间大小(1G),便可以将整个内核映射到用户态,任意访问修改。
在一种常用提权方式中,便可以利用这种能力将fsync()地址修改为shellcode地址,实现提权。
因此,在编写驱动mmap接口代码时,***一定要准确的校验remap_pfn_range()的pfn和size参数。***
一个CVE案例android_rooting_tools项目中,包含了一个这样的实例。libfb_mem_exploit工程包含了CVE-2013-2596的漏洞利用代码,这是高通图形设备驱动中的一个漏洞,由于整数溢出,导致绕过了remap_pfn_range()参数校验逻辑。
下面代码注释位置,是漏洞根源:
static int fb_mmap(struct file *file, struct vm_area_struct * vma) { struct fb_info *info = file_fb_info(file); struct fb_ops *fb; unsigned long off; unsigned long start; u32 len; if (!info) return -ENODEV; if (vma-> vm_pgoff > (~0UL > > PAGE_SHIFT)) return -EINVAL; off = vma-> vm_pgoff < < PAGE_SHIFT; fb = info-> fbops; if (!fb) return -ENODEV; mutex_lock(& info-> mm_lock); if (fb-> fb_mmap) { int res; res = fb-> fb_mmap(info, vma); mutex_unlock(& info-> mm_lock); return res; } /* frame buffer memory */ start = info-> fix.smem_start; len = PAGE_ALIGN((start & ~PAGE_MASK) + info-> fix.smem_len); if (off > = len) { /* memory mapped io */ off -= len; if (info-> var.accel_flags) { mutex_unlock(& info-> mm_lock); return -EINVAL; } start = info-> fix.mmio_start; len = PAGE_ALIGN((start & ~PAGE_MASK) + info-> fix.mmio_len); } mutex_unlock(& info-> mm_lock); start & = PAGE_MASK; /* 同时校验pfn与size参数,整数溢出将导致校验绕过 */ if ((vma-> vm_end - vma-> vm_start + off) > len) return -EINVAL; off += start; vma-> vm_pgoff = off > > PAGE_SHIFT; /* VM_IO | VM_DONTEXPAND | VM_DONTDUMP are set by io_remap_pfn_range()*/ vma-> vm_page_prot = vm_get_page_prot(vma-> vm_flags); fb_pgprotect(file, vma, off); if (io_remap_pfn_range(vma, vma-> vm_start, off > > PAGE_SHIFT, vma-> vm_end - vma-> vm_start, vma-> vm_page_prot)) return -EAGAIN; return 0; }

用户态mmap调用与fb_mmap的参数关系如下:
prot——vma-> vma_page_prot offset——vma-> vma_pgoff length——vma-> end - vma-> start

分析构造的PoC:
mapped_address = mmap((void *)MAPPED_BASE, (0x100000000 - kernel_phys_address), PROT_READ|PROT_WRITE, MAP_SHARED|MAP_FIXED, *fd, kernel_phys_address + info.smem_len);

以下是漏洞代码的校验逻辑:
start = info-> fix.smem_start; off = vma-> vm_pgoff < < PAGE_SHIFT; len = PAGE_ALIGN((start & ~PAGE_MASK) + info-> fix.smem_len); if ((vma-> vm_end - vma-> vm_start + off) > len) return -EINVAL;

vma-> vm_end - vma-> vm_start + off = (0x100000000 - kernel_phys_address) + (kernel_phys_address + info.smem_len) = 0x100000000 + info.smem_len
由于整数溢出,0x100000000 + info.smem_len = info.smem_len > len 恒不成立,即绕过参数校验。
下面代码直观感受整数溢出的效果:
[email  protected]:~/Linux_prj/PoC$ cat integer_overflow.c #include < stdio.h> #include < stdlib.h> int main () { unsigned long len = 126; unsigned long base = 0x100000000; printf ("%lu ", base+len); return 0; } [email  protected]:~/Linux_prj/PoC$ ./integer_overflow 126

安全建议用户态
  1. 设置合适的设备访问权限 (“/dev/graphics/fb0”)
  2. 配置SEAndroid 文件访问策略
【Android驱动中的remap_pfn_range()校验漏洞(CVE-2013-2596)】内核态
  1. 做好参数校验(pfn和size),尤其考虑好整数溢出导致校验逻辑绕过的问题

    推荐阅读