一个由 GitHub Security Lab Taskflow Agent 驱动的 LLM 驱动模糊测试流水线
一个由 LLM 驱动的、OSS-Fuzz 风格的针对原生 C/C++ 项目的模糊测试流水线。 使用 AFL++ 执行,clang+lcov 进行覆盖率统计,LLM 代理负责 测试框架编写、覆盖率反馈决策、分类和报告。
此仓库包含
GitHub Security Lab Taskflow Agent 的模糊测试任务流。
它依赖于
seclab-taskflows
配套仓库中的一些共享构建块
(fetch_source_code 任务流、local_file_viewer / gh_file_viewer
工具箱,以及默认的 model_config)——这些会作为 Python 依赖项
自动安装。
欢迎贡献!请参阅 CONTRIBUTING.md 了解指南。
apt 的 Linux 环境(或 Codespace)gh)pip install git+https://github.com/GitHubSecurityLab/seclab-taskflows-fuzzing
这会传递性地引入 `seclab-taskflow-agent` 和 `seclab-taskflows`(父级),因此所有形如
`seclab_taskflows.taskflows.audit.*`、
`seclab_taskflows.toolboxes.local_file_viewer`、
`seclab_taskflows.toolboxes.gh_file_viewer` 以及
`seclab_taskflows.configs.model_config` 的点分引用在运行时都会从父级发行版中解析。
---
## 目录
1. [这是什么](#what-this-is)
2. [快速开始](#quick-start)
3. [架构](#architecture)
4. [流水线,逐阶段解析](#the-pipeline-stage-by-stage)
5. [覆盖率反馈循环](#the-coverage-feedback-loop)
6. [结构感知模糊测试](#structure-aware-fuzzing)
7. [跨迭代和跨活动的持久化语料库](#persistent-corpus-across-iterations-and-campaigns)
8. [分类与漏洞报告](#triage-and-vulnerability-reports)
9. [实时仪表盘](#live-dashboard)
10. [输出文件](#output-files)
11. [数据库模式](#database-schema)
12. [MCP 工具(智能体的词汇表)](#mcp-tools-the-agents-vocabulary)
13. [可调参数(环境变量)](#tunable-knobs-environment-variables)
14. [扩展流水线](#extending-the-pipeline)
15. [基准项目与结果](#benchmark-projects-and-results)
16. [局限性与注意事项](#limitations-and-gotchas)
17. [安全警告](#security-warning)
18. [开发:测试、代码检查、贡献](#development-testing-linting-contributing)
19. [术语表](#glossary)
---
## 这是什么
此任务流是一个完全自主的模糊测试流水线。给定一个原生 C/C++ 项目的 GitHub 仓库,它将:
1. 在缺失时安装 AFL++ + clang/llvm/lcov + ctags/cscope/graphviz,
2. 获取源代码,
3. 识别候选模糊测试目标(解析器、解码器、验证器等),
4. 分析构建系统,
5. 为每个目标编写一个或多个测试桩候选,将每个构建为 AFL 插桩的 `.afl` 二进制文件和覆盖率插桩的 `.cov` 二进制文件,
6. (可选)通过 60 秒覆盖率对候选进行资格筛选并保留最佳者,
7. 运行模糊测试/覆盖率/改进循环,时间预算逐轮翻倍,
8. 对每个崩溃进行分类,确认先前已知的崩溃仍可复现,并编写每个崩溃的 markdown 漏洞报告,包含判定、可利用性、建议补丁和回归测试草图,
9. 为下一轮活动构建 Fuzz-Introspector 风格的调用图 + 未触及 API 报告,
10. 将所有内容发布到实时 HTML 仪表盘。
该流水线在精神上是 **OSS-Fuzz 风格**的:它使用许多相同的技术(按格式的变异器和字典、结构感知的令牌拼接、覆盖率驱动的测试桩改进、机器可读报告、去重的栈哈希崩溃),但它要小得多且自包含。
---
## 快速开始```bash
# Inside the codespace (or a host with python + git available):
./scripts/fuzzing/run_fuzzing.sh tukaani-project/xz
这就是整个界面。该脚本是自主运行的;它会在首次运行时安装 AFL++,然后驱动其余的任务流程。输出文件写入
~/.local/share/seclab-taskflow-agent/seclab-taskflows/。
仪表盘会在后台自动启动;在 Codespace 中,端口 8765 会被
自动转发——在任意浏览器中打开它即可实时查看进度。
如需快速冒烟测试,请使用一个小型目标:```bash ./scripts/fuzzing/run_fuzzing.sh DaveGamble/cJSON
---
## 架构
三层结构,自上而下:```
┌────────────────────────────────────────────────────────────────────┐
│ scripts/fuzzing/run_fuzzing.sh │
│ shell driver; chains the taskflow stages with `set +e` │
└────────────────────┬───────────────────────────────────────────────┘
│
▼
┌────────────────────────────────────────────────────────────────────┐
│ src/seclab_taskflows/taskflows/fuzzing/*.yaml │
│ LLM agent prompts; one YAML per pipeline stage │
└────────────────────┬───────────────────────────────────────────────┘
│ (calls MCP tools)
▼
┌────────────────────────────────────────────────────────────────────┐
│ src/seclab_taskflows/mcp_servers/ │
│ ├ fuzz_context.py persistence (SQLite via SQLAlchemy) │
│ └ fuzz_runner.py subprocess wrappers (AFL, clang, lcov, ...) │
│ │
│ scripts/fuzzing/dashboard.py │
│ read-only HTML view of fuzz_context.db │
└────────────────────────────────────────────────────────────────────┘
关键设计规则:
fuzz_context.db 中。run_afl_for、compile_harness、store_crash 等。afl-clang-lto -fsanitize=address,undefined(即 .afl 二进制文件),一次使用 clang -fprofile-instr-generate -fcoverage-mapping(即 .cov 二进制文件)。.afl 二进制文件用于 fuzz;.cov 二进制文件重放 AFL 队列,以生成真实的源码行/函数/分支覆盖率。每个阶段都是一个自包含的 taskflow YAML,由智能体端到端运行。各阶段仅通过 fuzz_context.db 中的 SQLite 数据库通信——不存在内存中的交接。
这是流水线的核心。时间预算每次迭代翻倍:``` 30s → 60s → 120s → 240s → 480s → 960s (≈ 32 min/target)
每次迭代、每个 harness,agent 会:
1. 调用 `get_persistent_corpus_dir(harness_id)` 获取该 harness 的稳定
corpus 目录。
2. 调用 `run_afl_for(afl_binary_path, seed_dir=<persistent corpus>,
output_dir=<run dir>, seconds=<budget>, dictionary=<auto.dict>)`。
3. 调用 `run_coverage(cov_binary_path, inputs_dir=<run>/default/queue,
output_dir=<run>/coverage)` 生成 LCOV tracefile 和 HTML 报告。
4. 调用 `store_coverage_from_lcov(run_id, lcov_path, html_path)` 持久化
一条 `coverage_report` 记录 + 每个未覆盖项的 `coverage_gap` 记录。
5. 调用 `fold_queue_into_persistent_corpus(...)` 将 AFL 的迭代
queue 合并到持久化 corpus 中,并运行 `cmin` 以保持大小有界。
6. 读取 `get_coverage_summary` + `get_coverage_gaps`,然后要么:
- 添加一个新种子(标记为 `coverage_feedback`)以到达未覆盖的
分支,
- 编辑 harness 源码以调用额外的 API,
- 调用 `enrich_dictionary_from_uncovered(...)` 自动为 AFL 满足 guard 所需的 magic 常量添加字典
条目,或者
- 跳过该 gap(冷错误路径 / 厂商代码)。
7. 调用 `store_iteration_note(repo, iteration_number, harness_id, note=<one
line summary>)`,以便 dashboard 的迭代时间线跟踪
变更内容。
**平台期检测。** 一旦连续两次迭代的行覆盖率绝对百分点增益均 < `FUZZ_PLATEAU_THRESHOLD_PCT`(默认 `1.0`),循环就会提前退出。
---
## 结构感知模糊测试
三种互补机制可产生比原始字节变异更强的输入。
### 1. 按格式的字典 + 自定义变异器
对于 `input_kind` 匹配已知格式的目标,taskflow 附带
预构建的字典和 `LLVMFuzzerCustomMutator` C 源文件:
| 格式 | 字典 | 变异器 | 备注 |
|--------|------------|---------|-------|
| `json` | `json.dict` | `json_mutator.c` | Token 拼接、平衡括号复制/删除、类型翻转 |
| `xml` | `xml.dict` | `xml_mutator.c` | 标签、实体、DTD、billion-laughs token |
| `regex` | `regex.dict` | `regex_mutator.c` | 锚点、字符类、量词、真实 ReDoS 模式 |
| `binary_tlv` | _(none)_ | `binary_tlv_mutator.c` | 长度前缀记录:长度溢出 / 复制 / 删除 |
| `png` | `png.dict` | _(reuses binary_tlv)_ | PNG 字典 + binary_tlv 变异器 |
这些会被 `write_initial_harnesses`(字典复制到种子旁边)和
`build_harnesses`(变异器链接到 AFL 二进制文件中)自动拾取。每个变异器将 50% 的变异委托给 AFL 的默认字节
变异器,这样我们就不会丢失引擎的随机化能力。
要添加新格式:将 `<name>.dict` 和/或 `<name>_mutator.c` 放入
`src/seclab_taskflows/dictionaries/`,然后在
`fuzz_runner.py` 底部的 `_FORMAT_ASSETS` 映射中注册它。
### 2. 源码感知(项目特定)智能变异器
对于不熟悉的格式,或者当你想要更强的项目特定
token 时,`generate_smart_mutator` 会扫描目标仓库自身的 `.c`/`.h`
文件,并生成一个 `LLVMFuzzerCustomMutator` C 文件,其拼接
字典提取自:
- 具有 ≥3 个字母字符的字符串字面量(在过滤编译器/许可证
噪声、路径、头文件、asm 约束、格式说明符之后),
- 来自 `#define`、`case` 和 `enum` 的 32 位数值常量(在
过滤 0、1、256、0xff… 等通用小整数噪声之后)。
有三种 focus 可用:
| Focus | 拼接内容 | 使用时机 |
|-------|-----------------|-------------|
| `strings` | 仅项目字符串字面量 | 文本格式(JSON、XML、YAML、CSV) |
| `constants` | 仅 32 位数值 magic 值 | 二进制协议、带 magic number 的头文件 |
| `combined` | 两者 | 默认;通常最佳 |
将 `generate_smart_mutators(...)`(复数)与 `HARNESS_CANDIDATES >= 3`
配对,这样每个 focus 都会成为资格轮中的一个候选 harness。
### 3. 项目感知 AFL 字典 + 覆盖率驱动增强
两个互补工具会在 campaign 进行过程中构建并扩展 AFL `-x` 字典:
- **`generate_project_dictionary(source_root, output_path)`** — 在第 1 次迭代前运行一次,静态提取与
智能变异器相同的源码 token 集,并将其写为 AFL 字典。数值常量
会以两种字节序同时输出,这样无论主机字节序如何,fuzzer 都能满足
`memcmp(x, &magic, 4)`。
- **`enrich_dictionary_from_uncovered(source_root, dictionary_path,
uncovered_locations)`** — 在每次迭代的覆盖率步骤之后运行,
扫描未覆盖行附近的条件 guard
(`strncmp/memcmp/strstr`、`case 0xN:`、`== 0xN`、`== 'X'`),并将任何新 token 追加到字典中。幂等:
绝不会重新添加已存在的条目。
### 4. Corpus 拼接操作
当将 `corpus_dir` 传递给 `generate_smart_mutator` 时,生成的 C
还会获得一个 corpus 拼接操作符:首次调用时,它会从该目录加载最多 64 个文件
(每个上限为 4 KiB),此后可以将这些文件的随机子区域拼接到变异后的输入中。这为
变异器提供了一个重组式操作符,而 AFL 自带的 havoc 在这方面做得不好。与 `get_persistent_corpus_dir(...)` 配对,这样拼接库
就是“重新混合 AFL 已经发现的内容”。
---
## 跨迭代和 campaign 的持久化 corpus
每个 harness 都有一个稳定的 corpus 目录,位于:```
<workspace>/corpus/harness_<id>/
fuzz_iteration 将此处用作 run_afl_for 的 seed_dir(而非 <harness>/seeds)。每次迭代结束时,fold_queue_into_persistent_corpus(...) 会将 AFL 的迭代队列合并到此目录,并运行 afl-cmin 以保持其大小有界。
结果是:昨天的队列会延续到今天的运行中,并且跨同一项目的多次重运行持续存在。停止并重启活动不会丢失任何进度。
在模糊测试/覆盖率/改进循环结束后,三个阶段会自动运行:
triage_crashes对于 <run>/default/crashes/ 中的每个崩溃文件:
afl-tmin 最小化输入,replay_under_asan 捕获堆栈跟踪和 stack_top_hash
(前 N 个规范化帧;模板、libcxx 内联命名空间、匿名
命名空间和 LTO 数字后缀会被剥离,以便语义
相同的崩溃具有相同的哈希值),crash 记录,包含缺陷类别分类 +
置信度说明(高 / 中 / 低)。confirm_fixed_crashes通过当前的 AFL+ASan 二进制文件重放每个先前已分类的崩溃(其判定结果尚未为
fixed/duplicate/non_reproducible)。
如果它不再崩溃,则标记 verdict="fixed"。当针对自上次活动以来已应用上游修复的项目重新运行活动时,此功能非常有用。
write_vuln_reports对于每个唯一崩溃,代理会读取测试框架源代码 + 崩溃 函数的源代码,从公共 API 遍历调用链,然后分配 十种 OSS-Fuzz 风格判定结果之一,并编写 markdown 漏洞报告:
每个漏洞报告包括:
仪表板由 run_fuzzing.sh 自动在后台启动。使用 FUZZ_NO_DASHBOARD=1 禁用;使用 FUZZ_DASHBOARD_PORT 覆盖端口(默认 8765)。
在 Codespace 中,端口 8765 会自动转发——在任何浏览器中打开转发的 URL。页面每 5 秒自动刷新,并显示:
fuzz_runvulnerability 优先),链接
到每个漏洞报告和最小化输入仪表板还暴露了一个小型只读 JSON API 供脚本使用:```bash
curl http://127.0.0.1:8765/api/json
curl 'http://127.0.0.1:8765/api/json?repo=kkos/oniguruma' | jq .
---
## 输出文件
全部位于 `~/.local/share/seclab-taskflow-agent/seclab-taskflows/` 下。
| 路径 | 内容 |
|------|----------|
| `fuzz_context/fuzz_context.db` | SQLite — 目标、测试框架、运行记录、覆盖率、崩溃、判定结果、调用图、测试框架建议、迭代笔记 |
| `fuzz_runner/builds/` | 构建的 `.afl` 和 `.cov` 二进制文件 |
| `fuzz_runner/runs/` | AFL 输出目录 + LCOV 文件 + HTML 覆盖率报告 |
| `fuzz_runner/corpus/harness_<id>/` | 每个测试框架的持久化语料库(跨迭代和活动周期保留) |
| `fuzz_runner/repo/<owner>__<repo>/REPORT.md` | Markdown 活动摘要,崩溃按判定结果分组 |
| `fuzz_runner/repo/<owner>__<repo>/vuln_<crash_id>.md` | 每个崩溃的 Markdown 漏洞报告 |
| `fuzz_runner/repo/<owner>__<repo>/call_graph.{dot,svg,md}` | 静态调用图 + 已到达/未到达覆盖层 |
---
## 数据库模式
`fuzz_context.db` 中的表(通过 SQLAlchemy 使用 SQLite):
| 表 | 关注的列 |
|-------|--------------------|
| `fuzz_target` | `repo, file, function, signature, input_kind` |
| `harness` | `target_id, repo, harness_path, afl_binary_path, cov_binary_path, build_status, version, sanitizers` |
| `seed_corpus` | `target_id, source, path, bytes_count, added_in_iteration` |
| `fuzz_run` | `harness_id, iteration_number, exec_per_sec, paths_total, crashes_count, status, output_dir, started_at, ended_at` |
| `coverage_report` | `run_id, lines_total, lines_hit, line_pct, fns_*, branches_*, lcov_path, html_path` |
| `coverage_gap` | `report_id, file, function, line, kind, reason_hint` |
| `crash` | `run_id, input_blob_path, minimized_path, stack_top_hash, sanitizer_output, verdict, bug_class, cwe, severity, vuln_report_path, reproducer_path, classification, notes` |
| `call_graph` | `repo, target_id, dot_path, svg_path, functions_total, functions_in_graph, functions_reached, functions_unreached, untouched_surface_json` |
| `harness_suggestion` | `repo, function_name, file, rationale, input_kind, priority` |
| `iteration_note` | `repo, harness_id, iteration_number, note, created_at` |
模式迁移位于 `fuzz_context.py` 中的 `_migrate()`。新表由
`Base.metadata.create_all()` 自动创建;只有新增列需要
基于 PRAGMA 的 `ALTER TABLE`。
---
## MCP 工具(智能体的词汇表)
智能体从不直接调用 AFL 或 clang — 它通过调用 MCP 工具来组合流水线。
完整工具集按用途分组如下:
### 持久化(`fuzz_context.py`)
- `store_fuzz_target`, `get_fuzz_targets`
- `store_harness`, `update_harness_build`, `get_harnesses`
- `store_seed`, `start_fuzz_run`, `finish_fuzz_run`, `get_fuzz_runs`
- `store_coverage_from_lcov`, `get_coverage_summary`, `get_coverage_gaps`,
`coverage_plateau_reached`
- `store_crash`, `update_crash_verdict`, `get_crashes`,
`get_crashes_grouped`, `suggest_severity`
- `store_call_graph`, `get_call_graphs`, `get_repo_reached_functions`
- `store_harness_suggestion`, `get_harness_suggestions`
- `store_iteration_note`, `get_iteration_notes`
### 构建 / 模糊测试 / 覆盖率(`fuzz_runner.py`)
- `check_tooling`, `workspace_paths`
- `compile_harness` — 构建 `.afl` 和 `.cov` 二进制文件
- `run_afl_for`, `cmin`, `tmin`, `replay_under_asan`, `reproduce_crash`
- `run_coverage` — 针对 `.cov` 二进制文件重放 AFL 队列,导出 LCOV
- `extract_dictionary` — 从二进制文件中挖掘可打印字符串
- `package_reproducer` — 打包单个崩溃的 `.tgz`
### 持久化语料库(v8)
- `get_persistent_corpus_dir`, `fold_queue_into_persistent_corpus`
### 格式资产(C5)
- `list_format_assets`, `get_format_dictionary`, `write_format_mutator`
### 智能变异器 + 项目感知字典
- `generate_smart_mutator`, `generate_smart_mutators`
- `generate_project_dictionary`, `enrich_dictionary_from_uncovered`
工具函数使用 `@mcp.tool()`(FastMCP)装饰。在测试中,
通过 `.fn` 属性调用它们,例如
`fr.run_afl_for.fn(afl_binary_path=..., ...)`。
---
## 可调参数(环境变量)
| 变量 | 默认值 | 用途 |
|----------|---------|---------|
| `HARNESS_CANDIDATES` | `1` | 每个目标生成的候选测试框架数量。设为 2 或 3 以进行 OSS-Fuzz-Gen 风格的竞争。资格筛选阶段对每个候选运行 `QUALIFIER_SECONDS` 秒,并按行覆盖率百分比保留最佳者。 |
| `QUALIFIER_SECONDS` | `60` | 资格筛选阶段中每个候选的墙钟时间预算。 |
| `FUZZ_PLATEAU_THRESHOLD_PCT` | `1.0` | 行覆盖率增益(以绝对百分点计)低于此值时,连续两次迭代被视为进入平台期,循环提前停止。 |
| `FUZZ_DASHBOARD_PORT` | `8765` | 实时仪表盘的端口。 |
| `FUZZ_NO_DASHBOARD` | (未设置) | 设为 `1` 以跳过启动仪表盘。 |
| `FUZZ_RUNNER_TIMEOUT` | `1200` | `fuzz_runner` 中每个工具子进程的超时时间(秒)。 |
| `LOCAL_SHELL_TIMEOUT` | `180` | `local_shell` 中每条命令的超时时间(秒)。 |
以及标准智能体变量(`COPILOT_TOKEN`、`LOG_DIR`、
`FUZZ_CONTEXT_DIR`、……)。完整列表请参阅项目根目录 README。
---
## 扩展流水线
### 添加新格式(变异器 + 字典)
1. 放入 `dictionaries/<name>.dict`(AFL `-x` 格式)和/或
`dictionaries/<name>_mutator.c`(libFuzzer 自定义变异器)。
2. 在 `fuzz_runner.py` 底部的 `_FORMAT_ASSETS` 中注册: ```python
"<name>": {
"dictionary": "<name>.dict",
"mutator": "<name>_mutator.c",
"description": "Short one-liner about the format",
},
list_format_assets() 自动获取它。fuzz_context.py(用于持久化)或 fuzz_runner.py(用于子进程工作)中添加一个用 @mcp.tool() 装饰的函数。Annotated[type, Field(description=...)] —— 描述就是 LLM 看到的内容。tests/test_fuzz_context.py / tests/test_fuzz_runner.py 中添加单元测试。通过工具的 .fn 属性调用它(FastMCP 约定)。user_prompt 中引用新工具。src/seclab_taskflows/taskflows/fuzzing/ 中创建一个新的 YAML。使用现有文件之一(例如 triage_crashes.yaml)作为模板。scripts/fuzzing/run_fuzzing.sh 中正确的两个现有阶段之间。scripts/fuzzing/dashboard.py 中添加阶段特定的仪表板部分。添加新的 SQL 表时:
fuzz_context_models.py 中添加 SQLAlchemy 模型。Base.metadata.create_all() 在引擎初始化时被调用,并自动创建新表。向现有表添加新 COLUMN 时:
fuzz_context.py 的 _migrate() 中添加 PRAGMA table_info + ALTER TABLE ADD COLUMN 块,以便旧数据库被透明升级。scripts/fuzzing/dashboard.py 中的 _migrate_if_writable()。benchmark/projects.yaml 列出了参考项目。选择它们是为了让完整的 v4+ 流水线能够在 codespace 开发镜像上端到端运行,无需人工干预。
在 codespace 开发镜像上运行完整 v4 流水线的参考数据(约 32 分钟/目标):
xz / cJSON / libexpat 的零崩溃结果是预期的:这些项目在上游已被大量模糊测试。oniguruma 中两个被归类为 vulnerability 的发现是 onig_snprintf_with_pattern 警告格式化代码路径中真实的越界读取(当模式以反斜杠结尾时,读取超过 pat_end 一个字节);每个崩溃的 markdown 报告包含建议的补丁。
要添加新的基准项目,请在 benchmark/projects.yaml 中添加一个条目,并(可选)在 benchmark/README.md 中记录原因。任何现有 analyze_build_system 阶段能够使用 clang + AFL++ 标志构建的项目都是合理的候选。纯 C 解析器、解码器和序列化器往往效果最好。
BUILD_FAILED: 并跳过它们。kernel.core_pattern=core 和 CPU governor 调整。在 Codespace 中这些不可用,因此 taskflow 默认导出 AFL_SKIP_CPUFREQ=1 和 AFL_I_DONT_CARE_ABOUT_MISSING_CRASHES=1。AFL 会打印警告,但仍通过 libFuzzer 风格的 abort 处理找到崩溃。<dirent.h>。适用于 Linux/macOS;无法在 Windows 上编译。compile_harness 构建的 AFL 二进制文件在 argv 模式下使用 libAFLDriver。因此 replay_under_asan 和 tmin 默认 stdin_input=False,因为 libAFLDriver 在通过 stdin 驱动时会无限循环。generate_smart_mutator + generate_smart_mutators 使用 Python —— C 模板中的每个字面 / 都必须加倍( / )。如果你编辑模板并开始看到 ,原因就在于此。此 taskflow 运行 afl-fuzz、clang、llvm-cov,以及由 LLM 选择的任意构建命令,直接在主机上(无容器)。被提示注入的 agent 原则上可以做你的用户能做的任何事情。仅在以下情况下运行:
git、apt 和构建系统所需的内容。local_shell 工具箱不在确认提示之后 —— taskflow 是自主的,没有人在环路中运行,因此交互式确认只会永远阻塞。每个 shell 命令都记录到 $LOG_DIR/mcp_local_shell.log 以供事后审查。
hatch test
hatch fmt --linter --check
hatch fmt --linter
hatch fmt --linter --check -- src/seclab_taskflows/mcp_servers/fuzz_runner.py
代码库约定(另请参阅 `benchmark/improvements.md` 中这些约定的活动历史版本):
- 使用 `os.environ.get(NAME) or "default"` 而非
`os.environ.get(NAME, "default")`。否则 YAML 模板替换产生的空字符串
会被返回。
- 在新注解中使用 `X | None`(PEP 604),而非 `Optional[X]`。
- 测试通过 `.fn(...)` 调用 MCP 工具,而非直接使用装饰后的名称。
- 避免在测试中使用 `/tmp/...` 字面量——使用 `tmp_path` pytest fixture
(lint 规则 `S108`)。
- 测试方法内的所有内联导入,如果无法将其移至文件顶部(例如在
`pytest.skip` 之后条件导入时),都需要添加 `# noqa: PLC0415`。
- 复合真值测试每行仅一个断言(lint 规则 `PT018`)。
改进追踪器(`benchmark/improvements.md`)是跨版本记录流水线中新增内容的
持久日志。当你添加实质性功能时,请在其中添加一节,描述变更内容、
所在位置以及哪些测试对其进行守护。
---
## 术语表
- **AFL++** — 覆盖率引导的灰盒模糊测试器;此处的执行引擎。
- **libAFLDriver** — 静态库,允许 AFL++ 测试框架使用
libFuzzer 入口点约定(`LLVMFuzzerTestOneInput`)。
- **LCOV** — 行业标准的覆盖率跟踪文件格式。我们通过
`llvm-cov export -format=lcov` 导出并自行解析。
- **`stack_top_hash`** — ASan/UBSan 堆栈跟踪中顶部 N 个规范化帧的
16 字符哈希。用于崩溃去重。
- **持久语料库** — 位于
`<workspace>/corpus/harness_<id>/` 的每个测试框架目录,用于在迭代和
同一活动的重新运行之间保留 AFL 的有趣输入。
- **智能变异器** — 一种 `LLVMFuzzerCustomMutator`,其拼接令牌从
目标自身的源代码中提取(`generate_smart_mutator`)。
- **自定义变异器(libFuzzer)** — 用户提供的 C 函数,由引擎调用,
可完全自由地决定如何变异缓冲区;AFL++ 支持相同的 ABI。
- **MCP 工具** — 经 FastMCP 装饰的函数,LLM 代理可调用。
- **OSS-Fuzz / Fuzz-Introspector** — Google 的开源模糊测试
基础设施及其配套的调用图/覆盖率分析工具。
此任务流的若干功能(按格式变异器、按堆栈去重、
调用图 + 未触及 API 报告、多候选测试框架)均受其启发。
---
## 许可证
本项目根据 MIT 开源许可证的条款授权。请参阅 [LICENSE](https://github.com/githubsecuritylab/seclab-taskflows-fuzzing/blob/main/LICENSE.txt) 文件了解完整条款。
## 维护者
请参阅 [CODEOWNERS](https://github.com/githubsecuritylab/seclab-taskflows-fuzzing/blob/main/CODEOWNERS) 或联系 GitHub Security Lab 团队。
## 支持
请参阅 [SUPPORT.md](https://github.com/githubsecuritylab/seclab-taskflows-fuzzing/blob/main/SUPPORT.md) 了解如何获取此项目帮助的详细信息。
## 致谢
本项目基于 [AFL++](https://github.com/AFLplusplus/AFLplusplus)、[OSS-Fuzz](https://github.com/google/oss-fuzz) 和 [Fuzz-Introspector](https://github.com/ossf/fuzz-introspector) 的概念和技术构建。
| # | 阶段 | Taskflow YAML |
|---|
| 1 | 安装 AFL++ + 工具链 | scripts/fuzzing/install_afl.sh |
| 2 | 获取源码 | seclab_taskflows.taskflows.audit.fetch_source_code |
| 3 | 识别 fuzz 目标 | seclab_taskflows_fuzzing.taskflows.fuzzing.identify_fuzz_targets |
| 4 | 分析构建系统 | seclab_taskflows_fuzzing.taskflows.fuzzing.analyze_build_system |
| 5a | 编写初始 harness(如请求则 ×N 个候选) | seclab_taskflows_fuzzing.taskflows.fuzzing.write_initial_harnesses |
| 5b | 构建 harness(AFL + 覆盖率) | seclab_taskflows_fuzzing.taskflows.fuzzing.build_harnesses |
| 5c | 筛选候选(当 HARNESS_CANDIDATES > 1 时) | seclab_taskflows_fuzzing.taskflows.fuzzing.qualify_harnesses |
| 6 | Fuzz/覆盖率/改进循环(×N 次迭代) | seclab_taskflows_fuzzing.taskflows.fuzzing.fuzz_iteration |
| 7 | 分类崩溃 | seclab_taskflows_fuzzing.taskflows.fuzzing.triage_crashes |
| 8 | 确认先前已知的崩溃仍可复现 | seclab_taskflows_fuzzing.taskflows.fuzzing.confirm_fixed_crashes |
| 9 | 构建调用图 + 未触及 API 报告 | seclab_taskflows_fuzzing.taskflows.fuzzing.analyze_call_graph |
| 10 | 编写每个崩溃的漏洞报告 | seclab_taskflows_fuzzing.taskflows.fuzzing.write_vuln_reports |
| 11 | 编写 campaign 报告 | seclab_taskflows_fuzzing.taskflows.fuzzing.write_report |
| 判定结果 | 含义 |
|---|
vulnerability | 真实存在,可通过公共 API 利用 |
library_hardening | 真实缺陷,但没有现实的公共 API 路径;库仍应自我防御 |
harness_bug | 缺陷在我们的测试框架中,而非库中 |
non_reproducible | 重放无法在最小化输入上复现崩溃 |
oom | 内存耗尽;仅当攻击者可控的大小无界时才为漏洞 |
timeout | 通过算法爆炸导致的 DoS |
assertion_failure | 触发了 assert();安全相关性各异 |
fixed | 由 confirm_fixed_crashes 设置:输入不再复现 |
duplicate | 与另一个具有不同堆栈哈希的崩溃具有相同根本原因 |
needs_investigation | 无法确定;标记为需要人工审查 |
| # | Repo | Why it's interesting | Notes |
|---|
| 1 | tukaani-project/xz | 真实世界中解析器密集型库(liblzma);丰富的过滤器链 + 整数/VLI 解析面 | 基线 |
| 2 | DaveGamble/cJSON | 小型单文件 C JSON 解析器;简单的 CMake | 流水线的快速冒烟测试 |
| 3 | akheron/jansson | 紧凑的 C JSON 库,具有文档化的 json_loadb() 字节缓冲区入口点 | CMake;非常快的 exec/sec |
| 4 | libexpat/libexpat | 成熟的流式 XML 解析器;许多历史 CVE | CMake 或 autotools |
| 5 | kkos/oniguruma | 正则引擎;接受攻击者模式 + 主题 | Autotools;模式编译是热点路径 |
| Repo | Targets | Harnesses | AFL runs | Crashes | Verdicts |
|---|
tukaani-project/xz | 8 | 8 | 48 | 0 | — |
DaveGamble/cJSON | 6 | 6 | 36 | 0 | — |
akheron/jansson | 7 | 7 | 35 | 10 | harness_bug、library_hardening、duplicate、needs_investigation |
libexpat/libexpat | 3 | 3 | 18 | 0 | — |
kkos/oniguruma | 10 | 10 | 60 | 13 | vulnerability(×2 在 regerror.c 中的 OOB 读取)、library_hardening、harness_bug、non_reproducible |
.format(){}{{}}KeyError