谷歌 Agent 首次发现真实世界代码漏洞:抢救全球数亿设备,有望挽回数十亿美元损失
AI 首次在现实世界中发现重大安全漏洞!一个存在于 SQLite 中的漏洞,竟然被谷歌研究者的 AI Agent 及时辨识并修复,避免了潜在损失。难道 AI 在进化后,可以彻底消除微软的全球蓝屏事件吗?这样的话题令人激动不已。
难以置信,LLM 居然能够在实际代码中识别出漏洞?
想象一下,AI 正在默默保护着我们每天使用的软件,突然发现了一个我们可能忽视的安全隐患,并且迅速予以修复!
近日,谷歌的 Big Sleep 项目发布了一项惊人发现:SQLite 数据库中存在一个安全漏洞,而这个漏洞被 AI 成功识别,及时挽救了潜在危机。
来自谷歌 Project Zero 和 Google DeepMind 的团队表示,此次是 AI Agent 在广泛使用的软件中首次公开发现未知的内存安全问题的例子。
需要指出的是,这不仅是一个崩溃案例,而是 AI 在实际软件中首次找到可利用的内存漏洞。
此前,网络安全巨头 CrowdStrike 曾经历到的系统逻辑错误事件,瞬间致使全球约十亿台计算机崩溃,直接引发了微软的蓝屏故障,造成全球停滞。
假如未来的某一天,AI 能够帮助我们解决所有技术领域的瞬时故障,势必将为人类节省巨额财富。
用 LLM 在现实世界中“捕虫”
随着 LLM 的代码理解和推理能力提升,谷歌的研究者们一直探索这些模型如何在识别和展示安全漏洞时,重塑人类安全研究人员的方式。
在《Project Naptime:评估大型语言模型的攻防能力》一文中,Big Sleep 团队介绍了一个利用 LLM 辅助的漏洞研究框架,并通过在 Meta 的 CyberSecEval2 基准测试上提升性能,展示了这种方法的潜力。
此后,Naptime 项目发展为 “Big Sleep”,成为谷歌 Project Zero 和 Google DeepMind 的合作项目。
近期,谷歌研究者兴奋地披露,Big Sleep Agent 发现了第一个真实世界的漏洞:一个在 SQLite 中的可利用栈缓冲区下溢漏洞。
SQLite 是广泛使用的开源数据库引擎。
在十月初,Agent 发现了这个漏洞,并立即将其报告给开发者,开发者当天便进行了修复。
幸运的是,AI 在问题出现在正式发布前就进行了识别,因此 SQLite 用户未受影响。
SQLite 作为一种轻量级嵌入式数据库,广泛应用于智能手机、浏览器、嵌入式系统及物联网设备中,涉及众多用户和敏感信息。
如果攻击者利用此漏洞进行数据泄露、系统入侵或破坏,其潜在损失可能少则数百万,多则数十亿美元!
谷歌的研究者表示,这是 AI Agent 首次在广泛使用的真实世界软件中发现未知的内存安全问题的公开案例。
之所以展开此次尝试,源于今年早些时候,在 DARPA 的 AIxCC 活动中,亚特兰大团队在 SQLite 中发现了一个空指针取消引用漏洞,这引发了谷歌研究者的思考:
是否可以借助 SQLite 进行测试,查找是否存在更严重的漏洞呢?
果然,AI Agent 成功找出了一个漏洞。
这项工作展现了巨大的潜力。
在软件发布前发现漏洞,意味着攻击者没有机会加以利用:漏洞在他们能利用它之前就已被修复。
虽然模糊测试也能提供显著帮助,但更需要一种方法,帮助防御者识别那些模糊测试难以发现的漏洞。
现在,AI 有望缩小这一差距!
谷歌研究者表示,这是一条充满希望的道路,将为防御者带来不对称的优势。
由于这一漏洞异常有趣,而现有的 SQLite 测试基础设施(如 OSS-Fuzz 和项目自身的测试)未能发现此漏洞,因此谷歌研究者进行了深入研究。
方法架构
Naptime 和 Big Sleep 项目的核心驱动,就是不断发现并修复的漏洞变种。
显而易见,fuzzing(模糊测试)并不能很好的捕获此类变种漏洞,而对于攻击者而言,手动变种分析依旧是一种高效的选择。
谷歌研究者认为,相比更加宽泛的开放式漏洞研究,变种分析任务更适合当前的 LLM。
通过提供一个具体的起点——比如已修复漏洞的详细信息——可以降低漏洞研究中的不确定性,并从一个明确且理论上有支持的假设开始:“这里曾经存在漏洞,很可能在其他地方也存在类似问题。”
目前,他们的项目仍在研究阶段,正在使用带有已知漏洞的小型程序来评估研究进展。
最近,他们决定在 SQLite 上进行首次大规模真实环境变种分析实验,以测试他们的模型和工具链。
他们收集了 SQLite repository 最近的一系列提交,手动剔除无关的改动和纯文档更新。
随后,他们调整了提示,要求 AI Agent 同时查看提交信息和代码变动,并审查当前代码库(在 HEAD 位置)中可能仍未修复的相关问题。
Project Naptime
Naptime 项目通过专门的架构提高了大语言模型的漏洞研究能力,其核心是 AI Agent 和目标代码库之间的交互。
系统架构
为了让 AI Agent 能够模仿人类安全研究人员的工作流程,研究团队开发了一系列专用工具:
-
代码浏览工具(Code Browser)允许 AI Agent 浏览目标代码库,类似于工程师使用 Chromium Code Search 的方式。它可以查看特定实体(例如函数、变量等)的源代码,并识别函数或实体的引用位置。
-
Python 工具使 AI Agent 能够在隔离的沙盒环境中运行 Python 脚本,进行中间计算并生成精确且复杂的目标程序输入。
-
调试器工具(Debugger)为 AI Agent 提供程序交互能力,可以观察程序在不同输入下的表现。它支持设置断点并在断点处评估表达式,实现动态分析。
-
报告工具(Reporter)为 AI Agent 提供结构化的进度报告机制。AI Agent 可以发送完成任务的信号,触发控制器验证是否达到成功条件(通常表现为程序崩溃)。当未能取得进一步进展时,AI Agent 也可以主动中止任务,避免卡住。
发现漏洞
这个漏洞非常有趣,例如在一个通常为索引类型的字段 iColumn 中,使用了特殊的哨兵值 -1:
7476 struct sqlite3_index_constraint { 7477 int iColumn /* Column constrained. -1 for ROWID */ 7478 unsigned char op /* Constraint operator */ 7479 unsigned char usable /* True if this constraint is usable */ 7480 int iTermOffset /* Used internally - xBestIndex should ignore */ 7481 } *aConstraint /* Table of WHERE clause constraints */
这种模式引发了一个边缘案例,所有使用该字段的代码都需要正确处理这种情况,因为正常情况下,有效的列索引值应为非负值。
seriesBestIndex 函数在处理这个边缘案例时存在缺陷,当处理带有 rowid 列约束的查询时,会导致负索引写入堆栈缓冲区。
在研究者提供给 AI Agent 的编译版本中,启用了 debug assertion 功能,该异常会被第 706 行的断言捕获:
619 static int seriesBestIndex( 620 sqlite3_vtab *pVTab, 621 sqlite3_index_info *pIdxInfo 622 ){ ... 630 int aIdx[7]; /* Constraints on start, stop, step, LIMIT, OFFSET, 631 ** and value. aIdx[5] covers value=, value>=, and 632 ** value>, aIdx[6] covers value<= and value< */ 633 const struct sqlite3_index_constraint *pConstraint; ... 642 for(i=0; i<pIdxInfo->nConstraint; i++, pConstraint++){ 643 int iCol; /* 0 for start, 1 for stop, 2 for step */ 644 int iMask; /* bitmask for those column */ 645 int op = pConstraint->op; ... 705 iCol = pConstraint->iColumn - SERIES_COLUMN_START; 706 assert( iCol>=0 && iCol<=2 ); 707 iMask = 1 << iCol; ... 713 if( pConstraint->usable==0 ){ 714 unusableMask |= iMask; 715 continue; 716 }else if( op==SQLITE_INDEX_CONSTRAINT_EQ ){ 717 idxNum |= iMask; 718 aIdx[iCol] = i; 719 } 720 }
然而,在发布版本中,这个断言检查并不存在。
在研究者的测试环境中(具体表现会因编译器和优化级别而异),第 718 行的后续写入操作会越界写入 aIdx 缓冲区下方的内存区域,从而导致 pConstraint 指针的最低有效 32 位被破坏。
当被破坏的指针在下次迭代中被引用时,将会触发可利用的漏洞条件。
但是,即使拥有这样的漏洞说明,完全理解如何触发这一漏洞对于人类研究者来说依然困难重重。
尽管针对 ROWID 列的约束显然是很好的切入点,但要深入了解这个问题,仍需详细研读代码。
而 AI 智能体似乎已经掌握了比人类研究人员更多的 SQLite 知识,这使得它能够更高效地定位问题!
一个典型的例子是,LLM 在生成测试用例时直接使用了 generate_series 虚拟表。
(尽管研究者发现该模型会先查看可用的虚拟表情况,但显而易见它能够直接运用现有知识)。
执行过程重点分析
在这次基于 Gemini 1.5 Pro 的成功测试中,seed commit 是 [1976c3f7],这是一个较大且复杂的代码变更。
而 AI Agent 发现的漏洞与该 seed commit 中的变更关系较为松散——这种情况在手动变种分析中较为普遍,因为研究人员在理解代码库的某个漏洞时,常常会发现其他潜在问题。
以下是关键节点摘要,其中,所有 ASSISTANT 块中的内容均直接来自 AI Agent。
AI Agent 对差异中的变化进行了合理分析,并以此为研究的切入点:
ASSISTANT
引入 termFromWhereClause 函数,CL 改善了在虚拟表查询中处理 OR 分支共享的 WHERE 条件的方法。该函数考虑嵌套子句,从 WhereClause 中检索特定条件。
在此更改之前,由于条件索引映射不正确,共享条件可能无法正确处理,导致低效查询计划或错误结果。
最可能出现的问题涉及在 allocateIndexInfo 和 whereLoopAddVirtualOne 函数中处理 WHERE 子句约束的错误,CL 修改了使用新辅助函数处理约束偏移量的方式。
我将首先检查 allocateIndexInfo 函数。
经过数个步骤探查相关代码后,AI Agent 形成了一个更明确的测试假设——试图通过运行补丁中的测试用例修改版本来验证该假设,但未能成功。
原因在于补丁中的测试用例依赖于未在当前配置环境中启用的扩展模块:
TOOL(debugger_run )
程序在未崩溃或命中断点的情况下退出。退出代码为 1。
STDERR 输出为:
Runtime error near line 2: no such module: tcl
Parse error near line 3: no such table: x1
ASSISTANT
程序退出是因为 TCL 模块不可用。测试依赖于 TCL 虚拟表模块,需要在编译 SQLite 时启用该模块。提供的程序输入基于 bestindexC.test 文件,利用 TCL 模块测试虚拟表的最佳索引选择。
为了解决这个问题,需要更改程序输入,使用不需要 TCL 模块的其他虚拟表。然而,该漏洞与虚拟表的查询计划相关,因此仍需使用虚拟表触发此问题。
我将利用 code_browser_source 工具检索 SQLite 中的内置虚拟表示例。然后,我将调整输入,使用其中一个虚拟表,并重新在 whereLoopAddVirtualOne 中设置断点。
AI Agent 迅速了应对挑战,识别出修复测试用例所需的步骤,并在后续几个步骤中成功收集所有构建测试用例所需的信息,最终顺利触达 whereLoopAddVirtualOne 的目标:
ASSISTANT
sqlite3_create_module 函数用于注册虚拟表模块。shell.c 文件包含多个此类实例,包括 generate_series、fsdir、completion 和 expert 模块。其中,json 表的实现也值得关注。
我首先关注的是 generate_series,然后构造一个查询以触发错误的约束处理。
需要注意的是,AI Agent 在首次发现和复现该漏洞时,描述其试图触发的“不当约束处理”时颇为模糊。
在此情况下,完整的执行轨迹几乎与人类研究人员的思维过程相似——从某一想法开始,在调查过程中产生其他几个想法,最终生成可以复现略有不同漏洞的测试用例。
在这个例子中,当 whereLoopAddVirtualOne 在 generate_series 表上被调用时,执行流程最终进入 seriesBestIndex,约束确实被错误处理。
特别有趣的是,一旦程序崩溃,模型在解释问题时表现得更加出色——在生成根因分析和完整崩溃报告时,AI Agent 清晰地构造出一个触发特定已识别边缘案例的测试用例:
ASSISTANT
相关字段为 sqlite3_index_constraint 结构体中的 iColumn。该字段表示约束的列编号。要导致断言失败,需要设置索引大于 3 或小于-1 的列约束。
让我们构建一个涉及这些列约束的 SQL 查询。generate_series 表包含名为 value、start、step 和 stop 的列。它们的索引是:
– value: 0
– start: 1
– step: 2
– stop:3
为了触发这一漏洞,我们可以对 ROWID 施加约束,并对 ROWID 的约束使用 iColumn = -1。
示例查询如下:
SELECT * FROM generate_series(1,10,1) WHERE ROWID = 1;
此查询将导致 seriesBestIndex 函数崩溃。
最终,得出了清晰准确的问题总结,几乎可以直接用于漏洞报告!
TOOL(debugger_run)
程序崩溃:SIGABRT at :0
STDERR 输出为:
sqlite3: src/shell.c:6816: int seriesBestIndex(sqlite3_vtab *, sqlite3_index_info *): Assertion `iCol>=0 && iCol<=2\’ failed.
ASSISTANT(report_success)
程序因执行 SELECT * FROM generate_series (1,10,1) WHERE ROWID = 1; 触发 seriesBestIndex 函数中的断言失败而崩溃。
断言 assert (iCol>=0 && iCol<=2) 失败的原因是 sqlite3_index_info 结构体中的 iColumn 字段可能包含超出列索引有效范围的值,例如在约束涉及 ROWID 时为-1。
这导致 iCol 被计算为-2,从而引发断言失败。
关于模糊测试
一个显而易见的问题是:为什么传统模糊测试没有更早发现这个漏洞?
答案在于模糊测试工具链的配置。
OSS-Fuzz 使用的工具并未启用 generate_series 扩展,而替代的 fuzzingshell.c 工具包含的是旧版本的 seriesBestIndex 函数,未受该漏洞影响。
虽然 SQLite AFL 仓库中包含一个针对研究者提供给 Big Sleep 智能体的同一 CLI 二进制文件的模糊测试配置,但似乎并未被广泛使用。
那么,这个漏洞是否容易发现?
为了解答,研究者尝试通过模糊测试重新识别该漏洞。
他们遵循 SQLite 文档中的模糊测试指南,并使用 CLI 目标。启动 AFL 运行前,验证模糊测试语料库是否包含必要的 generate_series 和 rowid 关键字。
然而,经过 150 个 CPU 小时的模糊测试,漏洞依然未被发现。
随后,他们尝试在 AFL 的 SQL 字典中添加必要的关键字,以简化模糊测试的过程。
然而,似乎只有当语料库中包含与导致崩溃的输入非常接近的实例时,漏洞才会被快速发现,因为在此特定问题中,代码覆盖率并不可靠。
诚然,AFL 并不适合像 SQL 这样的基于文本的格式进行模糊测试,大多数输入在语法上无效,都会被解析器拒绝。
然而,将此结果与 Michal Zalewski 在 2015 年关于模糊测试 SQLite 的博客文章进行对比会发现十分有趣。
当时,AFL 在发现 SQLite 漏洞方面表现出色;经过多年的模糊测试,该工具似乎已达到自然饱和点。
尽管研究者至今的结果与 AFL 发布时带来的显著效果相比显得微不足道,但它有一定优势——有可能有效发现一类不同的漏洞。
结论
对于团队而言,这个项目无疑取得了成功。
在广泛使用且模糊化的开源项目中发现漏洞,真是令人振奋!
这意味着:当提供合适工具时,当前的 LLMs 可以进行漏洞研究。
不过,研究者重申,这些都是高度实验性的结果。
Big Sleep 团队表示:目前,针对特定目标的模糊器在发现漏洞方面可能同样有效。
研究者希望这一工作未来能为防御者带来显著优势——不仅能找到导致崩溃的测试用例,还能提供高质量的根本原因分析,使问题分类和修复变得更加经济有效。
谷歌的研究者表示,将继续分享研究成果,力求缩小公共技术前沿和私有技术前沿之间的差距。
Big Sleep 团队也将继续努力,推进零日计划使命,让 0-day 攻击变得更加困难。
团队介绍
Dan Zheng
团队中的华人 Dan Zheng 是谷歌 DeepMind 的研究工程师,专注于代码和软件工程中的机器学习及编程语言研究。
在此之前,他曾参与 Swift for TensorFlow 项目,致力于 Swift 中的可微分编程。
他在普渡大学获得计算机科学学士学位。毕业后做了多年的学生研究员,积累了丰厚的研究成果。
参考资料:
-
https://googleprojectzero.blogspot.com/2024/10/from-naptime-to-big-sleep.html
本文来自微信公众号:微信公众号(ID:null),作者:新智元