写点什么

Linux 内核权限提升漏洞

  • 2022 年 3 月 30 日
  • 本文字数:2276 字

    阅读完需:约 7 分钟

一、漏洞详情

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


*严正声明:本文仅限于技术讨论与分享,严禁用于非法途径。

二、相关系统调用实现

(一)pipe 系统调用实现

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



首先调用 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()函数,该函数实现代码如下:



【一>所有资源获取<一】1、网络安全学习路线 2、电子书籍(白帽子)3、安全大厂内部视频 4、100 份 src 文档 5、常见安全面试题 6、ctf 大赛经典题目解析 7、全套工具包 8、应急响应笔记


首先初始化 pipe_bufs 为 PIPE_DEF_BUFFERS,该值为 16,然后分配 pipe,接着判断 pipe_bufs*PAGE_SIZE 的大小,pipe_bufs 最大值为 128,最小值为 2。



然后开始分配 pipe->bufs,正常一次性分配 16 个 pipe_buffer,然后初始化 pipe 的相关成员,这里并不会初始化 pipe_bufs 中的 pipe_buffer。piper_buffer 结构体定义如下:



其中 page 用于存放数据,大小为一个页面,ops 为对应内存页面操作集,成员 flags 为 buffer 类型。这 16 个 pipe_buffer 构成一个管道缓冲区的循环数组,pipe->head 指向缓冲区生产点,pipe->tail 指向消费点,在 pipe 的管理下,循环地用于数据的读取和写入。


当向管道中写入数据时,会调用 pipe_write()函数,该函数部分实现代码如下:



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



行 527,将 buf->flags 设置为 PIPE_BUF_FLAG_CAN_MERGE,表示该 buffer 是可以合并的。最后调用 copy_page_from_iter()函数将数据拷贝到新分配的 page 中。当从管道中读取数据时,就是逆过程,其间并不改变既定 buffer 的页面类型,不再赘述。

(二)splice 系统调用实现

splice 是 Linux 2.6.17 新加入的系统调用,用于在两个文件间移动数据,而无需内核态和用户态的内存拷贝,但需要借助管道(pipe)实现。大概原理就是通过 pipe buffer 实现一组内核内存页(pages of kernel memory)的引用计数指针(reference-counted pointers),数据拷贝过程中并不真正拷贝数据,而是创建一个新的指向内存页的指针。也就是说拷贝过程实质是指针的拷贝,称为零拷贝技术。


调用 splice 系统调用时,内核中会调用 do_splice()函数,该函数实现代码如下:



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



然后调用到__generic_file_splice_read()函数,该函数实现代码如下:



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



依次循环地从 spd->pages 中取出内存页放在对应的 buf->page 中。可以看出这里仅仅是对内存页面进行转移,而没有进行任何内存拷贝。

三、漏洞原理与补丁

(一)漏洞原理

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



比 linux-2.6.17 内核版本的复杂,最终会调用 copy_page_to_iter_pipe()函数操作内存页面,该函数实现代码如下:



如前文所述,从 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 中对应的内存页面。


(二)补丁

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


四、利用分析

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



然后将要覆盖的文件通过 splice 写入到 pipe 中,公开的利用中被覆盖的文件为/usr/bin/pkexec,因为该程序具备 suid 能力。



触发漏洞后,此时 pipe 中 buf 所包含的内存页面均是指向/usr/bin/pkexec 文件所属的内存页面,而且内存页面都是可以合并的。最后再次调用 write()函数将提权 payload 写入 pipe 中,即写入/usr/bin/pkexec 文件中,然后运行/usr/bin/pkexec 提升权限。

用户头像

我是一名网络安全渗透师 2021.06.18 加入

关注我,后续将会带来更多精选作品,需要资料+wx:mengmengji08

评论

发布
暂无评论
Linux内核权限提升漏洞_网络安全_网络安全学海_InfoQ写作平台