使用o3模型挖掘Linux内核SMB远程零日漏洞CVE-2025-37899

本文详细介绍如何利用OpenAI的o3模型,在无复杂框架或工具的情况下,发现了Linux内核SMB实现中的一个远程释放后使用零日漏洞CVE-2025-37899,并对AI在漏洞研究中的应用前景进行了探讨。

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

本文展示了我如何使用 OpenAI 的 o3 模型,在 Linux 内核中发现了一个零日漏洞。我发现这个漏洞仅使用了 o3 API,没有复杂的脚手架、没有智能体框架、也没有工具调用。

最近我一直在审计 ksmbd 中的漏洞。ksmbd 是“一个在用户空间实现 SMB3 协议以通过网络共享文件的 Linux 内核服务器”。我启动这个项目本意是想从 LLM 相关的工具开发中休息一下,但在 o3 发布后,我忍不住用我在 ksmbd 中发现的一些漏洞作为 o3 能力的快速基准测试。在未来的文章中,我将讨论 o3 在所有那些漏洞上的表现,但在这里我们将重点讨论 o3 在我的基准测试过程中是如何发现一个零日漏洞的。它发现的漏洞是 CVE-2025-37899(修复补丁在此),这是 SMB logoff 命令处理程序中的一个释放后使用漏洞。理解该漏洞需要对服务器并发连接以及它们如何在特定情况下共享各种对象进行推理。o3 能够理解这一点,并发现一个特定对象(该对象未被引用计数)被释放后,仍能被另一个线程访问的位置。据我所知,这是首次公开讨论由 LLM 发现的此类漏洞。

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

使用 CVE-2025-37778 对 o3 进行基准测试

首先讨论 CVE-2025-37778,这是一个我手动发现的漏洞,我正用它作为 o3 能力的基准测试,然后 o3 发现了零日漏洞 CVE-2025-37899。

CVE-2025-37778 是一个释放后使用漏洞。该问题发生在处理来自远程客户端的“会话建立”请求时的 Kerberos 认证路径中。为了便于称呼,我将此漏洞称为“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) 意识到代码库中还有其他部分可能在该变量被释放后访问它。
  • 虽然不简单,但也不算极其复杂。我可以在 10 分钟内向同事讲解整个代码路径,而且除了连接处理和会话建立代码外,你并不真正需要理解太多关于 Linux 内核、SMB 协议或 ksmbd 其余部分的辅助信息。我计算了如果阅读从数据包到达 ksmbd 模块到漏洞被触发路径上调用的每个 ksmbd 函数,最少需要阅读多少代码,结果大约是 3300 行代码。

好了,现在我们有了用于评估的漏洞,那么应该向 LLM 展示哪些代码以查看它是否能找到这个漏洞呢?我的目标是评估如果 o3 是一个假设的漏洞检测系统的后端,它会表现如何,所以我们需要明确这样一个系统将如何生成对 LLM 的查询。换句话说,如果我们无法清楚地描述一个自动化系统将如何选择这些函数,那么随意选择函数给 LLM 查看是没有意义的。理想情况下,我们给 LLM 一个代码库中的所有代码,它接收并输出结果。然而,由于上下文窗口的限制以及随着上下文量增加而出现的性能下降,目前这在实际中还不可行。

相反,我认为自动化工具为 LLM 生成上下文的一种可能方式是逐个展开每个 SMB 命令处理程序。因此,我给了 LLM session setup 命令处理程序的代码,包括它调用的所有函数的代码,依此类推,直到调用深度为 3(这是包含推理漏洞所需所有代码所需的深度)。我还包括了所有用于从网络读取数据、解析传入请求、选择要运行的命令处理程序以及在处理程序完成后拆除连接的代码。没有这些,LLM 将不得不猜测各种数据结构是如何设置的,这会导致更多的误报。最终,这总共大约是 3300 行代码(约 27k token),并为我们提供了一个可用于对比 o3 与先前模型的基准测试。

最终的决定是使用什么提示词。你可以在 这个 GitHub 仓库.prompt 文件中找到系统提示词和我提供给 LLM 的其他信息。需要注意的要点是:

  • 我告诉 LLM 寻找释放后使用漏洞。
  • 我简要地高度概括了 ksmbd 是什么、它的架构以及它的威胁模型。
  • 我试图强烈引导它不要报告误报,并倾向于不报告任何漏洞,而不是报告误报。

为了运行查询,我使用 llm 工具执行如下命令:

1
2
3
4
5
$ llm --sf system_prompt_uafs.prompt \
         -f session_setup_code.prompt \
         -f ksmbd_explainer.prompt \
         -f session_setup_context_explainer.prompt \
         -f audit_request.prompt

我的实验框架执行此命令 N 次(本实验 N=100)并保存结果。值得注意的是,如果你重新运行此命令,可能不会得到与我完全相同的结果。

o3 在基准测试的 100 次运行中,有 8 次发现了 Kerberos 认证漏洞。在另外 66 次运行中,o3 得出结论认为代码中没有漏洞(假阴性),其余 28 份报告是误报。作为比较,Claude Sonnet 3.7 在 100 次运行中发现了 3 次,而 Claude Sonnet 3.5 在 100 次运行中未发现。因此,至少在这个基准测试上,o3 比 Claude Sonnet 3.7 有 2-3 倍的改进。

有趣的一点是它们呈现结果的方式。使用 o3,你会得到感觉像是人类编写的漏洞报告,精简到只呈现发现,而使用 Sonnet 3.7,你会得到类似于思维流或工作日志的东西。两者各有优缺点。o3 的输出由于其结构和重点,通常更容易理解。另一方面,有时它过于简略,导致清晰度下降。

o3 发现一个零日漏洞 (CVE-2025-37899)

确认 o3 在给定会话建立命令处理程序代码的情况下能够找到 Kerberos 认证漏洞 (CVE-2025-37778) 后,我想看看如果我给它所有命令处理程序的代码,它是否还能找到。这是一个更难的问题,因为所有命令处理程序都在 smb2pdu.c 中,该文件大约有 9000 行代码。然而,如果 o3 在一次性给定所有处理程序时仍然能够发现漏洞,那么这表明我们可以为 o3 构建一个更简单的包装器,只需递交给它整个文件,涵盖各种功能,而不是逐个处理程序地分析。

将所有处理程序的代码与连接建立和拆除代码以及命令处理程序分发例程结合起来,最终大约有 12000 行代码(约 10 万输入 token),和之前一样,我运行了 100 次实验。

o3 在 100 次运行中,有 1 次使用这种更大输入 token 数量的配置发现了 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()
  4. 工作线程-B 不等待仍在使用该会话的其他连接;它只等待自己连接上正在运行的请求(ksmbd_conn_wait_idle(conn))。
  5. 工作线程-A 继续执行并解引用 sess->user。由于内存已在步骤 (1) 中被 kfree(),该访问是对已释放内存的访问。

看到这份报告,我对 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 之前的短暂窗口内使用它。

意识到这一点后,我再次查看了 o3 寻找 Kerberos 认证漏洞的结果,注意到在某些报告中它犯了和我一样的错误,而在另一些报告中则没有,并且它意识到由于会话绑定提供的可能性,设置 sess->user = NULL 不足以修复该问题。这很酷,因为这意味着如果我使用 o3 来发现和修复原始漏洞,理论上我会比没有它时做得更好。

结论

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

o3 并非完美无缺。远非如此。它仍然有很大几率会产生无意义的结果并让你感到沮丧。不同之处在于,首次,获得正确结果的几率足够高,值得你投入时间和精力尝试在真实问题上使用它。

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