flawopen.com/Teardowns/cve-2024-0582-linux-io-uring-privilege-escalation
CVE-2024-0582:Linux io_uring 已映射缓冲区环的释放后使用
CVE-2024-0582(CVSS 7.8,高危):用 IORING_REGISTER_PBUF_RING 和 IOU_PBUF_RING_MMAP 注册的缓冲区环,在拆除时被释放,而用户空间仍通过 mmap() 保持着映射,于是留下一页在内核重新使用后仍可被攻击者读写,造成本地权限提升。
图书馆允许你登记一间专用阅览室,并给你一把可以随意进出的钥匙。你注销这间房时,管理员立刻把它放回房源池,却从不收回你的钥匙。图书馆随后把同一间房租给别人,而你仍能用旧钥匙进去,改写他留在桌上的东西。修复办法是:让每一间这样的房都保持占用,只在闭馆时、所有钥匙都归还之后,才释放一次。
核心概念与专有名词
io_uring- Linux 的异步 I/O 接口。应用与内核共享提交环和完成环,从而省去每次操作一次系统调用。
提供的缓冲区环(IORING_REGISTER_PBUF_RING)- 应用注册的一圈缓冲区,供内核每次操作挑选一个。带
IOU_PBUF_RING_MMAP时,由内核分配这个环,应用用mmap()映射它,而不是自己提供内存。 释放后使用(UAF)- 在内存被释放后仍去访问它。一旦内核把这块内存重新分配给别的对象,过期引用就能读取或破坏它。
remap_pfn_range / folio_put- 该环通过
remap_pfn_range()映射进用户空间,因此普通的 munmap 记账不会释放它。拆除时调用folio_put()会在映射仍然存在时释放该页。
框架映射
- 本指南所述弱点的 CWE 参考: CWE-416
根本原因剖析 (Root Cause)
对于用 IOU_PBUF_RING_MMAP 创建的缓冲区环,io_alloc_pbuf_ring() 分配页面,用户空间再通过 remap_pfn_range() 用 mmap() 映射它们。由于这条路径绕过了普通的 munmap/release 记账,__io_remove_buffers() 调用 folio_put() 会在用户映射可能仍然存在时释放这些页面——这就是释放后使用(CWE-416):用户空间保留着一个仍然有效的映射,指向一页已被内核归还给分配器、可以再分配的内存。此问题由 Jann Horn 报告。提交 c392cbecd8ec 把每个由 mmap 分配的环记录到新的 io_buf_list 中,只有在 ->release() 的 io_kbuf_mmap_list_free() 里、映射消失之后才释放它。
攻击执行流程分解
注册一个由 mmap 支撑的环
无特权进程用 IORING_REGISTER_PBUF_RING 和 IOU_PBUF_RING_MMAP 标志调用 io_uring_register(),于是内核分配这个缓冲区环。
把它映射进用户空间
进程用 mmap() 映射该环。内核用 remap_pfn_range() 映射这些页面,进程保留着这个映射。
在仍被映射时释放该环
注销缓冲区(或拆除该环)会走到 __io_remove_buffers(),其中的 folio_put() 在用户映射仍然存在的情况下就释放了这些页面。
通过过期映射写入
内核把已释放的页面重新分配给别的对象;仍持有映射的进程读取并覆写它,从而破坏内核状态并提升到 root。
源代码对比:漏洞与安全实现
/* io_uring/kbuf.c 修复前(Linux 6.6.4) */
static int io_alloc_pbuf_ring(struct io_uring_buf_reg *reg,
struct io_buffer_list *bl)
{
gfp_t gfp = GFP_KERNEL_ACCOUNT | __GFP_ZERO | __GFP_NOWARN | __GFP_COMP;
size_t ring_size;
void *ptr;
ring_size = reg->ring_entries * sizeof(struct io_uring_buf_ring);
ptr = (void *) __get_free_pages(gfp, get_order(ring_size));
if (!ptr)
return -ENOMEM;
bl->buf_ring = ptr;
bl->is_mapped = 1;
bl->is_mmap = 1;
return 0;
}
static int __io_remove_buffers(struct io_ring_ctx *ctx,
struct io_buffer_list *bl, unsigned nbufs)
{
if (bl->is_mapped) {
i = bl->buf_ring->tail - bl->head;
if (bl->is_mmap) {
/* 缺陷:ring 通过 mmap() 和
* remap_pfn_range() 提供给用户空间。此处的 folio_put() 会释放页面,但
* 映射可能仍然存在,因此用户空间会继续写入已释放并重新分配的
* 内核页面。 */
folio_put(virt_to_folio(bl->buf_ring));
bl->buf_ring = NULL;
bl->is_mmap = 0;
}
}
/* ... */
}
/* io_uring/kbuf.c 在 commit c392cbecd8ec 之后(Linux 6.6.5 / 6.7) */
struct io_buf_free {
struct hlist_node list;
void *mem;
};
static int io_alloc_pbuf_ring(struct io_ring_ctx *ctx,
struct io_uring_buf_reg *reg,
struct io_buffer_list *bl)
{
struct io_buf_free *ibf;
size_t ring_size;
void *ptr;
ring_size = reg->ring_entries * sizeof(struct io_uring_buf_ring);
ptr = io_mem_alloc(ring_size);
if (!ptr)
return -ENOMEM;
/* 修复:记录每个可通过 mmap 访问的 ring,仅在
* ->release() 时、映射消失后才释放其页面,不能在 teardown 期间释放。 */
ibf = kmalloc(sizeof(*ibf), GFP_KERNEL_ACCOUNT);
if (!ibf) {
io_mem_free(ptr);
return -ENOMEM;
}
ibf->mem = ptr;
hlist_add_head(&ibf->list, &ctx->io_buf_list);
bl->buf_ring = ptr;
bl->is_mapped = 1;
bl->is_mmap = 1;
return 0;
}
static int __io_remove_buffers(struct io_ring_ctx *ctx,
struct io_buffer_list *bl, unsigned nbufs)
{
if (bl->is_mapped) {
i = bl->buf_ring->tail - bl->head;
if (bl->is_mmap) {
/* io_kbuf_mmap_list_free() 会在 ->release() 时释放页面,
* 因此只要页面仍可能被映射,此处就不会释放它。 */
bl->buf_ring = NULL;
bl->is_mmap = 0;
}
}
/* ... */
}
/* 在 ->release() 时或之后调用:此时可以安全释放 mmap 的 ring。 */
void io_kbuf_mmap_list_free(struct io_ring_ctx *ctx)
{
struct io_buf_free *ibf;
struct hlist_node *tmp;
hlist_for_each_entry_safe(ibf, tmp, &ctx->io_buf_list, list) {
hlist_del(&ibf->list);
io_mem_free(ibf->mem);
kfree(ibf);
}
}
工程与系统安全加固清单
- ✓运行包含提交 c392cbecd8ec 的内核(Linux 6.6.5、6.7 或更高版本,或变更日志中列出
CVE-2024-0582的发行版内核)。 - ✓在工作负载不需要 io_uring 的地方限制它:设置
kernel.io_uring_disabled=2,或用发行版提供的 seccomp/SELinux 机制按工作负载管控。 - ✓当通过
mmap()/remap_pfn_range()把内存交给用户空间时,把它的生命周期绑定到->release(),而不是更早的拆除路径,以便在映射消失之后只释放一次。 - ✓用 KASAN 和
slub_debug构建并模糊测试内核,让“映射期间释放”在测试中作为释放后使用被抓住。 - ✓对不可信的本地工作负载,优先使用不带
CONFIG_IO_URING编译的内核,或用 seccomp 阻止io_uring_setup/io_uring_register系统调用。