查看原文
其他

CVE-2021-20226:详解 Linux 内核 IO_URING 子系统中的引用计数漏洞

ZDI 代码卫士 2022-05-23

 聚焦源代码安全,网罗国内外最新资讯!

编译:奇安信代码卫士


趋势科技 ZDI 发布文章,详细描述了2020年6月收到的最近引入的 io_uring 子系统的引用计数漏洞情况。该漏洞导致在任意 file 结构上的释放后使用后果,进而导致内核提权。该漏洞是由 Flatt Security 公司的研究员 Ryota Shiga 发现的。ZDI 指出,该漏洞影响 5.6(含)到5.7(含)之间的版本,其编号为CVE-2021-20226。


漏洞


Linux 内核 5.1 引入一个新的异步 I/O 功能,名为 “io_uring”。该子系统通过批量进行 I/O 操作系统调用进行操作,因此多个 I/O 操作可在一个系统调用中执行。

Linux 内核 5.6 中的IORING_OP_CLOSE 操作实现中存在缺陷。当系统调用向内核线程传递 files_strut 时,io_grab_files() 并不会增加代码注释中所标注 (1) 处的引用基数,从而导致后续被释放的文件结构遭访问。

static int io_grab_files(struct io_kiocb *req) { // ... rcu_read_lock(); spin_lock_irq(&ctx->inflight_lock);spin_lock_irq(&ctx->inflight_lock); if (fcheck(ctx->ring_fd) == ctx->ring_file) { list_add(&req->inflight_entry, &ctx->inflight_list); req->flags |= REQ_F_INFLIGHT; req->work.files = current->files; // <-- (1) ret = 0; } spin_unlock_irq(&ctx->inflight_lock); rcu_read_unlock(); return ret; }


利用


map_lookup_elem() map_update_elem() 函数可用于利用该漏洞。

static int map_lookup_elem(union bpf_attr *attr) { void __user *ukey = u64_to_user_ptr(attr->key); int ufd = attr->map_fd; // ... f = fdget(ufd); // <-- (2) map = __bpf_map_get(f); // ... key = __bpf_copy_key(ukey, map->key_size); key = __bpf_copy_key(ukey, map->key_size); // <-- (3) if (IS_ERR(key)) { err = PTR_ERR(key); goto err_put; } value_size = bpf_map_value_size(map); // <-- (4) err = -ENOMEM; value = kmalloc(value_size, GFP_USER | __GFP_NOWARN); if (!value) goto free_key; err = bpf_map_copy_value(map, key, value, attr->flags); // <-- (5) if (err) goto free_value; err = -EFAULT; if (copy_to_user(uvalue, value, value_size) != 0) // <-- (6) goto free_value; // ... }


注释中 (2) 处的 fdget() 是一个优化后的函数,如果当前任务是 single-thread,则不会增加引用计数。返回的 file 结构 f 可由后续的 IORING_OP_CLOSE 释放。注释 (3) 处的 _bpf_copy_key() 系统调用实际上是 copy_from_user() 的封装。这就使得我们能够使用 userfaultfd 生成竞争条件并触发该漏洞。此时,文件结构 f 及其对应的 map 被释放。可通过 (4) 和 (5) 处的虚假数据对 map 内存进行重新分配。最终,我们可以读取 (6) 处的任意内存并披露用户模式。

利用时间轴如下:


函数 recvmsg() 用于定时控制。通过喷射 setxattr() 即可伪造被释放的 bpf_map。通过 map_update_elem() 即可实现任意写。由于 fdget() 条件的存在,该利用方法仅限于单核环境。


结论


新功能意味着新的攻击面,而新的攻击面通常会导致新漏洞的产生。有兴趣的读者可以看看从这个子系统中是否会找到其它漏洞。无论如何,Ryota 的这份漏洞报告十分出色。Ryota 正是在2021年 Pwn2Own 大赛中凭借演示 Ubuntu 中另外一个提权漏洞而获得3万美元赏金的研究员。让我们一起期待他再创佳绩吧!





推荐阅读
为了研究,可以在 Linux 内核中植入漏洞吗?
三个已存在15年的 Linux 内核漏洞
Linux 内核修复5个高危漏洞
攻击者可提前检测到 Linux 内核的补丁并开发 exploit




原文链接

https://www.zerodayinitiative.com/blog/2021/4/22/cve-2021-20226-a-reference-counting-bug-in-the-linux-kernel-iouring-subsystem


题图:Pixabay License


本文由奇安信编译,不代表奇安信观点。转载请注明“转自奇安信代码卫士 https://codesafe.qianxin.com”。



奇安信代码卫士 (codesafe)

国内首个专注于软件开发安全的

产品线。

    觉得不错,就点个 “在看” 或 "” 吧~


您可能也对以下帖子感兴趣

文章有问题?点此查看未经处理的缓存