使用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在基准测试过程中如何发现一个零日漏洞。

漏洞概述

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

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

首先讨论CVE-2025-37778,这是我手动发现的一个漏洞,并用作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。

实验设置

为了评估o3的性能,我选择了“会话设置”命令处理程序的代码,包括它调用的所有函数,等等,直到调用深度为3(这是推理漏洞所需的所有代码的深度)。我还包括了从线上读取数据、解析传入请求、选择要运行的命令处理程序以及在处理程序完成后拆除连接的所有函数代码。最终,这大约有3.3k行代码(~27k tokens),并给出了一个我们可以用来对比o3与先前模型的基准。

提示设计

提示设计的关键点包括:

  • 我告诉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发现零日漏洞(CVE-2025-37899)

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

将所有处理程序的代码与连接设置和拆除代码以及命令处理程序分发例程结合起来,最终大约有12k行代码(~100k输入tokens),和之前一样,我运行了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的解释

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()(smb2pdu.c)。

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的每个报告以发现其解决方案。不过,这个比率只会变得更好。

结论

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

o3并非万无一失。远非如此。仍然有相当大的机会它会生成无意义的结果并让你沮丧。不同的是,第一次获得正确结果的机会足够高,值得你花时间和精力尝试在真实问题上使用它。

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