Linux内核CVE-2024-50264漏洞分析与利用技术详解

本文深入分析了Linux内核AF_VSOCK子系统中的竞争条件漏洞CVE-2024-50264,详细介绍了该UAF漏洞的利用技术,包括内核对象布局、竞争条件触发、跨缓存攻击和权限提升方法。

内核漏洞分析与新的CVE-2024-50264利用程序

一些与内存损坏相关的漏洞极其难以利用。它们可能引发竞争条件、导致系统崩溃并施加各种限制,使研究人员的工作变得复杂。处理这类"脆弱"的错误需要显著更多的时间和精力。Linux内核中的CVE-2024-50264正是这样一个复杂的漏洞,它获得了2025年Pwnie Award"最佳权限提升"类别奖项。

同时发现的故事

2021年,我在Linux内核的AF_VSOCK子系统中发现了一个漏洞,并发表了相关文章。3年后,在2024年4月,我决定再次研究AF_VSOCK,通过修改的syzkaller模糊测试工具发现了另一个内核故障。我制作了导致内核崩溃的最小重现程序,禁用了KASAN消毒剂,发现该错误导致内核工作线程立即解引用空指针。

CVE-2024-50264分析

CVE-2024-50264漏洞于2016年8月通过提交06a8fc78367d引入Linux内核v4.8代码中。这是AF_VSOCK虚拟套接字实现中的竞争条件,发生在系统调用connect()和POSIX信号处理之间,导致释放后使用(UAF)。该漏洞特别危险,因为普通用户可以在没有额外权限的情况下触发它,无需使用用户命名空间。

内核错误地使用了已释放的virtio_vsock_sock对象,其大小为80字节,对应于slab分配器的kmalloc-96缓存。内存损坏表现为在内核工作线程中发生的释放后写入(UAF-write)。

使用"不朽信号"重现漏洞

首先创建服务器虚拟套接字:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
#define UAF_PORT 0x2712

int ret = -1;
int vsock1 = 0;
struct sockaddr_vm addr = {
    .svm_family = AF_VSOCK,
    .svm_port = UAF_PORT,
    .svm_cid = VMADDR_CID_LOCAL
};

vsock1 = socket(AF_VSOCK, SOCK_STREAM, 0);
if (vsock1 < 0)
    err_exit("[-] creating vsock");

ret = bind(vsock1, (struct sockaddr *)&addr, sizeof(struct sockaddr_vm));
if (ret != 0)
    err_exit("[-] binding vsock");

ret = listen(vsock1, 0); /* backlog = 0 */
if (ret != 0)
    err_exit("[-] listening vsock");

然后尝试为其建立客户端虚拟套接字连接:

1
2
3
4
5
6
7
int vsock2 = 0;

vsock2 = socket(AF_VSOCK, SOCK_STREAM, 0);
if (vsock2 < 0)
    err_exit("[-] creating vsock");

ret = connect(vsock2, (struct sockaddr *)&addr, sizeof(struct sockaddr_vm));

为了触发错误,需要使用POSIX信号中断系统调用connect()。研究人员@v4bel和@qwerty使用了SIGKILL,但这会杀死利用进程。我的模糊测试器找到了更巧妙的方法:

1
2
3
4
5
6
struct sigevent sev = {};
timer_t race_timer = 0;

sev.sigev_notify = SIGEV_SIGNAL;
sev.sigev_signo = 33;
ret = timer_create(CLOCK_MONOTONIC, &sev, &race_timer);

内存损坏详情

当系统调用connect()被信号中断时会发生竞争条件。如果此时易受攻击的套接字处于TCP_ESTABLISHED状态,则它会转换为TCP_CLOSING状态:

1
2
3
4
5
6
7
8
if (signal_pending(current)) {
    err = sock_intr_errno(timeout);
    sk->sk_state = sk->sk_state == TCP_ESTABLISHED ? TCP_CLOSING : TCP_CLOSE;
    sock->state = SS_UNCONNECTED;
    vsock_transport_cancel_pkt(vsk);
    vsock_remove_connected(vsk);
    goto out_wait;
}

CVE-2024-50264的严重限制

该漏洞有许多令人不快的细微差别,使利用变得复杂:

  1. 易受攻击的客户端virtio_vsock_sock对象与服务器对象一起创建
  2. 所需的竞争条件重现非常不稳定
  3. 在kfree()后仅几微秒内,释放后写入就在kworker中发生
  4. 在kworker中进行UAF写入后会发生空指针解引用
  5. 即使避免了内核崩溃,在VSOCK_CLOSE_TIMEOUT(八秒)后,kworker中会出现另一个空指针解引用
  6. 如果virtio_vsock_sock.tx_lock字段非零,kworker会在spin_lock_bh()中挂起

内核黑客训练项目

早在2017年,我为学生创建了一个名为kernel-hack-drill的项目。这是用于学习和实验Linux内核漏洞利用的测试环境。我想起了这个项目,并决定在开发CVE-2024-50264的利用原语时使用它。

kernel-hack-drill是一个开源项目,包含:

  • drill_mod.c - 小型Linux内核模块的源代码
  • drill.h - 描述drill_mod.ko接口的头文件
  • drill_test.c - 用户空间与drill_mod.ko交互的测试

跨缓存攻击实验

我需要在启用了slab分配器保护功能的Ubuntu Server HWE内核版本上研究跨缓存攻击的细节。

我实现了drill_uaf_w_msg_msg.c中的标准跨缓存攻击。攻击算法:

  1. 通过分配objs_per_slab对象创建新的活动slab
  2. 分配objs_per_slab * cpu_partial对象来准备cpu_partial完整slab
  3. 通过分配objs_per_slab对象形成包含UAF对象的slab
  4. 再次通过分配objs_per_slab对象创建新的活动slab
  5. 通过释放紧接最后一个对象之前分配的对象来完全释放包含UAF对象的slab
  6. 通过释放步骤2中保留的slab中的对象来填充partial list
  7. 通过创建大量目标msg_msg对象来重用包含UAF对象的页面

适应CVE-2024-50264的跨缓存攻击

易受攻击的客户端virtio_vsock_sock对象与服务器对象一起创建(限制1)。将它们分配在同一个slab中不允许进行跨缓存攻击,因为这阻止了完全释放slab。

解决方案是使用另一个竞争条件来利用主要竞争条件:

在易受攻击的系统调用connect()开始后10000纳秒发送"不朽"信号33。这比UAF所需的延迟要短得多。然后检查是否重现了早期竞争条件。

实验Dirty Pipe

我为kernel-hack-drill中的合成漏洞实现了Dirty Pipe攻击。PoC利用程序drill_uaf_w_pipe_buffer.c可在存储库中找到。

该利用程序:

  • 执行跨缓存攻击,将包含drill_item_t对象的slab转换为包含pipe_buffer对象的slab
  • 利用drill_item_t中的UAF写入
  • 实现Dirty Pipe攻击,实现"一枪"权限提升且无需信息泄漏

AARW和KASLR的最后报复

当我成功通过UAF写入将受控数据写入pipe_buffer.page指针时,我获得了通过管道在任意地址读写内核内存的能力(AARW)。

我决定通过管道将AARW应用于动态内核内存(堆)中的struct cred。我之前已经通过msg_msg中的越界读取获取了cred的虚拟地址。

计算vmemmap中相应struct page偏移的公式:

1
2
3
#define STRUCT_PAGE_SZ 64lu
#define PAGE_ADDR_OFFSET(addr) (((addr & 0x3ffffffflu) >> 12) * STRUCT_PAGE_SZ)
uaf_val = PAGE_ADDR_OFFSET(cred_addr);

结论

对于同时做出发现的安全研究人员来说,bug冲突是一种痛苦的情况。尽管如此,如果无论如何都能完成研究,那还是很高兴的。

处理具有多个限制的这种复杂竞争条件帮助我发明了新的漏洞利用技术,并改进了我的kernel-hack-drill项目 - Linux内核安全研究人员的测试环境。

comments powered by Disqus
使用 Hugo 构建
主题 StackJimmy 设计