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中�?梢钥闯稣饫锝鼋鍪嵌阅诖嬉趁婢傩凶� ,而没有举行任何内存拷贝。


误差原理与补丁


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/