终端优先的CodeQL多仓库变体分析工具mrva详解

本文介绍了mrva,一个终端优先的CodeQL多仓库变体分析工具,它允许开发者在本地机器上运行静态分析,提供了详细的安装使用指南,并与GitHub官方工具进行了全面对比。

安装和运行 mrva

首先,从 PyPI 安装 mrva:

1
$ python -m pip install mrva

或者,使用您喜欢的 Python 包安装程序,如 pipx 或 uv。

运行 mrva 大致可以分为三个步骤:

  1. 从 GitHub API 下载预构建的 CodeQL 数据库 (mrva download)。
  2. 使用 CodeQL 查询或查询包分析数据库 (mrva analyze)。
  3. 将结果输出到终端 (mrva pprint)。

让我们使用 Trail of Bits 的公共 CodeQL 查询来运行这个工具。首先下载前 1000 个 Go 项目数据库:

1
2
3
4
5
$ mkdir databases
$ mrva download --token YOUR_GH_PAT --language go databases/ top --limit 1000
2025-09-04 13:25:10,614 INFO mrva.main Starting command download
2025-09-04 13:25:14,798 INFO httpx HTTP Request: GET https://api.github.com/search/repositories?q=language%3Ago&sort=stars&order=desc&per_page=100 "HTTP/1.1 200 OK"
...

您也可以使用 $GITHUB_TOKEN 环境变量来更安全地指定您的个人访问令牌。此外,还有其他下载 CodeQL 数据库的策略,例如按 GitHub 组织 (download org) 或单个仓库 (download repo)。接下来,克隆查询并运行多仓库变体分析:

1
2
3
4
5
6
7
$ git clone https://github.com/trailofbits/codeql-queries.git
$ mrva analyze databases/ codeql-queries/go/src/crypto/ -- --rerun --threads=0
2025-09-04 14:03:03,765 INFO mrva.main Starting command analyze
2025-09-04 14:03:03,766 INFO mrva.commands.analyze Analyzing mrva directory created at 1757007357
2025-09-04 14:03:03,766 INFO mrva.commands.analyze Found 916 analyzable repositories, discarded 84
2025-09-04 14:03:03,766 INFO mrva.commands.analyze Running CodeQL analysis on mrva-go-ollama-ollama
...

根据您的数据库语料库大小、查询数量、查询复杂度和机器硬件,此分析可能需要相当长的时间。您可以通过将 --select--ignore 标志传递给 analyze 来筛选要分析的数据库。在 -- 之后传递的任何标志都将直接发送到 CodeQL 二进制文件。请注意,我们建议传递 --threads=0 让 CodeQL 处理并行化,而不是让 mrva 并行化多个 CodeQL 分析。这有助于避免父进程和子进程之间的 CPU 资源争用。

分析完成后,您可以打印结果:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
$ mrva pprint databases/
2025-09-05 10:01:34,630 INFO mrva.main Starting command pprint
2025-09-05 10:01:34,631 INFO mrva.commands.pprint pprinting mrva directory created at 1757007357
2025-09-05 10:01:34,631 INFO mrva.commands.pprint Found 916 analyzable repositories, discarded 84
tob/go/msg-not-hashed-sig-verify: Message must be hashed before signing/verifying operation

  builtin/credential/aws/pkcs7/verify.go (ln: 156:156 col: 12:31)
  https://github.com/hashicorp/vault/blob/main/builtin/credential/aws/pkcs7/verify.go#L156-L156

  155 if maxHashLen := dsaKey.Q.BitLen() / 8; maxHashLen < len(signed) {
  156    signed = signed[:maxHashLen]
  157 }

  builtin/credential/aws/pkcs7/verify.go (ln: 158:158 col: 25:31)
  https://github.com/hashicorp/vault/blob/main/builtin/credential/aws/pkcs7/verify.go#L158-L158

  157 }
  158 if !dsa.Verify(dsaKey, signed, dsaSig.R, dsaSig.S) {
  159    return errors.New("x509: DSA verification failure")
...

此发现是一个误报,因为消息确实被截断了,但更新查询的障碍列表超出了本文的范围。与前面的命令一样,pprint 也有许多可以影响其输出的标志。使用 --help 运行它以查看可用的选项。

快速说明:pprint 还能够漂亮地打印来自非 mrva CodeQL 分析的 SARIF 结果。也就是说,它解决了我对 CodeQL 的第一个也是最大的不满:为什么我不能以人类可读的形式获取 database analyze 的输出?如果您在运行 analyze 时使用了 --sarif-add-file-contents 标志,这将特别有用。输出 CSV 和 SARIF 对机器来说很好,但通常我只想立即在终端中查看结果。mrva 解决了这个问题。

比较 mrva 与 GitHub 工具

mrva 从 GitHub 的 CodeQL VS Code 扩展中汲取了很多灵感。GitHub 也提供了一个同名的非官方 CLI 扩展。然而,正如我们将看到的,这个扩展复制了许多与 VS Code 扩展相同的云优先工作流,而不是在本地运行所有内容。以下是这三种实现的总结:

特性 mrva gh-codeql vscode-codeql
需要 GitHub 控制器仓库
在 GitHub Actions 上运行
支持自托管运行器
在本地机器上运行
轻松修改 CodeQL 分析参数
本地查看发现
AST 查看器
使用 GitHub 搜索创建目标列表
自定义目标列表
导出/下载结果 ✅ (SARIF) ✅ (SARIF) ✅ (Gist 或 Markdown)

如您所见,mrva 的主要优点是能够在本地运行分析和查看发现。这使用户对分析选项有更多的控制权,并拥有其发现数据的所有权。一切只是磁盘上的文件——之后如何处置由您决定。

有趣的实现细节

在完成一个新项目后,我通常喜欢分享一些在此过程中学到的有趣的实现细节。这有助于揭开已完成任务的神秘面纱,为其他人提供有用的线索以走向不同的方向,或者简单地突出一些不寻常的东西。在这个项目中,我发现了三个特别有趣的细节:

  1. GitHub CodeQL 数据库 API
  2. 有用的 database analyze 标志
  3. 不同种类的 CodeQL 查询

CodeQL 数据库 API

尽管 mrva 在本地运行其分析,但它严重依赖于 GitHub 的预构建 CodeQL 数据库。构建 CodeQL 数据库可能非常耗时且容易出错,这就是为什么 GitHub 提供此 API 如此重要。许多最大的开源仓库会自动构建并提供相应的数据库。无论您的目标仓库是公开的还是私有的,都请配置代码扫描以启用此功能。

从 Trail of Bits 的角度来看,当我们在进行客户审计时,这很有帮助,因为我们可以轻松下载单个仓库的数据库 (mrva download repo) 或整个 GitHub 组织的数据库 (mrva download org)。然后,我们可以针对这些数据库运行我们自定义的 CodeQL 查询,而无需浪费时间自己构建它们。此功能对于针对大型开源代码语料库测试实验性查询也很有用。提供 CodeQL 数据库 API 使我们能够更快、更准确地工作,并为安全研究人员提供了一个测试平台。

Analyze 标志

在我研究 mrva 时,另一组我发现有用的功能是可以传递给 database analyze 的各种标志,尤其是关于 SARIF 输出的标志。其中一个特别突出:--sarif-add-file-contents。此标志在 SARIF 输出中包含文件内容,因此您可以将发现的文件位置与实际代码行交叉引用。这对于实现 mrva pprint 功能至关重要,并且避免了为了代码查找而独立管理源代码检出。

此外,--sarif-add-snippets 标志提供两行上下文,而不是整个文件。如果 SARIF 文件大小是一个问题,这可能是有益的。另一种在某些情况下有用的标志是 --no-group-results。此标志为每个消息提供一个结果,而不是为每个唯一位置提供一个结果。当您试图理解合并到单个位置的结果数量或可能出现在单行代码上的不同类型的查询时,这会很有帮助。这个标志和其他标志可以在运行 mrva 分析时通过像下面这样在双破折号后指定直接传递给 CodeQL:

1
$ mrva analyze <db_dir> <queries> -- --no-group-results ...

CodeQL 查询种类

使用 CodeQL 时,您会很快发现两种常见的查询类型:警报查询 (@kind problem) 和路径查询 (@kind path-problem)。警报查询使用基本的 select 语句来查询代码,就像您在 SQL 查询中期望看到的那样。路径查询用于数据流或污点跟踪分析。路径结果形成一系列从源到汇的代码位置,代表控制流图或数据流图中的一条路径。为此,这两种类型的查询在 SARIF 输出中也有不同的表示形式。例如,警报查询使用结果的 location 属性,而路径查询使用 codeFlows 属性。尽管使用频率较低,CodeQL 也支持其他类型的查询。

您还可以创建诊断查询 (@kind diagnostic) 和摘要查询 (@kind metric)。正如其名称所示,这些类型的查询有助于生成遥测和日志信息。也许最有趣的查询类型是图查询 (@kind graph)。这种查询用于 printAST.ql 功能,当与其他查询一起运行时,它将输出代码文件的抽象语法树 (AST)。在调试我自己的自定义查询时,我发现此功能非常宝贵。mrva 目前支持打印 AST 信息的实验性功能,我们有一个问题用于跟踪此功能的改进。

我怀疑使用图查询可以进行许多更有趣的分析类型,这是我将来热衷于深入研究的事情。例如,CodeQL 在运行图查询时也可以输出有向图形标记语言 (DGML) 或 Graphviz DOT 语言。这可以为检查代码时的数据流或控制流图可视化提供一种很好的方式。

在本地大规模运行

作为一个对 VS Code 又爱又恨的 Vim 用户,我着手构建 mrva 是为了为我们这些生活在终端中的人提供灵活性。我也很幸运,Trail of Bits 为我们提供了性能强大的笔记本电脑,可以快速处理静态分析工作,因此在本地对数千个项目运行复杂查询是可行的。终端优先的方法还支持无头运行和/或计划运行多仓库变体分析,如果您想将自动化漏洞查找纳入您的研究中。最后,我们经常有敏感的数据隐私需求,要求我们在本地运行作业,而不是将数据发送到云端。

我曾听人说,编写 CodeQL 查询需要程序分析博士学位。我不是博士,但有时我在研究查询时确实有这种感觉。然而,CodeQL 是那种您挖掘得越深,发现的就越多,几乎达到无限深度的工具之一。因此,我真的很喜欢更多地了解 CodeQL,并期待在未来更深入地探索。尽管我对 VS Code 有所保留,但没有 GitHub 和微软,这一切都不可能实现,所以我感谢他们对此工具的投资。CodeQL 数据库 API、丰富的标准查询库以及工具本身,使这一切成为可能。

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