利用跨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 关键代码分析
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 漏洞点
- [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),不受堆缓解措施影响。
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 堆布局策略
- 控制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