[原创]cve-2016-3842分析
static const struct {
unsigned int cmd;
kgsl_ioctl_func_t func;
unsigned int flags;
} kgsl_ioctl_funcs[] = {
KGSL_IOCTL_FUNC(IOCTL_KGSL_DEVICE_GETPROPERTY,
kgsl_ioctl_device_getproperty,
KGSL_IOCTL_LOCK),
KGSL_IOCTL_FUNC(IOCTL_KGSL_DEVICE_WAITTIMESTAMP,
kgsl_ioctl_device_waittimestamp,
KGSL_IOCTL_LOCK),
KGSL_IOCTL_FUNC(IOCTL_KGSL_DEVICE_WAITTIMESTAMP_CTXTID,
kgsl_ioctl_device_waittimestamp_ctxtid,
KGSL_IOCTL_LOCK),
KGSL_IOCTL_FUNC(IOCTL_KGSL_RINGBUFFER_ISSUEIBCMDS,
kgsl_ioctl_rb_issueibcmds, 0),
KGSL_IOCTL_FUNC(IOCTL_KGSL_SUBMIT_COMMANDS,
kgsl_ioctl_submit_commands, 0),
KGSL_IOCTL_FUNC(IOCTL_KGSL_CMDSTREAM_READTIMESTAMP,
kgsl_ioctl_cmdstream_readtimestamp,
KGSL_IOCTL_LOCK),
KGSL_IOCTL_FUNC(IOCTL_KGSL_CMDSTREAM_READTIMESTAMP_CTXTID,
kgsl_ioctl_cmdstream_readtimestamp_ctxtid,
KGSL_IOCTL_LOCK),
KGSL_IOCTL_FUNC(IOCTL_KGSL_CMDSTREAM_FREEMEMONTIMESTAMP,
kgsl_ioctl_cmdstream_freememontimestamp,
KGSL_IOCTL_LOCK),
KGSL_IOCTL_FUNC(IOCTL_KGSL_CMDSTREAM_FREEMEMONTIMESTAMP_CTXTID,
kgsl_ioctl_cmdstream_freememontimestamp_ctxtid,
KGSL_IOCTL_LOCK),
KGSL_IOCTL_FUNC(IOCTL_KGSL_DRAWCTXT_CREATE,
kgsl_ioctl_drawctxt_create,
KGSL_IOCTL_LOCK),
KGSL_IOCTL_FUNC(IOCTL_KGSL_DRAWCTXT_DESTROY,
kgsl_ioctl_drawctxt_destroy,
KGSL_IOCTL_LOCK),
KGSL_IOCTL_FUNC(IOCTL_KGSL_MAP_USER_MEM,
kgsl_ioctl_map_user_mem, 0),
KGSL_IOCTL_FUNC(IOCTL_KGSL_SHAREDMEM_FROM_PMEM,
kgsl_ioctl_map_user_mem, 0),
KGSL_IOCTL_FUNC(IOCTL_KGSL_SHAREDMEM_FREE,
kgsl_ioctl_sharedmem_free, 0),
KGSL_IOCTL_FUNC(IOCTL_KGSL_SHAREDMEM_FLUSH_CACHE,
kgsl_ioctl_sharedmem_flush_cache, 0),
KGSL_IOCTL_FUNC(IOCTL_KGSL_GPUMEM_ALLOC,
kgsl_ioctl_gpumem_alloc, 0),
KGSL_IOCTL_FUNC(IOCTL_KGSL_CFF_SYNCMEM,
kgsl_ioctl_cff_syncmem, 0),
KGSL_IOCTL_FUNC(IOCTL_KGSL_CFF_USER_EVENT,
kgsl_ioctl_cff_user_event, 0),
KGSL_IOCTL_FUNC(IOCTL_KGSL_TIMESTAMP_EVENT,
kgsl_ioctl_timestamp_event,
KGSL_IOCTL_LOCK),
KGSL_IOCTL_FUNC(IOCTL_KGSL_SETPROPERTY,
kgsl_ioctl_device_setproperty,
KGSL_IOCTL_LOCK),
KGSL_IOCTL_FUNC(IOCTL_KGSL_GPUMEM_ALLOC_ID,
kgsl_ioctl_gpumem_alloc_id, 0),
KGSL_IOCTL_FUNC(IOCTL_KGSL_GPUMEM_FREE_ID,
kgsl_ioctl_gpumem_free_id, 0),
KGSL_IOCTL_FUNC(IOCTL_KGSL_GPUMEM_GET_INFO,
kgsl_ioctl_gpumem_get_info, 0),
KGSL_IOCTL_FUNC(IOCTL_KGSL_GPUMEM_SYNC_CACHE,
kgsl_ioctl_gpumem_sync_cache, 0),
KGSL_IOCTL_FUNC(IOCTL_KGSL_GPUMEM_SYNC_CACHE_BULK,
kgsl_ioctl_gpumem_sync_cache_bulk, 0),
};
1.首先,我们来看漏洞的形成原因。
$1
漏洞产生代码 和 利用方案主要都在 都在kgsl_ioctl_gpumem_alloc_id函数 里面
首先,我们先看看漏洞产生的函数.
result = kgsl_mem_entry_attach_process(entry, dev_priv); // 这个函数产生了漏洞
现在我们来看看这里面都有些什么内容。
static int
kgsl_mem_entry_attach_process(struct kgsl_mem_entry *entry,
struct kgsl_device_private *dev_priv)
{
... 这里添加到了idr中
id = idr_alloc(&process->mem_idr, entry, 1, 0, GFP_NOWAIT);
...
在调用 kgsl_mmu_map 之前 把entry释放掉 造成UAF?
ret = kgsl_mmu_map(pagetable, &entry->memdesc);
...
}
上面是我打印出来的几个和漏洞相关的重要函数
首先是第一个 漏洞参数主要在这个函数内。 首先来看看 idr_alloc
这个函数 主要是给管理对象,会给对象分配一个编号,然后我们可以再次通过这个编号找到要管理的对象。
重要的是这个编号是按照顺序排序的。第一个对方存放进去的时候 返回的 ID 将会是1 这个很重要。销毁对象的时候我们将会用到
我们再来看看销毁对象是怎么做的。
销毁对象的方式有很多,但是其中有一个 IOCTL_KGSL_GPUMEM_FREE_ID 他是根据idr 编号找到对象,然后进行销毁。
释放内存 造成漏洞形成
ioctl(fd,IOCTL_KGSL_GPUMEM_FREE_ID, &arg_free);
通过调用路径我们找到 最终会调用下面的这个函数来销毁对象 ,我们再来具体分析
static long kgsl_ioctl_gpumem_free_id(struct kgsl_device_private *dev_priv,
unsigned int cmd, void *data)
{
struct kgsl_gpumem_free_id *param = data;
struct kgsl_process_private *private = dev_priv->process_priv;
struct kgsl_mem_entry *entry = NULL;
entry = kgsl_sharedmem_find_id(private, param->id);
...
/*
* First kgsl_mem_entry_put is for the reference that we took in
* this function when calling kgsl_sharedmem_find_id, second one is
* to free the memory since this is a free ioctl
*/
kgsl_mem_entry_put(entry);
return 0;
}
entry = kgsl_sharedmem_find_id(private, param->id);注意这个函数
他是根据ID 来获取到对象的,而我们已经知道idr_alloc的编号是按顺序发放的
struct kgsl_gpumem_free_id *param = data; 中的data 使我们从用户空间传入的
那么就是说 param->id 我们可以直接赋值 1的话,他会返回我们kgsl_mem_entry_attach_process函数中
管理的entry对象 最后销毁entry对象
这个调用过程,单线程跑的话。是不会有问题的。
但是如果我们假设用双线程跑到某种特殊的情况下又会是怎么样呢?
现在我们来假设一下情况。
首先第一个线程
kgsl_mem_entry_attach_process(struct kgsl_mem_entry *entry,
struct kgsl_device_private *dev_priv)
{
...
id = idr_alloc(&process->mem_idr, entry, 1, 0, GFP_NOWAIT);
线程1:跑到这里,然后创建了ID,保存了对象。 ID ret 1
这时候 cpu切换到线程2,开始执行线程2的代码
...
}
第二个线程
static long kgsl_ioctl_gpumem_free_id(struct kgsl_device_private *dev_priv,
unsigned int cmd, void *data)
{
entry = kgsl_sharedmem_find_id(private, param->id);
kgsl_mem_entry_put(entry);
线程2:执行完这个函数。根据ID 找到了 entry 对象,最后然后释放了entry
}
然后cpu切换到线程1再继续往下执行的话。我们发现 线程1所用的 entry 对象已经被线程2释放掉了。
这样漏洞就被触发了.
2,漏洞利用
刚刚我们弄清了漏洞触发的原理,那接下来,就得说说如何利用漏洞提权了。
首先如何利用漏洞提权,我们先要知道 entry 对象 是怎么样来的。
kgsl_ioctl_gpumem_alloc(struct kgsl_device_private *dev_priv,
unsigned int cmd, void *data)
{
struct kgsl_process_private *private = dev_priv->process_priv;
struct kgsl_gpumem_alloc *param = data;
struct kgsl_mem_entry *entry = NULL;
int result;
param->flags &= ~KGSL_MEMFLAGS_USE_CPU_MAP;
result = _gpumem_alloc(dev_priv, &entry, param->size, param->flags);
if (result)
return result;
result = kgsl_mem_entry_attach_process(entry, dev_priv); //这个是漏洞函数
if (result != 0)
goto err;
kgsl_process_add_stats(private, entry->memtype, param->size);
trace_kgsl_mem_alloc(entry);
param->gpuaddr = entry->memdesc.gpuaddr;
param->size = entry->memdesc.size;
param->flags = entry->memdesc.flags;
return result;
err:
kgsl_sharedmem_free(&entry->memdesc);
kfree(entry);
return result;
}
从代码可以看到 kgsl_mem_entry_attach_process 是漏洞函数
而他的参数 entry 是从 _gpumem_alloc 中得到的
展开后发现,里面有一句代码
entry = kgsl_mem_entry_create();
static inline struct kgsl_mem_entry *
kgsl_mem_entry_create(void)
{
struct kgsl_mem_entry *entry = kzalloc(sizeof(*entry), GFP_KERNEL);
if (!entry)
KGSL_CORE_ERR("kzalloc(%d) failed\n", sizeof(*entry));
else
kref_init(&entry->refcount);
return entry;
}
我们发现 最终 entry 是通过 kzalloc 来申请内存的
内存分配算法什么的就不细说了,本人也不是太熟悉。
但是我们要了解到几个关键信息。
1.申请的空间,会根据申请的大小,来确定返回某段内存.
2.释放后的空间,是会被重复利用的。(也就是说我们再次申请内存的话,是很有可能申请到刚刚释放的那一段内存)
知道了这个内存的机制。那么我们接下来就要找到一个系统调用函数,可以申请到这片空间。
而这个函数必须要满足几个条件
{
1,申请的大小 必须和 entry 差不多。
2,内容必须是可控的,这样才能让程序完全执行,不会崩溃
}
经过一段时间的找寻,终于找到了一个函数。pipe
可以通过write来进行写入
再来漏洞利用的完整逻辑就是这样的
首先第一个线程
kgsl_mem_entry_attach_process(struct kgsl_mem_entry *entry,
struct kgsl_device_private *dev_priv)
{
...
id = idr_alloc(&process->mem_idr, entry, 1, 0, GFP_NOWAIT);
//线程1:创建了ID,保存 entry 对象。 索引 == 1
//这时候 cpu切换到线程2,开始执行线程2的代码
...
...
if (entry->memdesc.gpuaddr) {
//如果线程2到这里已经执行完毕了的话,那么 entry 的内容已经被改写 data
// ret 将会返回错误值
ret = kgsl_mmu_map(process->pagetable, &entry->memdesc);
if (ret)
kgsl_mem_entry_detach_process(entry);
}
return ret;
...
}
第二个线程
ioctl(fd,IOCTL_KGSL_GPUMEM_FREE_ID, &arg_free);
// arg_free.id = 1 释放掉了entry 内存。
write(pipe[1],data,XX);
//再用pipe 写入
// entry 已经变成我们改写的内容 data 了
cpu继续切换到线程1
当 kgsl_mem_entry_attach_process 返回错误后
static long
kgsl_ioctl_gpumem_alloc(struct kgsl_device_private *dev_priv,
unsigned int cmd, void *data)
{
...
result = kgsl_mem_entry_attach_process(entry, dev_priv); // 返回错误 跳转到err: 执行
if (result != 0)
goto err;
...
err:
kgsl_sharedmem_free(&entry->memdesc);
kfree(entry);
return result;
}
因为ret 返回错误 代码会跳转到 err 执行
void kgsl_sharedmem_free(struct kgsl_memdesc *memdesc)
{
if (memdesc == NULL || memdesc->size == 0)
return;
if (memdesc->gpuaddr) {
kgsl_mmu_unmap(memdesc->pagetable, memdesc);
kgsl_mmu_put_gpuaddr(memdesc->pagetable, memdesc);
}
if (memdesc->ops && memdesc->opsza?e ->free)
memdesc->ops->free(memdesc);
//entry 已经被修改成data的内容
// &entry->memdesc->ops->free(memdesc) 这个函数地址我们是可以控制的了.
kgsl_sg_free(memdesc->sg, memdesc->sglen_alloc);
memset(memdesc, 0, sizeof(*memdesc));
}
这样 &entry->memdesc->ops->free 设置成自己的函数地址后。就可以进行提权操作了。
但是测试之后发现机器还是会崩溃。经过调试发现
kgsl_sharedmem_free(&entry->memdesc);
kfree(entry);
导致崩溃的原因是因为 kfree(entry);
猜测应该是因为entry 已经被销毁了,然后这里再执行了一次,从而导致了崩溃。
这样的话。我们可以在 &entry->memdesc->ops->free 中 通过栈,找到保存了kgsl_ioctl_gpumem_alloc上级函数的返回地址,直接返回
跳过 kfree(entry),这一句指令的调用,即可避免崩溃
这样整个漏洞利用提权思路分析完毕。
