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

本文详细介绍了如何利用OpenAI的o3模型发现Linux内核ksmbd模块中的远程零日漏洞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”命令处理程序中的一个释放后使用(use-after-free)漏洞。理解这个漏洞需要推理服务器上的并发连接,以及它们在特定情况下如何共享各种对象。o3能够理解这一点,并发现一个特定对象在没有引用计数的情况下被释放,而另一个线程仍然可以访问它。据我所知,这是首次公开讨论由LLM发现的此类漏洞。

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

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

首先讨论CVE-2025-37778,这是我手动发现的一个漏洞,我正在用它作为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行代码。

好了,我们有了用于评估的漏洞,现在我们应该向LLM展示什么代码来看它是否能找到?我的目标是评估o3作为假设漏洞检测系统后端时的表现,所以我们需要明确这样的系统如何生成对LLM的查询。换句话说,如果我们不能清楚地描述自动化系统如何选择这些函数,那么任意选择函数给LLM看是没有用的。LLM的理想使用方式是,我们给它一个仓库中的所有代码,它吸收并吐出结果。然而,由于上下文窗口的限制和随着上下文量增加而出现的性能回归,这在目前实际上是不可能的。

相反,我认为自动化工具为LLM生成上下文的一种可能方式是通过单独扩展每个SMB命令处理程序。所以,我给了LLM“会话设置”命令处理程序的代码,包括它调用的所有函数的代码,依此类推,直到调用深度为3(这是包含推理漏洞所需所有代码的深度)。我还包括了从线上读取数据、解析传入请求、选择要运行的命令处理程序以及在处理程序完成后拆除连接的所有函数的代码。没有这些,LLM将不得不猜测各种数据结构是如何设置的,这会导致更多的误报。最终,这大约是3.3k行代码(约27k tokens),并给出了一个我们可以用来对比o3与之前模型的基准测试。如果你感兴趣,要分析的代码在这里作为一个单独的文件,使用files-to-prompt工具创建。

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

  • 我告诉LLM寻找释放后使用漏洞。
  • 我给了它一个简要的高级概述,说明ksmbd是什么,它的架构以及它的威胁模型是什么。
  • 我试图强烈引导它不要报告误报,并倾向于不报告任何错误而不是报告误报。我不知道这是否有帮助,但我希望它有帮助,所以就这样了。事实上,我的整个系统提示都是推测性的,因为我还没有运行足够多的评估来确定它是帮助还是阻碍,所以认为它等同于我祈祷,而不是任何类似于科学或工程的东西。一旦我运行了这些评估,我会让你知道。

为了运行查询,我然后使用llm工具(github)如下:

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,你得到的东西感觉像人写的错误报告,浓缩到只呈现发现,而使用Sonnet 3.7,你得到的东西像思维流或工作日志。两者都有优缺点。o3的输出由于其结构和重点通常更容易理解。另一方面,有时它太简短,清晰度受到影响。

o3发现一个0日(CVE-2025-37899)

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

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

o3在这个更大输入tokens的情况下,在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
comments powered by Disqus
使用 Hugo 构建
主题 StackJimmy 设计