CVE-2022-0847 Linux内核漏洞简要分析

0x00 背景

2022年03月07日,国外安全研究员披露了一个Linux内核本地提权漏洞CVE-2022-0847,命名为“Dirtypipe”。攻击者通过利用此漏洞可进行任意可读文件重写,将普通权限用户提升到root权限。目前网上已有公开的漏洞利用工具PoC,但目前还未有对容器环境影响分析,于是就有了这篇文章。

0x01 漏洞影响

目前来看,漏洞对容器环境影响有限。受影响的内核版本为5.8及以后,而且已经于Linux 5.16.11、5.15.25 和 5.10.102 中修复。漏洞可以用作本地提权,但是本身并不能持久化,重启后所有影响将会消失。由于漏洞本身影响的并非本地文件而是页面缓存,容器环境中页面缓存受到namespace隔离影响,无法直接影响到host环境,而一部分特殊文件访问,由于访问时不经过页面缓存,因此也不受到此漏洞影响。此漏洞是否造成和“脏牛”一样的容器逃逸,还有待进一步研究。

0x02 背景知识

CPU 管理的最小内存单位是页。Linux 内存管理是基于页的。当应用程序向内核请求内存,它获得的实际上是许多内存页。
文件 I/O 也与页有关:当从文件中读取数据时,内核首先将文件分成与内存页等大小的块,然后从硬盘复制到内核内存中,这一工作由名为页面缓存的子系统管理,然后数据再从页面缓存里通过mmap()调用直接映射到用户空间。直到内核决定回收内存之前,页面缓存中的文件副本会一直存在。通过使用页面缓存可以避免不必要的硬盘 I/O,提高文件系统的效率。
Splice()调用在两个文件描述符之间移动数据,而不需要数据在内核空间和用户空间来回拷贝,但是有两个文件描述符中有一个必须是管道设备。
管道是一种用于单向进程间通信的工具。一端用于将数据推送到其中,另一端可以提取该数据。Linux 内核中通过将缓冲区(通常大小等于一个内存页)放入一个环结构来实现。“匿名管道”第一次写入管道时,内核会分配一个页用于数据存放,如果最近的一次写入没有完全填满页面,则后续写入可能会附加到这个现有的页而不是分配新的页。
当使用splice()将数据从一个文件导入到管道中时,内核会首先将数据加载到页面缓存中,然后它将创建一个管道缓冲区,但与匿名管道缓冲区不同,写入管道的数据不能附加到这类页面,因为这类页并不属于管道,而是由页面缓存所有。
对于能否将数据附加至一个管道缓冲区,内核采用了如下的机制:
• 很久以前,pipe_buf_operations结构有一个单独的flag叫做can_merge。
• Linux 2.6.16起,为了支持splice调用,引入了 page_cache_pipe_buf_ops,它实际上是一个设置了can_merge=0的pipe_buf_operations,用来指示这部分页是不能合并的。
图片1.png
• Linux 5.0中,由于只有一种管道缓冲区类型可以追加新数据,can_merge的检查被修改为只检查类型是否是anon_pipe_buf_ops(这就是那个唯一可追加内容的类型)。
图片2.png
图片3.png
• Linux 5.8中又将pipe_buf_operations类型的比较修改为pipe_buffer 的一个flag——PIPE_BUF_FLAG_CAN_MERGE。
图片4.png

0x03 未初始化

在PIPE_BUF_FLAG_CAN_MERGE这个flag诞生之前很多很多年,Linux 4.9添加了两个新函数,它们分配了新的管道缓冲区,但并没有初始化flag。这使得可以使用任意flag创建页面缓存引用,但这并不是什么大问题,因为当时所有的flag都没有什么用处。
图片5.png
图片6.png
图片7.png
图片8.png
(可以看到,两个函数都没有初始化flag)
然而到了Linux 5.8,这个问题一下子就严重起来。通过注入PIPE_BUF_FLAG_CAN_MERGE标记到页面缓存,当新数据写入到以特殊方式初始化的管道中时将可以覆盖页面缓存中的数据。

0x04 利用

漏洞的本质是任意文件的页面缓存覆盖,但是有一些限制: * 攻击者必须具有读取权限(因为它需要使用 splice()将页放入管道) * 文件偏移量不能在页面边界上(因为页面上的至少一个字节必须拼接到管道中) * 写入不能跨越页面边界(因为内核将为其余部分创建一个新的匿名缓冲区) * 文件大小无法修改(因为管道有自己的页面管理器,并且不会告诉页面缓存已经写入了多少数据)
漏洞利用的步骤:
1. 创建管道。
图片9.png
2. 用任意数据填充管道( 为整个缓冲区环结构设置PIPE_BUF_FLAG_CAN_MERGE标记)。
图片10.png
3. 清空管道(保留pipe_inode_info环中每一个缓冲区的flag )。
图片11.png
4. 使用 splice()将目标文件(以只读方式打开)中的数据从目标偏移之前的位置放入到管道中。
图片12.png
5. 将任意数据写入管道;此数据将覆盖缓存的文件页面,而不是创建新的匿名缓冲区。
图片13.png
利用这个漏洞,甚至可以修改物理上不可变文件的页面缓存,如:CD,只读存储器等。

0x05局限

  1. 可供攻击的机器太少。
    由于存在漏洞的是5.8以上的内核,极少有公司生产环境更新的如此及时,而最新的内核已经修复了这个漏洞。因而,野外环境中很少有机器能够利用这个漏洞。
  2. 不能持久化。
    由于修改的是页面缓存,并未修改磁盘上的文件(有极小概率某个对文件有写权限的进程碰巧执行了读写操作,导致缓存被回写磁盘),虽然可以用于提权等操作,但是如果完成提权后不对被修改的文件重新进行持久化操作的话,当操作系统回收内存或者更简单的重启机器后,所做的修改都将失效。如:修改passwd文件去除掉root用户密码后,简单一个重启操作,root密码就恢复如初了。
  3. 特殊文件限制。
    由于文件系统的特性,一些特殊文件不经过页面缓存,导致此漏洞对这类文件无效。

0x06 参考资料

  1. https://dirtypipe.cm4all.com/
  2. https://github.com/torvalds/linux
  3. https://lore.kernel.org/lkml/20220221100313.1504449-1-max.kellermann@ionos.com/

查看原文