CVE-2025-21893分析与复现
Cheater 发表于 北京 漏洞分析 2354浏览 · 2025-06-04 10:20



0x00.前言

新版linux内核keyring框架的uaf漏洞,这个漏洞本质上是很简单的,但是触发条件极其苛刻,需要条件竞争(时机卡的很死),然后最后还有个调度问题(可能可以用别的方法绕过)。因此为了方便分析漏洞点的可触发性,这里我对内核源码进行了3处的只影响运行速度和时机的修改。

0x01.前置知识

本文使用的源码版本为linux6.13.2.

keyring

这个模块想必搞内核的师傅都了解过,user_key_payload利用就是使用的keyring相关的系统调用。

Linux 内核的 keyring 机制提供了一种 安全地存储和管理加密密钥、身份认证令牌、证书等敏感数据 的方式。它通过一组 系统调用 允许用户空间程序管理密钥,并提供内核内部组件(如文件系统、加密 API)对密钥的访问能力。

用户可利用add_key、keyctl、request_key等系统调用对密钥进行一些管理。

模块目录:security/keys/

add_key

顾名思义,add_key实际就是向指定的 keyring 添加一个新的密钥。

Plain Text
复制代码
参数包括

type:密钥的类型(如 "user""logon")。

description:密钥的描述,用于标识密钥。

payload:密钥的数据内容。

plen:密钥数据的长度。

keyring:目标 keyring 的句柄,密钥将被添加到此 keyring(KEY_SPEC_USER_KEYRING、KEY_SPEC_PROCESS_KEYRING等)。

keyctl

keyctl和ioctl一样,提供多种管理密钥的功能。

功能包括

KEYCTL_GET_KEYRING_ID:获取 keyring ID。

KEYCTL_JOIN_SESSION_KEYRING:创建或加入 session keyring。

KEYCTL_REVOKE:撤销密钥,使其不可用。

KEYCTL_DESCRIBE:获取密钥的信息(如类型、描述)。

KEYCTL_CLEAR:清空 keyring。

KEYCTL_LINK:将密钥链接到 keyring。

KEYCTL_UNLINK:从 keyring 移除密钥。

KEYCTL_INVALIDATE:将key标记为无效key。

KEYCTL_SEARCH:在 keyring 中搜索密钥。

KEYCTL_READ:读取密钥数据。

KEYCTL_INSTANTIATE:实例化密钥(通常用于 request_keyupcall 机制)。

request_key

查找现有密钥,若找不到,则尝试调用 upcall 机制获取密钥。

参数包括

type:密钥的类型。

description:密钥的描述。

callout_info:可选参数,用于 upcall 机制(通常用于自动生成密钥)。

keyring:目标 keyring 的句柄

Keyring结构

Keyring 是一种特殊的密钥,可以包含其他密钥。每个进程默认有三个 keyring:

进程 Keyring :与进程绑定,在进程退出时销毁。

线程 Keyring:与线程绑定,线程结束时销毁。

用户 Session Keyring:与用户会话绑定,在用户登出时销毁。

User Keyring:特定用户拥有的密钥集合。

Group Keyring:用户组共享的密钥集合。

这里需要注意,当使用多线程时,只有User Keyring或者Group Keyring能线程间共享,后面多线程条件竞争的时候就踩了这个坑。

一个keyring可以拥有多个key,也就意味着我们可以通过add_key往一个keyring中添加多个key。

Keyring-GC

在security/keys/gc.c文件中存放着关于keyring框架的gc垃圾回收代码。

当进行一些操作如keyctl_invalidate或者key_put操作时,会使用schedule_work调用gc回收函数key_garbage_collector来进行释放操作。

0x02.漏洞分析

根据http://lore.kernel.org/linux-cve-announce网站可以看的该CVE的相关信息。



根据以上描述可以知道,漏洞出现在key_put函数,在将key->usage减为0之后,还会对key进行一些操作,而此时异步的gc处理函数是通过key->usage是否为0判断是否进行释放操作的,这样就可能会导致gc处理函数释放key后,key_put继续对key进行操作导致uaf。

根据issue commit可以看到diff信息。

这里能看到将gc对于key->usage的判断改成key->flags的判断,并在key_put操作完key后才设置这个flags值。

我们查看未修复的相关源码:

key_put函数在对key->usage减一后,只要减为0就会判断key是否KEY_FLAG_IN_QUOTA的flag类型,如果符合就会继续对key进行操作。最后调用schedule_work(&key_gc_work)触发gc回收来释放key。

可以看到key_gc_work注册的函数是key_garbage_collector。

再看key_garbage_collector函数,会对遍历树中的所有key,并检查key->usage,为0就会执行释放的代码。

这样很明显是存在问题的,因为key_garbage_collector并不只是会被key_put调用,包括keyctl_invalidate等函数也会调用。

keyctl_invalidate调用链如下:

所以我们只要能在key_put减引用与操作key之间,通过key_garbage_collector释放key即可造成uaf漏洞。当然这样的利用条件十分苛刻,太卡时机了。

0x03.漏洞触发

经过分析,我们可以了解到keyctl_invalidate可以触发gc回收函数,而key_put的调用就需要用到keyctl_unlink操作了。

先前我们add_key就是创建一个key并放入keyring中,而keyctl_unlink则是将key从keyring中删除。

通过如下调用链可触发key的减引用。

其中key_unlink会调用__key_unlink_begin函数创建删除key的edit命令。

之后__key_unlink调用assoc_array_apply_edit执行edit操作,即删除操作。

最后调用keyring_assoc_array_ops结构体的keyring_free_object调用key_put对key进行减引用。

这里需要注意assoc_array_apply_edit是通过call_rcu触发的keyring_free_object函数。

由此我们可以写出如下半成poc:

因为这里条件竞争很麻烦,所以我们需要修改内核代码保证竞争成功。poc里面的多线程就设置只需要执行一次。

其中我们修改的两处分别为key_put函数:

以及key_garbage_collector函数

因为我们这里使用的是for循环阻塞,所以需要在函数声明处加个attribute((optimize("O0")))来防止这段for循环被优化掉。

给key_garbage_collector一个小循环保证另一个线程能执行到key_put减引用之后,同时给key_put加一个大循环,保证在key_put执行后续对key的操作时,gc能来得急进行key的校验以及释放工作。

但只做以上两处修改你会发现poc并不管用,跟踪之后发现是因为gc最后卡在了synchronize_rcu函数。

可以看到found_unreferenced_key会把key放入graveyard,之后通过key_gc_unused_keys(&graveyard)来free掉key。

但是在key_gc_unused_keys调用之前,会调用synchronize_rcu函数等待rcu任务完成。

而又刚好我们前文unlink是通过call_rcu来调用key_put函数的,所以这里会等待key_put操作之后才会继续释放key。这样看来,key又变得十分安全了。

但是我们要知道key_put不只会被unlink的call_rcu调用,unlink在对key进行删除操作时,会优先给key加引用,之后结束时再减引用。

这里的lookup_user_key会自动给key加引用,在执行完key_unlink之后,又会直接调用key_ref_put来减引用。

如果我们能在call_rcu执行完key_put后再去调用key_ref_put来减引用就可以绕过rcu保护机制了,当然这样会比较困难,因为call_rcu是延迟释放。不过兴许可以通过其他对key进行操作的函数来实现。

这里我们就不再去深入探索了,索性直接在key_ref_put前加个synchronize_rcu来等call_rcu执行完(当然这里使用循环来造成延迟应该也是可以的)。

经过这三处地方的修改,再结合我们的半成poc即可实现uaf。







0x04.总结

该漏洞产生的核心原因在于:当进行异步的gc操作时,错误的将引用计数为0作为释放的判断条件,而又在减引用之后对准备释放的指针进行操作。

个人认为这是一个比较新的攻击面,很有可能在其他异步操作时出现相同的问题,有待深挖。



0 条评论
某人
表情
可输入 255