议题解读:Utilizing Cross-CPU Allocation to Exploit Preempt-Disabled Linux Kernel
字数 3731 2025-08-20 18:17:41

利用跨CPU分配技术攻击Preempt-Disabled Linux内核

1. 概述

本教学文档详细分析如何利用跨CPU分配技术来攻击Preempt-Disabled Linux内核,以CVE-2023-31248和CVE-2024-36978为例。主要内容包括漏洞分析、利用技术、跨CPU分配策略以及相关内核内存管理机制。

2. CVE-2023-31248漏洞分析

2.1 漏洞背景

CVE-2023-31248是Linux内核netfilter子系统中nftables模块的一个漏洞,允许攻击者通过精心构造的netlink批处理请求实现双重释放(Double Free)和UAF(Use-After-Free)。

2.2 漏洞成因

漏洞位于nft_chain_lookup_byid函数中,该函数在查找struct nft_chain对象时没有验证chain的状态(是否active),导致在一个batch请求中,expr会引用已经释放的chain。

补丁链接:https://lore.kernel.org/netfilter-devel/20230705121627.GC19489@breakpoint.cc/T/

2.3 Netlink批处理机制

用户态进程可以一次提交多个netlink请求给内核(批处理请求),这些请求在内存中按顺序存储,请求的存储结构为struct nlmsghdr。内核通过nfnetlink_rcv_batch解析每个请求并处理。

2.4 处理流程

  1. 用户态进程通过sendmsg发送netlink消息
  2. 进入nfnetlink_rcv_batch,在进程的系统调用上下文(sys_sendmsg)中执行对nlmsg的处理(nc->call)
  3. 将nlmsg请求转换为trans放到commit_list
  4. 进入nf_tables_commit做收尾工作
  5. nf_tables_commit_release(net)将trans放入nf_tables_destroy_list
  6. 提交给nf_tables_trans_destroy_work内核线程做进一步处理

2.5 资源释放流程

以NFT_MSG_DELCHAIN为例:

  1. nfnetlink_rcv_batch -> nf_tables_delchain
  2. 找到要删除的chain对象,将其genmask标记为inactive
  3. 分配trans对象放入commit_list
  4. nf_tables_commit中将chain对象从table->chains中移除
  5. nft_commit_release释放chain对象

2.6 漏洞触发条件

在一个批处理中同时包含:

  1. NFT_MSG_DELCHAIN请求
  2. 特定Expr(nft_immediate_init)请求

导致UAF的流程:

  1. NFT_MSG_DELCHAIN第一次释放chain
  2. NFT_MSG_DELRULE释放expr时触发第二次释放

两次释放均在nf_tables_trans_destroy_work(内核线程)中完成。

3. 漏洞利用技术

3.1 跨CPU分配挑战

释放和堆喷线程位于不同CPU:

  • 释放线程在CPU #0上执行
  • 堆喷线程在CPU #1上执行

当CPU #0释放内存后,对象会进入该CPU的per cpu cache,CPU #1无法直接分配到这些对象。

3.2 解决方案:强制对象进入共享池

策略:

  1. 在NFT_MSG_DELCHAIN请求前后塞入大量资源释放操作(free chain)
  2. 使cpu partial链表节点数目超过阈值
  3. 迫使slab进入node partial list(跨CPU共享)
  4. 在CPU #1上通过nf_tables_addchain分配victim chain实现占位

3.3 利用步骤

  1. 通过Double Free获得victim chain的UAF
  2. 利用victim_chain_ptr(在nft_table->chains中)再次释放对象
  3. 占位victim chain的name和udata
  4. 利用victim_chain_ptr释放,转换为其他对象的UAF

3.4 信息泄露技术

  1. 利用double free让table->udata和cgroup_namespace共用内存
  2. 通过读取table->udata泄露ops和user_ns
  3. 获取内核镜像地址和堆地址

table->udata的优势:

  • 大小可控
  • 数据可控
  • 可随时读取

3.5 控制流劫持

  1. 在堆上布置数据,设置ROP
  2. 利用table->udata和nft_rule重叠
  3. 篡改nft_expr对象的ops指针

nft_rule结构特点:

  • nft_expr内嵌到nft_rule的udata中
  • 通过篡改nft_rule的udata可修改nft_expr->ops

3.6 替代占位策略

另一种思路(CVE-2023-32233):

  1. 使用死循环线程占位CPU(1, 2, 3)
  2. 提高内核将nf_tables_trans_destroy_work调度到特定CPU(0)的可能性
  3. 控制堆喷进程绑定到CPU #0执行
  4. 增加批处理中两次释放中间的任务数目,扩大时间窗

4. CVE-2024-36978漏洞分析

4.1 漏洞概述

这是一个堆越界写漏洞,位于multiq调度器的multiq_tune函数中。

4.2 关键代码分析

static int multiq_tune(struct Qdisc *sch, struct nlattr *opt, struct netlink_ext_ack *extack)
{
    qopt = nla_data(opt);
    qopt->bands = qdisc_dev(sch)->real_num_tx_queues; // [1] 从dev取出real_num_tx_queues
    
    removed = kmalloc(sizeof(*removed) * (q->max_bands - q->bands), GFP_KERNEL); // [2] 根据q->bands申请内存
    
    sch_tree_lock(sch);
    q->bands = qopt->bands; // [3] 设置q->bands作为循环起点
    
    for (i = q->bands; i < q->max_bands; i++) {
        if (q->queues[i] != &noop_qdisc) {
            struct Qdisc *child = q->queues[i];
            removed[n_removed++] = child;
        }
    }
    sch_tree_unlock(sch);
    
    // [4] 根据n_removed遍历removed调用qdisc_put释放qdisc对象
    for (i = 0; i < n_removed; i++)
        qdisc_put(removed[i]);
    kfree(removed);
}

4.3 漏洞点

  1. [2]处根据q->bands申请内存(q->max_bands - q->bands)
  2. [3]处重新赋值q->bands并作为循环条件
  3. 导致对removed数组的越界写入

4.4 越界场景示例

假设:

  • q->max_bands = 100
  • q->bands = 20

在[2]处会分配80 * 8字节内存
控制qopt->bands = 10,循环最多会写入90次,导致越界

4.5 漏洞原语

越界写qdisk对象指针:

  1. [3]处循环写指针时通过n_removed记录写入数目
  2. [4]处根据n_removed从removed开始取指针调用qdisc_put
  3. 在[2]-[3]之间释放+占位可控制qdisc_put的入参

4.6 其他利用可能性

OOB写指针的其他利用方式:

  1. 修改相邻对象中的size域(构造OOB)
  2. 修改相邻对象中的指针域构建类型混淆
  3. 转换为其他漏洞原语

5. CVE-2024-36978的利用技术

5.1 KCTF的堆缓解措施

  1. CONFIG_KMALLOC_SPLIT_VARSIZE:编译时无法确定大小的对象在单独slab中分配
  2. CONFIG_RANDOM_KMALLOC_CACHES:从16个子slab中随机选择分配
  3. CONFIG_SLAB_VIRTUAL:防止Cross Cache攻击

5.2 绕过策略:使用物理页分配

当kmalloc的size > KMALLOC_MAX_CACHE_SIZE(8192)时,直接分配物理页(kmalloc_large_noprof),不受堆缓解措施影响。

static __always_inline __alloc_size(1) void *kmalloc_noprof(size_t size, gfp_t flags)
{
    if (__builtin_constant_p(size) && size) {
        unsigned int index;
        if (size > KMALLOC_MAX_CACHE_SIZE)
            return kmalloc_large_noprof(size, flags);
        index = kmalloc_index(size);
        return kmalloc_trace_noprof(
            kmalloc_caches[kmalloc_type(flags, _RET_IP_)][index],
            flags, size);
    }
    return __kmalloc_noprof(size, flags);
}

5.3 堆布局策略

  1. 控制removed的大小使其走物理页分配
  2. 寻找能控制kmalloc大小的堆喷对象(xattr->name)
  3. 在removed后面做布局和堆喷
  4. 控制removed[n_removed]中的指针

5.4 跨CPU物理页分配

  1. 塞满per cpu空闲页链表
  2. 迫使物理页回到CPU共享池
  3. 其他CPU就能分配到这些页面

5.5 控制流劫持

  1. 完成占位后控制qdisc指针
  2. 利用qdisc_put劫持控制流
  3. 需要配合其他漏洞完成地址泄露和数据布置

5.6 辅助漏洞利用

  1. CVE-2023-0597:利用CPU-entry-area位置固定特性
  2. CVE-2022-4543:利用CPU侧信道泄露内核镜像基地址

5.7 对象布局

伪造qdisc对象指针和ops指针都指向cpu entry area

5.8 ROP技巧

使用wakeup_long64的gadget让ROP栈再次迁移,绕过ROP栈太小的限制

6. 跨CPU分配技术总结

6.1 核心思路

  1. 塞满Per CPU的缓存链表
  2. 迫使内存对象回到共享资源池
  3. 打破CPU间的内存隔离

6.2 与Cross Cache的比较

  1. 类似思路,但Cross Cache利用虚拟地址共享
  2. 跨CPU分配打破物理层面的隔离

6.3 RACE在漏洞利用中的重要性

  1. 这两个漏洞本身触发不需要RACE
  2. 但漏洞利用过程需要借助RACE
  3. 使看似不可利用的漏洞变得可利用

7. 参考资源

原始议题PDF:https://www.hexacon.fr/slides/Cho_Lee-Utilizing_Cross-CPU_Allocation_to_Exploit_Preempt-Disabled_Linux_Kernel.pdf

利用跨CPU分配技术攻击Preempt-Disabled Linux内核 1. 概述 本教学文档详细分析如何利用跨CPU分配技术来攻击Preempt-Disabled Linux内核,以CVE-2023-31248和CVE-2024-36978为例。主要内容包括漏洞分析、利用技术、跨CPU分配策略以及相关内核内存管理机制。 2. CVE-2023-31248漏洞分析 2.1 漏洞背景 CVE-2023-31248是Linux内核netfilter子系统中nftables模块的一个漏洞,允许攻击者通过精心构造的netlink批处理请求实现双重释放(Double Free)和UAF(Use-After-Free)。 2.2 漏洞成因 漏洞位于 nft_chain_lookup_byid 函数中,该函数在查找 struct nft_chain 对象时没有验证chain的状态(是否active),导致在一个batch请求中,expr会引用已经释放的chain。 补丁链接:https://lore.kernel.org/netfilter-devel/20230705121627.GC19489@breakpoint.cc/T/ 2.3 Netlink批处理机制 用户态进程可以一次提交多个netlink请求给内核(批处理请求),这些请求在内存中按顺序存储,请求的存储结构为 struct nlmsghdr 。内核通过 nfnetlink_rcv_batch 解析每个请求并处理。 2.4 处理流程 用户态进程通过 sendmsg 发送netlink消息 进入 nfnetlink_rcv_batch ,在进程的系统调用上下文(sys_ sendmsg)中执行对nlmsg的处理(nc->call) 将nlmsg请求转换为trans放到commit_ list 进入 nf_tables_commit 做收尾工作 nf_tables_commit_release(net) 将trans放入 nf_tables_destroy_list 提交给 nf_tables_trans_destroy_work 内核线程做进一步处理 2.5 资源释放流程 以NFT_ MSG_ DELCHAIN为例: nfnetlink_rcv_batch -> nf_tables_delchain 找到要删除的chain对象,将其genmask标记为inactive 分配trans对象放入commit_ list nf_tables_commit 中将chain对象从table->chains中移除 nft_commit_release 释放chain对象 2.6 漏洞触发条件 在一个批处理中同时包含: NFT_ MSG_ DELCHAIN请求 特定Expr(nft_ immediate_ init)请求 导致UAF的流程: NFT_ MSG_ DELCHAIN第一次释放chain NFT_ MSG_ DELRULE释放expr时触发第二次释放 两次释放均在 nf_tables_trans_destroy_work (内核线程)中完成。 3. 漏洞利用技术 3.1 跨CPU分配挑战 释放和堆喷线程位于不同CPU: 释放线程在CPU #0上执行 堆喷线程在CPU #1上执行 当CPU #0释放内存后,对象会进入该CPU的per cpu cache,CPU #1无法直接分配到这些对象。 3.2 解决方案:强制对象进入共享池 策略: 在NFT_ MSG_ DELCHAIN请求前后塞入大量资源释放操作(free chain) 使cpu partial链表节点数目超过阈值 迫使slab进入node partial list(跨CPU共享) 在CPU #1上通过 nf_tables_addchain 分配victim chain实现占位 3.3 利用步骤 通过Double Free获得victim chain的UAF 利用victim_ chain_ ptr(在nft_ table->chains中)再次释放对象 占位victim chain的name和udata 利用victim_ chain_ ptr释放,转换为其他对象的UAF 3.4 信息泄露技术 利用double free让table->udata和cgroup_ namespace共用内存 通过读取table->udata泄露ops和user_ ns 获取内核镜像地址和堆地址 table->udata的优势: 大小可控 数据可控 可随时读取 3.5 控制流劫持 在堆上布置数据,设置ROP 利用table->udata和nft_ rule重叠 篡改nft_ expr对象的ops指针 nft_ rule结构特点: nft_ expr内嵌到nft_ rule的udata中 通过篡改nft_ rule的udata可修改nft_ expr->ops 3.6 替代占位策略 另一种思路(CVE-2023-32233): 使用死循环线程占位CPU(1, 2, 3) 提高内核将 nf_tables_trans_destroy_work 调度到特定CPU(0)的可能性 控制堆喷进程绑定到CPU #0执行 增加批处理中两次释放中间的任务数目,扩大时间窗 4. CVE-2024-36978漏洞分析 4.1 漏洞概述 这是一个堆越界写漏洞,位于multiq调度器的 multiq_tune 函数中。 4.2 关键代码分析 4.3 漏洞点 [ 2]处根据q->bands申请内存(q->max_ bands - q->bands) [ 3 ]处重新赋值q->bands并作为循环条件 导致对removed数组的越界写入 4.4 越界场景示例 假设: q->max_ bands = 100 q->bands = 20 在[ 2]处会分配80 * 8字节内存 控制qopt->bands = 10,循环最多会写入90次,导致越界 4.5 漏洞原语 越界写qdisk对象指针: [ 3]处循环写指针时通过n_ removed记录写入数目 [ 4]处根据n_ removed从removed开始取指针调用qdisc_ put 在[ 2]-[ 3]之间释放+占位可控制qdisc_ put的入参 4.6 其他利用可能性 OOB写指针的其他利用方式: 修改相邻对象中的size域(构造OOB) 修改相邻对象中的指针域构建类型混淆 转换为其他漏洞原语 5. CVE-2024-36978的利用技术 5.1 KCTF的堆缓解措施 CONFIG_ KMALLOC_ SPLIT_ VARSIZE:编译时无法确定大小的对象在单独slab中分配 CONFIG_ RANDOM_ KMALLOC_ CACHES:从16个子slab中随机选择分配 CONFIG_ SLAB_ VIRTUAL:防止Cross Cache攻击 5.2 绕过策略:使用物理页分配 当kmalloc的size > KMALLOC_ MAX_ CACHE_ SIZE(8192)时,直接分配物理页(kmalloc_ large_ noprof),不受堆缓解措施影响。 5.3 堆布局策略 控制removed的大小使其走物理页分配 寻找能控制kmalloc大小的堆喷对象(xattr->name) 在removed后面做布局和堆喷 控制removed[ n_ removed ]中的指针 5.4 跨CPU物理页分配 塞满per cpu空闲页链表 迫使物理页回到CPU共享池 其他CPU就能分配到这些页面 5.5 控制流劫持 完成占位后控制qdisc指针 利用qdisc_ put劫持控制流 需要配合其他漏洞完成地址泄露和数据布置 5.6 辅助漏洞利用 CVE-2023-0597:利用CPU-entry-area位置固定特性 CVE-2022-4543:利用CPU侧信道泄露内核镜像基地址 5.7 对象布局 伪造qdisc对象指针和ops指针都指向cpu entry area 5.8 ROP技巧 使用wakeup_ long64的gadget让ROP栈再次迁移,绕过ROP栈太小的限制 6. 跨CPU分配技术总结 6.1 核心思路 塞满Per CPU的缓存链表 迫使内存对象回到共享资源池 打破CPU间的内存隔离 6.2 与Cross Cache的比较 类似思路,但Cross Cache利用虚拟地址共享 跨CPU分配打破物理层面的隔离 6.3 RACE在漏洞利用中的重要性 这两个漏洞本身触发不需要RACE 但漏洞利用过程需要借助RACE 使看似不可利用的漏洞变得可利用 7. 参考资源 原始议题PDF:https://www.hexacon.fr/slides/Cho_ Lee-Utilizing_ Cross-CPU_ Allocation_ to_ Exploit_ Preempt-Disabled_ Linux_ Kernel.pdf