名称: codeql 类型: 工具 描述: > CodeQL 是一个静态分析框架,将代码作为数据库查询。 当您需要跨过程分析或复杂数据流跟踪时使用。
CodeQL
CodeQL 是一个强大的静态分析框架,允许开发者和安全研究人员查询代码库以寻找特定代码模式。CodeQL 标准库实现了跨过程和过程内控制流与数据流分析的支持。然而,编写自定义查询的学习曲线陡峭,CodeQL 标准库的文档仍然匮乏。
何时使用
使用 CodeQL 当:
- 您需要在整个代码库中进行跨过程控制流和数据流查询
- 需要对抽象语法树、控制流图和数据流图进行细粒度控制
- 您希望防止已知漏洞和安全漏洞引入代码库
- 您拥有源代码和第三方依赖的访问权限(并且可以构建编译语言)
- 漏洞类别需要超出单文件模式匹配的复杂分析
考虑替代方案当:
- 单文件模式匹配足够 → 考虑 Semgrep
- 您没有源代码访问权限或无法构建项目
- 分析时间关键(复杂查询可能耗时较长)
- 您需要分析没有 GitHub Advanced Security 许可证的闭源代码库
- 语言不被 CodeQL 支持
快速参考
| 任务 | 命令 |
|---|---|
| 创建数据库 (C/C++) | codeql database create codeql.db --language=cpp --command='make -j8' |
| 创建数据库 (Go) | codeql database create codeql.db --language=go |
| 创建数据库 (Java/Kotlin) | codeql database create codeql.db --language=java |
| 创建数据库 (JavaScript/TypeScript) | codeql database create codeql.db --language=javascript |
| 创建数据库 (Python) | codeql database create codeql.db --language=python |
| 分析数据库 | codeql database analyze codeql.db --format=sarif-latest --output=results.sarif -- codeql/cpp-queries |
| 列出已安装包 | codeql resolve qlpacks |
| 下载查询包 | codeql pack download trailofbits/cpp-queries |
| 运行自定义查询 | codeql query run --database codeql.db -- path/to/Query.ql |
| 测试自定义查询 | codeql test run -- path/to/test/pack/ |
安装
安装 CodeQL
CodeQL 可以手动安装或在 macOS/Linux 上通过 Homebrew 安装。
手动安装:
导航到 CodeQL 发布页面 并下载适用于您架构的最新捆绑包。捆绑包包含 codeql 二进制文件、支持语言的查询库和预编译查询。
使用 Homebrew:
brew install --cask codeql
保持 CodeQL 更新
CodeQL 正在积极开发中。定期更新以受益于改进。
手动安装: 从 CodeQL 发布页面 下载新更新。
Homebrew 安装:
brew upgrade codeql
验证
codeql --version
核心工作流
步骤 1: 构建 CodeQL 数据库
要构建 CodeQL 数据库,您通常需要能够构建相应代码库。确保代码库处于干净状态(例如,运行 make clean、go clean 或类似命令)。
对于编译语言 (C/C++, Swift):
codeql database create codeql.db --language=cpp --command='make -j8'
如果使用 CMake 或外部构建,添加 --source-root 指定源文件树根目录:
codeql database create codeql.db --language=cpp --source-root=/path/to/source --command='cmake --build build'
对于解释语言 (Python, JavaScript):
codeql database create codeql.db --language=python
对于具有自动检测的语言 (Go, Java):
codeql database create codeql.db --language=go
对于复杂构建系统,使用 --command 参数传递构建命令。
步骤 2: 分析数据库
在数据库上运行预编译查询包:
codeql database analyze codeql.db --format=sarif-latest --output=results.sarif -- codeql/cpp-queries
输出格式包括 SARIF 和 CSV。SARIF 结果可以通过 VSCode SARIF Explorer 扩展 查看。
步骤 3: 审查结果
SARIF 文件包含发现项,带有位置、严重性和描述。导入到您的 IDE 或 CI/CD 管道中进行审查和修复。
安装第三方查询包
发布的查询包通过范围/名称/版本标识。例如:
codeql pack download trailofbits/cpp-queries trailofbits/go-queries
对于 Trail of Bits 公共 CodeQL 查询,请参阅 trailofbits/codeql-queries。
如何自定义
编写自定义查询
CodeQL 查询使用声明式、面向对象的语言 QL,具有类似 Java 的语法和类似 SQL 的查询表达式。
基本查询结构:
import cpp
from FunctionCall call
where call.getTarget().getName() = "memcpy"
select call.getLocation(), call.getArgument(0)
这选择所有作为 memcpy 第一个参数传递的表达式。
创建自定义类:
import cpp
class MemcpyCall extends FunctionCall {
MemcpyCall() {
this.getTarget().getName() = "memcpy"
}
Expr getDestination() {
result = this.getArgument(0)
}
Expr getSource() {
result = this.getArgument(1)
}
Expr getSize() {
result = this.getArgument(2)
}
}
from MemcpyCall call
select call.getLocation(), call.getDestination()
关键语法参考
| 语法/运算符 | 描述 | 示例 |
|---|---|---|
from Type x where P(x) select f(x) |
查询: 为所有 P(x) 为真的 x 选择 f(x) | from FunctionCall call where call.getTarget().getName() = "memcpy" select call |
exists(...) |
存在量词 | exists(FunctionCall call | call.getTarget() = fun) |
forall(...) |
全称量词 | forall(Expr e | e = arg.getAChild() | e.isConstant()) |
+ |
传递闭包 (1+ 次) | start.getASuccessor+() |
* |
自反传递闭包 (0+ 次) | start.getASuccessor*() |
result |
方法/函数输出的特殊变量 | result = this.getArgument(0) |
示例: 查找未处理的错误
import cpp
/**
* @name 未处理的错误返回值
* @id custom/unhandled-error
* @description 返回错误代码但未检查的函数调用
* @kind problem
* @problem.severity warning
* @precision medium
*/
predicate isErrorReturningFunction(Function f) {
f.getName().matches("%error%") or
f.getName().matches("%Error%")
}
from FunctionCall call
where
isErrorReturningFunction(call.getTarget()) and
not exists(Expr parent |
parent = call.getParent*() and
(parent instanceof IfStmt or parent instanceof SwitchStmt)
)
select call, "错误返回值未检查"
添加查询元数据
查询元数据在初始注释中定义:
/**
* @name 问题简短名称
* @id 范围/查询名称
* @description 问题详细描述
* @kind problem
* @tags security external/cwe/cwe-123
* @problem.severity error
* @precision high
*/
必填字段:
name: 标识问题的短字符串id: 唯一标识符 (小写字母、数字、/、-)description: 详细描述 (几句话)kind:problem或path-problemproblem.severity:error、warning或recommendationprecision:low、medium、high或very-high
输出格式要求:
problem查询: 输出必须是(Location, string)path-problem查询: 输出必须是(DataFlow::Node, DataFlow::PathNode, DataFlow::PathNode, string)
测试自定义查询
创建测试包并包含 qlpack.yml:
name: scope/name-test
version: 0.0.1
dependencies:
codeql-query-pack-to-test: "*"
extractor: cpp
创建测试目录 (例如 MemcpyCall/) 包含:
test.c: 源代码文件,包含要检测的代码模式MemcpyCall.qlref: 文本文件,包含查询路径MemcpyCall.expected: 预期输出
运行测试:
codeql test run -- path/to/test/pack/
如果 MemcpyCall.expected 缺失或不正确,会创建 MemcpyCall.actual 文件。审查它,如果正确,重命名为 MemcpyCall.expected。
高级用法
创建新查询包
初始化查询包:
codeql pack init <scope>/<name>
这会创建 qlpack.yml 文件:
---
library: false
warnOnImplicitThis: false
name: <scope>/<name>
version: 0.0.1
添加标准库依赖:
codeql pack add codeql/cpp-all
创建工作空间文件 (codeql-workspace.yml) 以便 CLI 正常工作。
安装依赖:
codeql pack install
配置 CLI 以找到您的查询,创建 ~/.config/codeql/config:
--search-path /full/path/to/your/codeql/root/directory
推荐目录结构
.
├── codeql-workspace.yml
├── cpp
│ ├── lib
│ │ ├── qlpack.yml
│ │ └── scope
│ │ └── security
│ │ └── someLibrary.qll
│ ├── src
│ │ ├── qlpack.yml
│ │ ├── suites
│ │ │ ├── scope-cpp-code-scanning.qls
│ │ │ └── scope-cpp-security.qls
│ │ └── security
│ │ └── AppSecAnalysis
│ │ ├── AppSecAnalysis.c
│ │ ├── AppSecAnalysis.qhelp
│ │ └── AppSecAnalysis.ql
│ └── test
│ ├── qlpack.yml
│ └── query-tests
│ └── security
│ └── AppSecAnalysis
│ ├── AppSecAnalysis.c
│ ├── AppSecAnalysis.expected
│ └── AppSecAnalysis.qlref
递归和传递闭包
递归谓词:
predicate isReachableFrom(BasicBlock start, BasicBlock end) {
start = end or isReachableFrom(start.getASuccessor(), end)
}
使用传递闭包 (等效):
predicate isReachableFrom(BasicBlock start, BasicBlock end) {
end = start.getASuccessor*()
}
使用 * 表示零次或多次应用,+ 表示一次或多次。
排除单个文件
CodeQL 检测构建过程。如果目标文件已存在且是最新的,相应的源文件不会添加到数据库。这可以减少数据库大小,但意味着 CodeQL 对排除文件只有部分了解,无法推理通过它们的数据流。
推荐: 包含第三方库,并根据位置过滤问题,而不是在数据库创建时排除文件。
编辑器支持
VSCode: CodeQL 扩展 提供 LSP 支持、语法高亮、查询运行和 AST 可视化。
Neovim: codeql.nvim 提供类似功能。
Helix/其他编辑器: 使用 CodeQL LSP 服务器和 Tree-sitter 语法 for CodeQL。
VSCode 快速查询: 使用 “CodeQL: Quick Query” 命令在数据库中运行单个查询。
调试查询: 添加数据库源到工作空间,然后使用 “CodeQL: View AST” 显示单个节点的 AST。
配置
CodeQL 标准库
CodeQL 标准库是语言特定的。请参考 API 文档:
支持的语言
CodeQL 支持 C/C++、C#、Go、Java、Kotlin、JavaScript、TypeScript、Python、Ruby 和 Swift。查看 支持的语言和框架 了解详情。
CI/CD 集成
GitHub Actions
从仓库设置中的 “Code security and analysis” 启用代码扫描。选择默认或高级设置。
高级设置工作流:
name: "CodeQL"
on:
push:
branches: [ "main" ]
pull_request:
branches: [ "main" ]
schedule:
- cron: '34 10 * * 6'
jobs:
analyze:
name: Analyze
runs-on: ${{ (matrix.language == 'swift' && 'macos-latest') || 'ubuntu-latest' }}
timeout-minutes: ${{ (matrix.language == 'swift' && 120) || 360 }}
permissions:
actions: read
contents: read
security-events: write
strategy:
fail-fast: false
matrix:
language: [ 'cpp' ]
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Initialize CodeQL
uses: github/codeql-action/init@v3
with:
languages: ${{ matrix.language }}
- name: Autobuild
uses: github/codeql-action/autobuild@v3
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v3
with:
category: "/language:${{matrix.language}}"
对于编译语言,用自定义构建命令替换 autobuild:
- run: |
make -j8
在 CI 中使用自定义查询
在 “Initialize CodeQL” 步骤中指定查询包和查询:
- uses: github/codeql-action/init@v3
with:
queries: security-extended,security-and-quality
packs: trailofbits/cpp-queries
对于仓库本地查询:
- uses: github/codeql-action/init@v3
with:
queries: ./codeql/UnhandledError.ql
packs: trailofbits/cpp-queries
注意仓库相对路径的前缀 .。所有查询必须是查询包的一部分,并包含 qlpack.yml 文件。
在 CI 中测试自定义查询
name: Test CodeQL queries
on: [push, pull_request]
jobs:
codeql-test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- id: init
uses: github/codeql-action/init@v3
- uses: actions/cache@v4
with:
path: ~/.codeql
key: ${{ runner.os }}-${{ runner.arch }}-${{ steps.init.outputs.codeql-version }}
- name: Run tests
run: |
${{ steps.init.outputs.codeql-path }} test run ./path/to/query/tests/
此工作流缓存查询提取和编译以加快后续运行。
常见错误
| 错误 | 为什么错误 | 正确方法 |
|---|---|---|
| 创建数据库前未构建项目 | CodeQL 不会有完整信息 | 运行 make clean 或等效命令,然后用 CodeQL 构建 |
| 从数据库中排除第三方库 | 阻止通过库代码的跨过程分析 | 包含库,根据位置过滤结果 |
| 在查询包中使用相对导入 | 导致解析问题 | 使用标准库的绝对导入 |
| 未添加查询元数据 | SARIF 输出缺乏严重性、描述 | 始终添加必填字段的元数据注释 |
| 忘记工作空间文件 | CLI 找不到查询包 | 在根目录创建 codeql-workspace.yml |
限制
- 许可: 闭源代码库需要 GitHub Enterprise 或 Advanced Security 许可证
- 构建要求: 编译语言必须可构建;无构建 = 不完整数据库
- 性能: 复杂跨过程查询在大型代码库上可能耗时较长
- 语言支持: 仅限于 CodeQL 支持的语言和框架
- 学习曲线: 编写自定义查询的学习曲线陡峭;文档匮乏
- 单语言数据库: 每个数据库针对一种语言;多语言项目需要多个数据库
相关技能
| 技能 | 何时一起使用 |
|---|---|
| semgrep | 首先使用 Semgrep 进行快速模式分析,然后使用 CodeQL 进行更深层的跨过程分析 |
| sarif-parsing | 用于在自定义 CI/CD 管道中处理 CodeQL SARIF 输出 |
资源
Trail of Bits 关于 CodeQL 的博客文章
学习资源
编写自定义 CodeQL 查询
视频资源
- Trail of Bits: CodeQL 介绍 - 示例、工具和 CI 集成
- 使用 CodeQL 查找 C/C++ 中的安全漏洞
- 使用 CodeQL 查找 JavaScript 中的安全漏洞
- 使用 CodeQL 查找 Java 中的安全漏洞