文章来源:SecTr安全团队
概述
本文中详细介绍了Linux内核新引入的io_uring子系统中的UAFCVE-2021-20226。该文件结构释放后重用,可用于在内核中提升权限,影响范围为Linux内核v5.6至v5.7。
漏洞详情
Linux内核v5.1引入了一个名为io_uring的新异步I/O接口。io_uring通过批处理I/O操作系统调用运行,因此可以在一个系统调用中执行多个I/O操作。
Linux内核v5.6中的IORING_OP_CLOSE操作实现中存在漏洞。当系统调用将files_struct传递给内核线程时,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; //
ret = 0;
}
spin_unlock_irq(&ctx->inflight_lock);
rcu_read_unlock();
return ret;
}
漏洞利用(Exp)
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); //
map = __bpf_map_get(f);
// ...
key = __bpf_copy_key(ukey, map->key_size); key = __bpf_copy_key(ukey, map->key_size); //
if (IS_ERR(key)) {
err = PTR_ERR(key);
goto err_put;
}
value_size = bpf_map_value_size(map); //
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); //
if (err)
goto free_value;
err = -EFAULT;
if (copy_to_user(uvalue, value, value_size) != 0) //
goto free_value;
// ...
}
注释(2)处的fdget()是一个优化函数,如果当前任务是单线程的,则不会增加参考计数。返回的文件结构f可以在之后的IORING_OP_CLOSE中释放。注释(3)处的__bpf_copy_key()系统调用实际上是copy_from_user()的封装。这使我们能够使用userfaultfd生成竞争条件,并触发漏洞。此时,文件结构f及其对应的映射被释放。可通过注释(4)和(5)处的虚假注释(6)处读取任意内存并向用户模式暴露。
该漏洞利用时间表如下所示:
recvmsg()函数用于定时控制。通过喷射setxattr()即可伪造被释放的bpf_map。通过map_update_elem()即可实现任意写入。fdget()条件的存在,使得该漏洞利用方法仅限于单核环境。
如侵权请私聊公众号删文