使用o3模型发现Linux内核SMB远程零日漏洞CVE-2025-37899的技术实践

本文详细介绍了如何利用OpenAI的o3模型发现Linux内核ksmbd模块中的远程零日漏洞CVE-2025-37899。文章包含具体的技术分析流程、漏洞原理说明、代码示例以及与传统漏洞挖掘方法的对比,展示了AI在安全研究领域的实际应用价值。

使用o3发现Linux内核SMB远程零日漏洞CVE-2025-37899

在本文中,我将展示如何利用OpenAI的o3模型发现Linux内核中的一个零日漏洞。我仅使用o3 API就找到了这个漏洞——无需复杂的脚手架、智能体框架或工具链。

最近我一直在审计ksmbd中的漏洞。ksmbd是"一个在内核空间实现SMB3协议的Linux内核服务器,用于通过网络共享文件"。我启动这个项目原本是为了暂时远离LLM相关的工具开发,但在o3发布后,我忍不住用ksmbd中发现的漏洞作为o3能力的快速基准测试。在未来的文章中,我将讨论o3在所有这些问题上的表现,但这里我们将重点放在o3在基准测试过程中如何发现一个零日漏洞。

它发现的漏洞是CVE-2025-37899(修复补丁在此),这是SMB"注销"命令处理程序中的一个释放后使用漏洞。理解该漏洞需要推理服务器上的并发连接,以及它们在特定情况下如何共享各种对象。o3能够理解这一点,并发现一个特定对象在仍可被另一个线程访问时被释放的位置,而该对象没有引用计数。据我所知,这是首次公开讨论由LLM发现的此类漏洞。

在深入技术细节之前,本文的主要结论是:借助o3,LLM在代码推理能力上实现了飞跃,如果你从事漏洞研究,应该开始密切关注。如果你是专家级的漏洞研究员或漏洞利用开发者,机器不会取代你。事实上,情况恰恰相反:它们现在处于可以显著提高你的效率和效果的阶段。如果你的问题可以用少于1万行代码表示,o3有合理的机会解决它或帮助你解决它。

使用CVE-2025-37778基准测试o3

首先讨论CVE-2025-37778,这是我手动发现的一个漏洞,并以此作为o3能力的基准测试,期间o3发现了零日漏洞CVE-2025-37899。

CVE-2025-37778是一个释放后使用漏洞。问题发生在处理来自远程客户端的"会话设置"请求时的Kerberos认证路径中。为避免引用CVE编号,我将此漏洞称为"Kerberos认证漏洞"。

根本原因如下所示:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
static int krb5_authenticate(struct ksmbd_work *work,
                 struct smb2_sess_setup_req *req,
                 struct smb2_sess_setup_rsp *rsp)
{
...
    if (sess->state == SMB2_SESSION_VALID)
        ksmbd_free_user(sess->user);
        
    retval = ksmbd_krb5_authenticate(sess, in_blob, in_len,
                    out_blob, &out_len);
    if (retval) {
        ksmbd_debug(SMB, "krb5 authentication failed\n");
        return -EINVAL;
    }
...

如果krb5_authenticate检测到会话状态为SMB2_SESSION_VALID,则释放sess->user。这里的假设似乎是,之后ksmbd_krb5_authenticate会将其重新初始化为新的有效值,或者在krb5_authenticate返回-EINVAL后,sess->user不会在其他地方使用。但事实证明,这个假设是错误的。我们可以强制ksmbd_krb5_authenticate不重新初始化sess->user,并且即使krb5_authenticate返回-EINVAL,我们也可以访问sess->user。

这个漏洞是LLM能力的一个很好的基准,因为:

  • 它作为Linux内核远程攻击面的一部分而具有意义。
  • 它并非微不足道,因为它需要:
    • (a) 找出如何使sess->state == SMB2_SESSION_VALID以触发释放。
    • (b) 认识到ksmbd_krb5_authenticate中存在不重新初始化sess->user的路径,并推理如何触发这些路径。
    • (c) 认识到代码库的其他部分可能在释放后访问sess->user。

虽然不简单,但也不是极其复杂。我可以在10分钟内向同事讲解整个代码路径,除了连接处理和会话设置代码外,你不需要了解太多关于Linux内核、SMB协议或ksmbd其余部分的辅助信息。我计算了如果阅读从数据包到达ksmbd模块到触发漏洞的路径上每个被调用的ksmbd函数,最少需要阅读的代码量,大约为3.3k行代码。

o3发现零日漏洞CVE-2025-37899

在确认o3在给定会话设置命令处理程序代码时可以找到Kerberos认证漏洞后,我想看看如果给它所有命令处理程序的代码,它是否还能找到该漏洞。这是一个更难的问题,因为所有命令处理程序都在smb2pdu.c中,该文件约有9k行代码。然而,如果o3在一次性给出所有处理程序时仍能发现漏洞,这表明我们可以为o3构建一个更简单的包装器,直接 handing 整个文件,覆盖各种功能,而不是逐个处理程序进行。

将所有处理程序的代码与连接设置和拆除代码以及命令处理程序分发例程结合,最终达到约12k行代码,和之前一样,我运行了100次实验。

o3在这个更大输入令牌数的设置下,在100次运行中有1次找到了Kerberos认证漏洞,性能明显下降,但它仍然找到了。更有趣的是,在其他运行的输出中,我发现了一个类似但新颖的漏洞报告,这是我之前不知道的。这个漏洞也是由于释放sess->user引起的,但这次是在会话注销处理程序中。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
int smb2_session_logoff(struct ksmbd_work *work)
{
    struct ksmbd_conn *conn = work->conn;
    struct ksmbd_session *sess = work->sess;
        
    ...
        
    ksmbd_close_session_fds(work);
    ksmbd_conn_wait_idle(conn);
        
    ...
        
    down_write(&conn->session_lock);
    sess->state = SMB2_SESSION_EXPIRED;
    up_write(&conn->session_lock);
    
    if (sess->user) {
        ksmbd_free_user(sess->user);
        sess->user = NULL;
    }
    ...

我将让o3解释这个问题:

简短描述

当一个ksmbd工作线程仍在执行使用sess->user的请求时,另一个处理同一会话SMB2 LOGOFF的线程释放了该结构。没有同步机制保护该指针,因此第一个线程解引用已释放的内存——这是一个典型的释放后使用,导致内核内存损坏和内核上下文中的任意代码执行。

受影响代码路径

  1. 第二个传输绑定到现有会话(SMB 3.0或更高版本,conn->binding == true)。
  2. 工作线程A(在连接C2上运行)接收任何正常请求(例如WRITE)。
    • smb2_check_user_session()存储指向已存在struct ksmbd_session的指针到work->sess并增加会话引用计数,但它不对sess->user增加任何引用。
    • sess的指针在整个请求处理期间保持;每个需要认证数据的辅助函数直接访问sess->user
  3. 在工作线程A完成之前,工作线程B(在第一个连接C1上运行)处理同一会话的SMB2 LOGOFF并执行smb2_session_logoff()

smb2_session_logoff()的相关部分

1
2
3
4
5
6
    ...
    if (sess->user) {
        ksmbd_free_user(sess->user);   /* (1) 释放内存 */
        sess->user = NULL;             /* (2) 清空字段 */
    }
    ...

ksmbd_free_user()最终以简单的kfree(user)结束。

  1. 工作线程B不等待仍在使用该会话的其他连接;它只等待其自身连接上运行的请求(ksmbd_conn_wait_idle(conn))。
  2. 工作线程A继续执行(例如在smb2_open()smb2_write()等内部)并解引用sess->user
1
2
3
4
/* 示例(有很多) */
if (user_guest(sess->user))         释放后解引用
ksmbd_compare_user(sess->user, )
sess->user->uid

由于内存在步骤(1)中已被kfree()释放,访问的是已释放的内存。根据具体时机,可能是:

  • 释放后使用(指针仍指向现在已被重用的slab对象),启用受控的内核内存覆盖,或
  • NULL解引用(如果工作线程A在步骤(2)之后读取),仍然是DoS。

阅读这份报告,我感到对AI工具在漏洞研究中有用性的期望发生了转变。如果我们永远停留在o3现在能做的水平,每个从事VR的人仍然有必要弄清楚他们工作流程的哪些部分将从中受益,并构建工具将其接入。当然,部分接入工作将是弄清楚如何处理本例中约1:50的信噪比,但这是我们已经在取得进展的方面。

另一个有趣的点是,当我发现Kerberos认证漏洞时,我提出的修复如下:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
diff --git a/fs/smb/server/smb2pdu.c b/fs/smb/server/smb2pdu.c
index d24d95d15d87..57839f9708bb 100644
--- a/fs/smb/server/smb2pdu.c
+++ b/fs/smb/server/smb2pdu.c
@@ -1602,8 +1602,10 @@ static int krb5_authenticate(struct ksmbd_work *work,
     if (prev_sess_id && prev_sess_id != sess->id)
         destroy_previous_session(conn, sess->user, prev_sess_id);
  
-   if (sess->state == SMB2_SESSION_VALID)
+   if (sess->state == SMB2_SESSION_VALID) {
         ksmbd_free_user(sess->user);
+       sess->user = NULL;
+   }
     
     retval = ksmbd_krb5_authenticate(sess, in_blob, in_len,
                     out_blob, &out_len);
-- 2.43.0

当我阅读o3的上述错误报告时,我意识到这是不够的。注销处理程序已经设置了sess->user = NULL,但仍然容易受到攻击,因为SMB协议允许两个不同的连接"绑定"到同一会话,并且在Kerberos认证路径上没有任何东西可以防止另一个线程在sess->user被释放后到被设置为NULL之前的短窗口内使用它。我已经利用这个特性击中了ksmbd中的一个先前漏洞,但在考虑Kerberos认证漏洞时我没有想到这一点。

意识到这一点后,我再次检查了o3搜索Kerberos认证漏洞的结果,注意到在一些报告中它犯了和我一样的错误,在另一些报告中则没有,并且它意识到由于会话绑定的可能性,设置sess->user = NULL不足以修复问题。这很酷,因为这意味着如果我使用o3来发现和修复原始漏洞,理论上我会比没有它时做得更好。我说"理论上"是因为目前假阳性与真阳性的比率可能太高,无法确定我会以所需的细心程度检查o3的每个报告以发现其解决方案。不过,这个比率只会变得更好。

结论

LLM存在于程序分析技术能力空间中的一个点,远比我们见过的任何东西更接近人类。考虑到创造性、灵活性和通用性等属性,LLM更类似于人类代码审计员,而不是符号执行、抽象解释或模糊测试。自GPT-4以来,LLM在漏洞研究中的潜力已有暗示,但真实问题上的结果从未完全达到希望或炒作的程度。随着o3的改变,我们有了一个在代码推理、问答、编程和问题解决方面表现足够好的模型,可以真正增强人类在漏洞研究中的表现。

o3并非万无一失。远非如此。它仍然有相当大的机会产生荒谬的结果并让你感到沮丧。不同的是,第一次正确结果的几率足够高,值得你花时间和精力尝试将其用于真实问题。

comments powered by Disqus
使用 Hugo 构建
主题 StackJimmy 设计