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

Posted gm-201705

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了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 文件访问策略

内核态

  1. 做好参数校验(pfn和size),尤其考虑好整数溢出导致校验逻辑绕过的问题

以上是关于Android驱动中的remap_pfn_range()校验漏洞(CVE-2013-2596)的主要内容,如果未能解决你的问题,请参考以下文章

内存映射函数remap_pfn_range学习——示例分析

内存映射函数remap_pfn_range学习——示例分析

内存映射函数remap_pfn_range学习——代码分析

android_rooting_tools 项目介绍(CVE-2012-4220)

Binder 机制分析 Android 内核源码中的 Binder 驱动源码 binder.c ( googlesource 中的 Android 内核源码 | 内核源码下载 )

Android 中的 postgres JDBC 驱动程序