Linux内核权限提升缝隙“DirtyPipe”(CVE-2022-0847)分析

颁布功夫 2022-03-14

缝隙详情


近日,钻研人员披露了一个Linux内核本地权限提升缝隙,发此刻copy_page_to_iter_pipe和 push_pipe函数中,新分配的pipe_buffer结构体成员“flags”未被正确地初始化,可能蕴含旧值PIPE_BUF_FLAG_CAN_MERGE。攻击者可利用此缝隙向由只读文件支持的页面缓存中的页面写入数据,从而提升权限。该缝隙编号为CVE-2022-0847,因缝隙类型和“DirtyCow”(脏牛)类似,亦称为“DirtyPipe”。



有关系统挪用实现


2.1 pipe系统挪用实现


挪用pipe()创建一个管路,返回两个文件描述符,fd[1]为读,fd[2]为写。这里以linux-5.16.10内核代码为例,挪用到__do_pipe_flags()函数,该函数代码实现如下:


代码文件.png


首先挪用create_pipe_files(),而后挪用get_unused_fd_flags()别离获取未使用的文件描述符fdr和fdw,并写入到指针fd中。create_pipe_files()函数挪用get_pipe_inode()函数获取一个inode,并初始化有关数据结构。get_pipe_inode()函数又挪用alloc_pipe_info()函数分配一个pipe_inode_info,该结构体是一个内核pipe结构体,用于管路的治理和操作。具体看下alloc_pipe_info()函数,该函数实现代码如下:


代码文件.png


而后起头分配pipe->bufs,正常一次性分配16个pipe_buffer,而后初始化pipe的有关成员,这里并不会初始化pipe_bufs中的pipe_buffer。piper_buffer结构体界说如下:


代码文件.png

首先从pipe->head起头,判断pipe是否为满的。不满的情况下,拿出一个pipe_buffer,判断page是否已分配,未分配随即分配一个新page,而后初始化这个pipe_buffer有关成员,实现代码如下:


代码文件.png


代码文件.png


代码文件.png


分三种情况,第一种为in/out均为pipe类型,第二种是in为pipe类型,第三种是out为pipe类型,这里我们分析第三种情况。挪用spilce_file_tp_pipe()函数将数据写入pipe中,具体味挪用到generic_file_splice_read()函数,这里以linux-2.6.17内核版本为例,更容易理解零拷贝过程。该函数实现如下:


代码文件.png


代码文件.png

首先获取in->f_mapping,该结构体是用于治理文件(struct inode)映射到内存的页面(structpage),其实就是每个file都有这么一个结构,将文件系统中这个file对应的数据与这个file对应的内存绑定到一路。而后界说一个splice_pipe_desc结构体,该结构体用于中转file对应的内存页。接下来就是将file对应的内存页面整顿放在spd中,过程比力复杂,略过。最后挪用splice_to_pipe()函数操作pipe和spd,该函数实现关键代码如下所示:


代码文件.png


顺次循环地从spd->pages中取出内存页放在对应的buf->page中� D芄豢闯稣饫锝鼋鍪嵌阅诖嬉趁娼凶�,而没有进行任何内存拷贝。


缝隙道理与补丁


3.1 缝隙道理

在linux-5.16.10内核中,挪用splice()函数将数据写入管路时,挪用蹊径如下所示:


代码文件.png


如前文所述,从pipe中取出buf,只是代替了ops,page,offset和len,并没有批改buf->flags,因而该buffer所蕴含的页面是能够归并的。当再次向管路中写入数据时,由于pipe非初次使用,首先判断要写入的buffer类型,若是buf->flags为PIPE_BUF_FLAG_CAN_MERGE,行466,直接挪用copy_page_from_iter()函数进行内存拷贝,而主张地址为buf->page,这个buf->page现实上就是来自file中对应的内存页面。


代码文件.png


该缝隙补丁在copy_page_to_iter_pipe()函数和push_pipe()函数中,将buf->flags置零。其中push_pipe()函数可在其他蹊径中触发,不再赘述。


代码文件.png


利用分析


首先,挪用pipe创建管路并通过写读操作将管路中的buffer类型设置为PIPE_BUF_FLAG_CAN_MERGE。


代码文件.png


触发缝隙后,此时pipe中buf所蕴含的内存页面均是指向/usr/bin/pkexec文件所属的内存页面,并且内存页面都是能够归并的。最后再次挪用write()函数将提权payload写入pipe中,即写入/usr/bin/pkexec文件中,而后运行/usr/bin/pkexec提升权限。


参考链接:


[1]https://dirtypipe.cm4all.com/


[2]https://haxx.in/files/dirtypipez.c


[3]https://lore.kernel.org/lkml/20220221100313.1504449-1-max.kellermann@ionos.com/