Linux内核eBPF RINGBUF越界接见缝隙(CVE-2021-3489)利用分析

颁布功夫 2022-03-01

近年来,,,,,, ,在PWN2OWN角逐Ubuntu桌面系统破解项目中,,,,,, ,Linux内核eBPF机造一向是热点的攻击面。。。。。。。本文分析的CVE-2021-3489是在PWN2OWN 2021角逐中使用的缝隙,,,,,, ,该eBPF缝隙和以往的逻辑验证缝隙分歧,,,,,, ,缝隙呈此刻新引入的eBPF RINGBUF职能中,,,,,, ,导致内存接见越界,,,,,, ,可实现越界读写达到权限提升。。。。。。。


BPF环形缓冲区及映射


eBPF提供多种类型的映射,,,,,, ,环形缓冲区映射就是其中之一。。。。。。。该实现的动机之一是通过在CPU之间共享环形缓冲区来更有效地利用内存。。。。。。。单个RINGBUF环形缓冲区作为 BPF_MAP_TYPE_RINGBUF类型的BPF映射事俘出现给BPF法式。。。。。。�;;;;;;;;固峁┒喔鯞PF_CALL接口函数,,,,,, ,其中bpf_ringbuf_output()职能为允许将数据从一个处所复造到环形缓冲区,,,,,, ,bpf_ringbuf_reserve()/bpf_ringbuf_commit()/bpf_ringbuf_discard()这组函数将整个过程分为两个步骤。。。。。。。首先,,,,,, ,预留固定数量的空间。。。。。。。若是成功,,,,,, ,则返回指向环形缓冲区数据区域内数据的指针,,,,,, ,BPF法式能够像使用数组/哈希映射内的数据一样使用该指针。。。。。。。一旦筹备好,,,,,, ,这块内存要么被提交,,,,,, ,要么被抛弃。。。。。。。discard与commit类似。。。。。。。


在创建BPF_MAP_TYPE_RINGBUF映射时,,,,,, ,内核将分配两个内存区域。。。。。。。一个是 bpf_ringbuf_map 结构,,,,,, ,类似于其他的映射类型,,,,,, ,另一个是bpf_ringbuf结构。。。。。。。该结构界说如下图所示:


代码文件.png


其中,,,,,, ,pages是内存分配的所有页面集中,,,,,, ,consumer_pos为消费者计数器,,,,,, ,producer_pos为出产者计数器,,,,,, ,别离放在相邻的单独的页面中,,,,,, ,在该缝隙建复前,,,,,, ,这两个页面均能够通过MMAP映射到用户空间进行读写操作的。。。。。。。bpf_ringbuf_alloc()函数是实现bpf_ringbuf并初始化的,,,,,, ,实现代码如下所示:


代码文件.png


挪用bpf_ringbuf_area_alloc()函数分配bpf_ringbuf,,,,,, ,而后初始化rb->spinlock,,,,,, ,rb->waitq和rb->work,,,,,, ,最后设置rb->mask,,,,,, ,rb->consumer_pos和rb->producer_pos。。。。。。。bpf_ringbuf_area_alloc()函数是用来具体分配ringbuf内存区域的,,,,,, ,该实现如下代码所示:


代码文件.png


第一个参数data_sz为申请分配内存的大�。。。。。。。�,,,,, ,nr_meta_pages为元数据页面数,,,,,, ,蕴含一个不成映射页面和两个可映射页面,,,,,, ,别离为consumer_pos和producer_pos,,,,,, ,nr_data_pages为现实申请分配内存所需的内存页面数,,,,,, ,nr_pages为nr_meta_pages和nr_data_pages之和,,,,,, ,pages用于存放所有页面集中,,,,,, ,内存分配如下代码所示:


代码文件.png

挪用bpf_map_area_alloc()函数分配pages指针数组,,,,,, ,用于存放即将分配的内存页面。。。。。。。而后循环挪用alloc_pages_node()函数分配页面并存放在pages中,,,,,, ,把稳到nr_data_pages是双份的。。。。。。。现实内存布局如下所示:


示例图.png

最后,,,,,, ,挪用vmmap()函数将pages中的页面映射到陆续虚构内存空间中,,,,,, ,如下代码所示:


代码文件.png

缝隙道理与建复补丁


该缝隙产生在__bpf_ringbuf_reserve()函数中,,,,,, ,该函数能够返回指向环形缓冲区数据区域内数据的指针,,,,,, ,但是并没有判断接见长度大�。。。。。。。�,,,,, ,导致能够越界接见数据。。。。。。。该函数关键实现如下代码所示:


代码文件.png


参数size为接见长度,,,,,, ,首先判断size是否大于0x3fffffff,,,,,, ,但是并没有判断len是否大于ringbuf的data_sz,,,,,, ,即接见的领域是否大于现实分配的ringbuf内存领域。。。。。。。而后对size+8上限取整为len,,,,,, ,接下来取出rb->producer_pos,,,,,, ,通过prod_pos+len推算出new_prod_pos。。。。。。。


代码文件.png


行333,,,,,, ,首先判断新的出产者地位不超过ringbuf的data_sz-1,,,,,, ,确保ringbuf内存空间是充足的。。。。。。。而后通过rb->data+prod_pos推算出hdr的地位,,,,,, ,最后将rb->producer_pos更新为new_prod_pos,,,,,, ,返回hdr+8地位的指针,,,,,, ,如下代码所示:


代码文件.png


凭据前文分析,,,,,, ,rb->consumer_pos和rb->producer_pos地点页面是可映射的,,,,,, ,是可控的且没有查抄,,,,,, ,size接见长度也是可控的,,,,,, ,因而能够机关如下前提达到大领域越界接见,,,,,, ,令producer_pos = 0,,,,,, ,consumer_pos= 0x3fffffff和size=0x3fffffff。。。。。。。这三个变量能够绕过所有查抄,,,,,, ,最后推算出的new_prod_pos为0x3fffffff+8,,,,,, ,这是个很大的领域。。。。。。。


该缝隙建复补丁有两部门,,,,,, ,第一部门是加上了和data_sz大幼的判断,,,,,, ,预防接见长度超涌现实分配的空间领域,,,,,, ,如下代码所示:


代码文件.png


不允许对rb->producer_pos地点内存页面进行写映射。。。。。。。


缝隙利用过程


(1)通过堆喷机关陆续内存布局,,,,,, ,给越界读写提供场景

陆续创建多个size=0x1000000的ringbuf,,,,,, ,这里mapfd的ringbuf和victimfd的ringbuf是陆续的,,,,,, ,中央距离一个页面的guard page


代码文件.png

(2)通过eBPF指令机关出越界读写原语

通过eBPF指令接见mapfd的ringbuf,,,,,, ,并挪用bpf_ringbuf_reserve()函数获取mapfd的ringbuf->data指针。。。。。。。这里SIZE为0x30000000大于现实分配的内存空间。。。。。。。


代码文件.png


偏移size*2+0x1000跳过mapfd的ringbuf,,,,,, ,再偏移8处是victimfd的ringbuf->wait_queue_head->list_head,,,,,, ,偏移40处是ringbuf-> irq_work->func,,,,,, ,初始化bpf_ringbuf时,,,,,, ,func为bpf_ringbuf_notify,,,,,, ,因而能够推算出内核基地址。。。。。。。


(4)劫持返回地址执行代码

通过大量fork子过程,,,,,, ,在victimfd内存后面得到陆续的task_struct内存布局。。。。。。。


代码文件.png


同时,,,,,, ,子过程和父过程通过pipe进行通讯,,,,,, ,这个过程中会挪用__x64_sys_read和ksys_read函数并处于阻塞状态,,,,,, ,而后不休搜索thread内核栈,,,,,, ,搜索到这两个函数返回地址将其批改成commit_creds和prepare_kernel_cred,,,,,, ,在父过程中解除阻塞状态便可劫持流程进行执行肆意代码。。。。。。。      


综上利用过程,,,,,, ,通过精心机关可实现对该缝隙的提权成效。。。。。。。


        

参考链接: 

https://flatt.tech/reports/210401_pwn2own/