avaloki108/sci-fuzz

GitHub: avaloki108/sci-fuzz

一个基于覆盖率引导的EVM智能合约不变量模糊测试器,通过快照状态探索和经济预言机检测DeFi协议中的漏洞。

Stars: 0 | Forks: 0

# sci-fuzz — 智能合约不变量模糊测试器 一个覆盖引导的、基于快照的 EVM 模糊测试器,以最少的手动规范发现不变量违反。 **状态:可投入生产的原型。** 六个改进阶段已完成。模糊测试循环、EVM 执行器、预言机栈(包括借贷健康检测)、CI 输出管道、语料库持久化,以及一个精简的 `sci-fuzz diff` MVP 均可运行。剩余工作是对真实目标进行验证性测试和语义缩减器。 ## 这是什么 sci-fuzz 是一个基于 Rust 的智能合约模糊测试器,构建于 [revm](https://github.com/bluealloy/revm) 之上。它将 ItyFuzz(基于快照的状态探索)、AFL++(命中计数分桶和能量调度)、EF/CF(结构感知变异和基准测试)和 Echidna(基于属性的测试)的理念整合到一个工具中。 名称代表 **S**mart **C**ontract **I**nvariant **Fuzz**er。其论点是,有效的智能合约模糊测试的最大障碍不是执行速度——而是编写良好不变量的成本。sci-fuzz 通过自动不变量生成、模板库和经济预言机检测来解决这个问题。 ## 当前可用的功能 - **EVM 执行** 通过 revm 19.7 实现快照/恢复(CacheDB 克隆) - **每合约控制流边缘覆盖** 通过 revm 检查器记录 `(prev_pc, current_pc)` 转换(按归属合约),执行期间使用原始命中计数 - **有序路径 ID(每笔交易和每序列)** — [`ExecutionResult::tx_path_id`]( [ …] --rpc-url …` **需要** RPC URL(`ETH_RPC_URL` 或 `--rpc-url`)。传递**多个地址**可在一次运行中模糊测试几个预部署合约(共享分叉根)。每个地址可选获取 Etherscan ABI,当 `ETHERSCAN_API_KEY` 设置时;否则显示一行说明 ABI 可能缺失。 - **攻击者/发送者模型** — [`CampaignConfig::resolved_attacker`]( 1` 时,**调度在运行之间不能完全重现**(线程交错不同)。 - **扩展:** **锁争用**在共享语料库/状态和**共享变异器**上(序列化选择器/值字典更新)可以**限制吞吐量**;不要期望与工作线程数成线性加速。 - **范围:** **没有分布式模糊测试** — 仅限进程内线程;没有多机器语料库同步(参见*尚不能工作的内容*)。 - **校准阶段** — 在主循环之前运行种子交易以建立覆盖基线并填充值字典 - **ABI 感知变异** — 从 ABI JSON 提取函数选择器,生成类型化参数(uint256、address、bool、bytes32),使用位翻转、字节替换、选择器交换、值交换、发送者交换进行变异 - **值字典** — 从 EVM 字节码(PUSH1–PUSH32 操作数提取)种子化,并从执行结果(返回数据、日志主题、存储写入)增长 - **核心不变量检查器**(始终注册):BalanceIncreaseUnexpectedRevert、SelfDestruct、EchidnaProperty(基于日志的断言检测)、FlashloanEconomicOracle(模拟闪电贷脚手架后的收益)。收益风格检查的 ETH 余额基线是**每序列**:在执行序列之前立即捕获(在恢复选定的快照之后),而不是在活动开始时一次捕获 — 因此非根语料库快照与正确的序列前余额比较。 - **利用级经济预言机**([`economic.rs`]( --fuzz-seed` 端到端,记录墙上时间,并仅在命令实际运行时将行标记为 `measured`。发现/未发现从 Forge 报告的失败测试数量映射(`N failed` ⇒ 发现)。执行计数和首次命中指标对于外部引擎仍然不可用,按设计留空/为零。 - **基准测试矩阵** — 81 个条目将 EF/CF 合约映射到预期漏洞类型,并具有文件存在性和类别覆盖验证测试 - **Forge VM 作弊码** **[第 1 阶段]** — `vm.warp()`、`vm.roll()`、`vm.prank()`、`vm.deal()`、`vm.expectRevert()`、`vm.assume()`、`vm.store()` 和 `vm.load()` 在 revm 执行循环中被拦截。使用这些作弊码的脚手架在 `setUp()` 和测试函数中现在可以正确执行,而不是回滚。 - **扩展变异引擎** **[第 2 阶段]** — 可配置的变异深度限制、序列拼接变异(跨序列段重组)和时间感知变异(calldata 生成期间的块时间戳/块号扰动)。这些提升了对时间门控和多步状态机的覆盖,而这些之前对模糊测试器是不可见的。 - **访问控制预言机** **[第 3 阶段]** — `AccessControlOracle` 检测非所有者发送者成功调用包含 `onlyOwner` / `onlyRole` 模式回滚的函数。自动注册其 ABI 包含标准 `Ownable` / `AccessControl` 事件签名的合约。 - **重入预言机** **[第 3 阶段]** — `ReentrancyOracle` 标记嵌套调用中(`call_depth > 1`)的 `SSTORE` 写入,结合净 ETH 收益。通过 `ExecutionResult` 上的 `sstore_in_nested_call` 标志与 `CoverageInspector` 集成。 - **代币流守恒预言机** **[第 3 阶段]** — `TokenFlowConservationOracle` 检测 ERC-20 余额耗尽:跟踪指定代币和目标的序列前/后 `balanceOf`,当攻击者获得的代币超过目标损失的代币时触发高危/严重。 - **CI 输出管道** **[第 4 阶段]** — `src/output.rs` 提供三种纯格式化器: - `sarif_from_findings()` — SARIF 2.1.0 JSON(GitHub 代码扫描、GitLab SAST、任何 SARIF 使用者)。对 `rules` 数组去重;映射 Critical/High → `"error"`,Medium → `"warning"`,Low/Info → `"note"`。 - `junit_from_findings()` — JUnit XML。在干净运行时发出通过测试用例,以便 CI 解析器始终看到至少一个结果。 - `forge_reproducer()` — 可编译的 Solidity `.t.sol` 骨架,包含每个重放序列中交易的 `vm.prank()` + `call{value:}`。 - **`sci-fuzz ci` 命令** **[第 4 阶段]** — 完全实现(`main.rs` 中的 `handle_ci()`)。运行真实活动(50k 执行,种子 `0xcafebabe`,2 个工作线程,可配置超时)。当传递 `--github-actions` 时发出 GitHub Actions `::error/warning/notice` 注解。将 Forge `.t.sol` 重放器写入 `test/repros/`。当严重/高发现满足配置阈值时以代码 `2` 退出(区别于构建错误退出 `1`)。 - **语料库持久化** **[第 5 阶段]** — `CampaignConfig::corpus_dir`(可选 `PathBuf`)启用跨活动运行的基于 JSON 的语料库保存/加载。加载在活动开始时注入最多 64 个先前条目;保存在主循环之后的活动结束时运行。所有 I/O 失败仅记录 `tracing::warn!` — 语料库丢失永远不会终止运行。CLI 标志:`forge` 和 `ci` 子命令上的 `--corpus-dir`。 - **借贷健康预言机** **[第 6 阶段]** — `LendingHealthOracle` 通过扫描序列累积日志中的标准 `Borrow` 事件签名来检测无抵押借款债务。处理通用 `Borrow(address,address,uint256)` 形状和 Compound-v2 `Borrow(address,uint256,uint256,uint256)` 形状(金额始终从 `data[0..32]` 解码)。配对的 `Repay` 和 `RepayBorrow` 事件被减去;只有超过 `min_net_borrow` 的净未偿债务才会触发。当配置文件附加时,通过 `is_lending_like()`(来自 `protocol_semantics.rs` 的 ABI 借贷分数 ≥ 3)门控 — 在非借贷目标上静默。当攻击者在同一序列中也获得 ETH 时,严重性从 Medium 升级到 High。 ## 尚不能工作的内容 诚实比营销更重要。这些是真正的差距: - **路径 ID 是紧凑指纹,不是完美跟踪。** 完整的规范基本块跟踪、超越当前合约归属的调用深度索引边缘键,以及全局路径数据库仍然超出范围。路径新颖性使用有界缓存 — 在极端唯一性压力下,较旧的路径 ID 可能会被驱逐并重新变得新颖。原生模拟闪电贷交易使用确定性合成路径 ID(没有字节码检查器)。 - **缩减仍然是第一遍。** 缩减器是确定性的,今天很有用,但它还不是完整的语义缩减器:它不推理 ABI 类型、存储依赖性或最小基础状态快照,也不保证全局最小序列。 - **没有分布式模糊测试** — 并行工作线程只是一个进程中的线程;没有多机器语料库或协调器(参见上面的多工作线程**范围**)。 - **Foundry 集成差距 beyond 基础作弊码。** 基于脚本的部署流程、库特定引导、`StdInvariant` / `targetContract` 连接和多合约设置脚本未实现。第 1 阶段涵盖了常见作弊码(`vm.warp`、`vm.roll`、`vm.prank`、`vm.deal`、`vm.expectRevert`、`vm.assume`、`vm.store`、`vm.load`),但与 Foundry 的不变量运行器的完全等效不是目标。 - **`sci-fuzz diff` 现在是一个 MVP 差分执行器,不是语义证明系统。** 它通过重放相同的生成调用来比较两个本地 Foundry 工件/合约目标,并报告可重现的分歧:成功/回滚不对称、输出 ABI 匹配时的 ABI 解码输出差异、无法解码时的原始返回数据差异,以及轻量级日志主题 0 序列/计数差异。它**不**证明正确性、语义等价性或跨链行为。 - **外部比较执行仍然部分。** Forge 现在对 Foundry 项目基准测试案例有真实测量路径;Echidna 和非项目案例仍然是 `skipped`/`unavailable`,带有明确原因。 - **部分 Echidna 兼容性。** `EchidnaPropertyCaller` 实现核心工作流程(发现 `echidna_*` 函数,调用它们,检查布尔返回)。`EchidnaProperty` 检测日志中的断言事件。两者都没有以完整的 Echidna 保真度处理回滚/断言区分,并且属性脚手架工作流程(`targetContract`、可配置测试限制、缩减)未实现。 - **经济和守恒预言机仍然是启发式的,尽管有 ABI 提示和探测。** 存储槽布局(ERC-20 `totalSupply` 在槽 2,余额在映射槽 0)仍然匹配常见的 OpenZeppelin 布局,并在代理、钻石和自定义存储上断开。分类和门控减少了一些噪音,但不保证健全性。汇率跳跃、同一交易价差和"没有 Transfer 到金库"检查仍然可能在极端舍入、首次流动性边缘、捐赠经济学或非标准金库上产生误报。探测与事件检查可能在费用转移资产、捐赠风格储备移动或非标准金库/对上产生误报。**守恒**检查(Sync 窗口解释、Deposit vs 底层 `Transfer`)改善了池和金库的分类,但**不是**健全的会计证明;桥接仍然未建模。`LendingHealthOracle` 覆盖常见的 `Borrow` 事件模式,但不是完整的抵押比率证明 — 未建模多资产头寸、健康因子数学和清算阈值。 - **207k execs/sec 数字是一个冒烟测试。** 它测量空目标吞吐量。带有存储和复杂逻辑的真实合约将以 1–5k execs/sec 运行。该数字展示了低框架开销,而不是安全测试强度。 ## 架构 ``` campaign.rs main loop: calibrate → (optional) parallel workers → shared corpus/feedback → execute → check → learn corpus save/load via corpus_dir in both sequential and parallel paths harness.rs Foundry-style setUp() selector + one-shot setup execution on the revm executor evm.rs revm 19.7 wrapper: execute, deploy, static_call, snapshot/restore, Fast/Realistic modes edge coverage + ordered path stream + call_depth tracking + sstore_in_nested_call flag [Phase 3] path_id.rs rolling path hash, per-sequence fold, native-flashloan synthetic id snapshot.rs state corpus: novelty-weighted selection, power scheduling metadata, auto-pruning over real coverage feedback.rs AFL++ hitcount bucketing (8 classes), virgin-bits tracking, bounded path-ID novelty mutator.rs ABI-aware generation, 5 mutation strategies + splice + time-aware mutations [Phase 2], value dictionary output.rs SARIF 2.1 / JUnit XML / Forge .t.sol reproducer formatters [Phase 4] economic.rs exploit-oriented economic invariants (ERC-4626, ERC-20 accounting, AMM swap/sync sanity, optional lending drift, probe-informed checks) conservation.rs log-order helpers for reserve/Sync conservation reasoning conservation_oracles.rs AmmSyncExplainedOracle, Erc4626DepositVsUnderlyingTransferOracle protocol_probes.rs post-tx static_call probes (ERC-4626 / ERC-20 / AMM) into ExecutionResult protocol_semantics.rs best-effort ABI/event protocol classification and triage helpers for economic oracles invariant.rs Invariant trait + default registry + EchidnaPropertyCaller AccessControlOracle, ReentrancyOracle, TokenFlowConservationOracle [Phase 3] LendingHealthOracle [Phase 6] oracle.rs routes execution results through invariant registry; balance baselines supplied per check types.rs core types built on alloy-primitives (Address, U256, B256) ExecutionResult: tx_path_id, sequence_cumulative_logs, protocol_probes, sstore_in_nested_call [Phase 3] CampaignConfig: corpus_dir [Phase 5] scoreboard.rs stable benchmark result / summary schema + CSV / JSON writers benchmark.rs benchmark case loading, sci-fuzz measurement, Forge project comparison path, comparison scaffolding cli.rs clap-based CLI: benchmark, forge, audit, test (thin wrapper), ci (implemented), diff (MVP differential executor), version rpc.rs JSON-RPC fork DB (RpcCacheDB), chain id, full block header parse/merge into BlockEnv main.rs CLI dispatch; handle_forge(), handle_ci() [Phase 4], handle_benchmark(), handle_audit() ``` ## 安装 ``` # 从源码构建 (Rust 1.75+) git clone https://github.com/your-org/sci-fuzz cd sci-fuzz cargo build --release ``` ## 使用方法 ``` # 对当前 Foundry 项目进行模糊测试 sci-fuzz forge --timeout 120 # 使用一条命令对指定 Foundry 项目进行模糊测试 sci-fuzz forge --project /path/to/foundry-project --timeout 120 # 通过更多快照进行深入探索 sci-fuzz forge --project /path/to/foundry-project --depth 32 --max-snapshots 8192 --timeout 600 # 可复现运行 sci-fuzz forge --seed 42 --timeout 60 # 跨运行持久化语料库(从之前的有效输入继续) sci-fuzz forge --project /path/to/project --corpus-dir .sci-fuzz/corpus --timeout 600 # Foundry 项目 + JSON-RPC 分支(可选区块固定;或在 foundry.toml 中设置 eth_rpc_url) sci-fuzz forge --project /path/to/project --fork-url https://eth.llamarpc.com --fork-block 19000000 --timeout 600 # 审计已部署合约(需要 ETH_RPC_URL 或 --rpc-url) export ETH_RPC_URL=https://eth.llamarpc.com sci-fuzz audit 0xYourTarget --chain mainnet --timeout 300 # 同一分支上的多个预部署目标 sci-fuzz audit 0xVault 0xRouter 0xOracle --chain mainnet --timeout 300 # CI 安全扫描 — 发出 SARIF/JUnit、GitHub Actions 注释、Forge 复现器 sci-fuzz ci --project . --output-format sarif --output results.sarif --github-actions --fail-on-critical sci-fuzz ci --project . --output-format junit --output results.xml --corpus-dir .sci-fuzz/corpus # 运行内置 EF/CF 基准预设并发出 CSV/JSON 证据 sci-fuzz benchmark --preset efcf-demo --seeds 1,2,3 --max-execs 5000 --output-dir target/benchmark # 使用相同模式对真实 Foundry 项目进行基准测试 sci-fuzz benchmark --project /path/to/foundry-project --target Vault --property campaign --category Campaign --seeds 1,2,3 --max-execs 5000 # 两个本地 Foundry 目标之间的差异执行 MVP sci-fuzz diff ImplA ImplB --project . --max-execs 2000 --depth 8 --seed 42 # 显示版本 sci-fuzz version ``` ## 差分(`sci-fuzz diff`) `sci-fuzz diff --project .` 将本地 Foundry 项目中的两个合约部署到隔离的 revm 实例中,并针对两者驱动相同的生成调用序列,报告任何可重现的分歧。 ### 它报告的内容 | 分歧类型 | 条件 | |---|---| | `success-vs-revert` | 实现 A 成功,实现 B 回滚 | | `revert-vs-success` | 实现 A 回滚,实现 B 成功 | | `output-mismatch` | 两者都成功但返回不同的字节 | | `log-signature-difference` | 不同的事件主题 [0] 集合(事件签名) | 首次发现分歧时,序列使用现有的 [`SequenceShrinker`](
标签:EVM, property-based testing, revm, Rust, 不变量测试, 以太坊, 区块链安全, 可视化界面, 合约fuzzing, 合约状态探索, 安全测试, 形式化验证, 攻击性安全, 智能合约, 智能合约审计, 模糊测试工具, 网络流量审计, 覆盖率引导, 语料库管理, 路径覆盖