mcpField
F
fuzzing-obstacles

fuzzing-obstacles

github.com/trailofbits测试与质量7.9k5.4k 次安装今天更新
代码仓库

关于 fuzzing-obstacles

这项技术指南介绍如何通过条件编译调整被测代码,绕过阻碍模糊测试深入探索的校验或非确定性状态,同时保留生产构建行为。内容强调逐项评估覆盖率变化和误报风险。

主要功能

  1. 01识别校验与全局状态障碍
  2. 02用条件编译调整模糊测试构建
  3. 03固定随机种子以保持确定性
  4. 04逐步测量覆盖率变化
  5. 05评估绕过检查引入的误报风险

适用场景

  1. 01校验和检查阻止测试深入代码路径
  2. 02时间种子导致运行结果不稳定
  3. 03复杂格式校验难以生成有效输入
  4. 04覆盖率显示大量代码不可达

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/trailofbits/skills --skill fuzzing-obstacles

安装后在 Claude Code 中输入/fuzzing-obstacles确认已加载。

附带文件agents/openai.yamlassets/trail-of-bits-mark.svg

详细介绍

克服模糊测试障碍

代码库中常常包含阻碍有效覆盖率的反模糊测试模式。校验和、全局状态(如使用时间作为种子的伪随机数生成器)以及验证检查,都可能阻止模糊测试器探索更深层的代码路径。本技术介绍如何在模糊测试期间修补被测系统(SUT)以绕过这些障碍,同时保留生产环境中的行为。

概述

许多真实世界的程序并非为模糊测试而设计。它们可能会:

  • 在处理输入前验证校验和或加密哈希
  • 依赖全局状态(例如系统时间、环境变量)
  • 使用非确定性随机数生成器
  • 执行复杂的验证,导致模糊测试器难以生成有效输入

这些模式会让模糊测试变得困难,因为:

  1. 校验和: 模糊测试器必须猜中正确的哈希值(成功概率低到几乎不可能)
  2. 全局状态: 相同输入在不同运行中会产生不同的行为(破坏确定性)
  3. 复杂验证: 模糊测试器耗费精力处理验证失败,而非探索更深层的代码

解决方案是条件编译:在模糊测试构建中修改代码行为,同时保持生产代码不变。

关键概念
概念说明
SUT 修补修改被测系统,使其适用于模糊测试
条件编译根据编译时标志而表现不同的代码
模糊测试构建模式启用模糊测试专用补丁的特殊构建配置
误报在模糊测试期间发现、但在生产环境中不会发生的崩溃
确定性相同输入始终产生相同的行为(对模糊测试至关重要)

何时应用

在以下情况应用本技术:

  • 模糊测试器卡在校验和或哈希验证处
  • 覆盖率报告显示,大块代码因验证而无法触达
  • 代码使用基于时间的种子或其他非确定性全局状态
  • 复杂的验证让生成有效输入几乎不可能
  • 你看到模糊测试器反复遇到相同的验证失败

在以下情况跳过本技术:

  • 使用良好的种子语料库或字典即可克服障碍
  • 验证足够简单,模糊测试器可以学会(例如魔数)
  • 你正在进行可处理验证的基于语法或了解结构的模糊测试
  • 跳过检查会引入过多误报
  • 代码已经适用于模糊测试

快速参考

任务C/C++Rust
检查是否为模糊测试构建#ifdef FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTIONcfg!(fuzzing)
在模糊测试期间跳过检查#ifndef FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION return -1; #endifif !cfg!(fuzzing) { return Err(...) }
常见障碍校验和、伪随机数生成器、基于时间的逻辑校验和、伪随机数生成器、基于时间的逻辑
支持的模糊测试器libFuzzer、AFL++、LibAFL、honggfuzzcargo-fuzz、libFuzzer

分步指南

第 1 步:识别障碍

运行模糊测试器并分析覆盖率,找出无法触达的代码。常见模式:

  1. 查找深层处理前的校验和/哈希验证
  2. 检查是否调用了带有系统种子的 rand()、time() 或 srand()
  3. 找出会拒绝大多数输入的验证函数
  4. 找出在不同运行中初始化结果不同的全局状态

可用工具:

  • 覆盖率报告(参见覆盖率分析技术)
  • 使用 -fprofile-instr-generate 进行性能分析
  • 手动检查入口点代码
第 2 步:添加条件编译

修改障碍处的代码,使其在模糊测试构建中绕过该障碍。

C/C++ 示例:

// Before: Hard obstacle
if (checksum != expected_hash) {
    return -1;  // Fuzzer never gets past here
}

// After: Conditional bypass
if (checksum != expected_hash) {
#ifndef FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION
    return -1;  // Only enforced in production
#endif
}
// Fuzzer can now explore code beyond this check

Rust 示例:

// Before: Hard obstacle
if checksum != expected_hash {
    return Err(MyError::Hash);  // Fuzzer never gets past here
}

// After: Conditional bypass
if checksum != expected_hash {
    if !cfg!(fuzzing) {
        return Err(MyError::Hash);  // Only enforced in production
    }
}
// Fuzzer can now explore code beyond this check
第 3 步:验证覆盖率提升

修补后:

  1. 使用模糊测试插桩重新构建
  2. 短时间运行模糊测试器
  3. 将覆盖率与未修补版本进行比较
  4. 确认正在探索新的代码路径
第 4 步:评估误报风险

考虑跳过检查是否会导致不可能出现的程序状态:

  • 检查之后的代码是否会假设某些属性已通过验证?
  • 跳过验证是否会导致生产环境中不会发生的崩溃?
  • 是否存在隐式状态依赖?

如果误报风险较高,可以考虑采用更有针对性的补丁(参见下文“常见模式”)。

常见模式

模式:绕过校验和验证

用例: 哈希/校验和阻碍了模糊测试器的进展

之前:

uint32_t computed = hash_function(data, size);
if (computed != expected_checksum) {
    return ERROR_INVALID_HASH;
}
process_data(data, size);

之后:

uint32_t computed = hash_function(data, size);
if (computed != expected_checksum) {
#ifndef FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION
    return ERROR_INVALID_HASH;
#endif
}
process_data(data, size);

误报风险: 低 — 如果数据处理不依赖校验和的正确性

模式:使用确定性的 PRNG 种子

用例: 非确定性的随机状态会妨碍复现

之前:

void initialize() {
    srand(time(NULL));  // Different seed each run
}

之后:

void initialize() {
#ifdef FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION
    srand(12345);  // Fixed seed for fuzzing
#else
    srand(time(NULL));
#endif
}

误报风险: 低 — 使用固定种子时,模糊测试器可以探索所有代码路径

模式:谨慎跳过验证

用例: 必须跳过验证,但下游代码存在假设

之前(危险):

#ifndef FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION
if (!validate_config(&config)) {
    return -1;  // Ensures config.x != 0
}
#endif

int32_t result = 100 / config.x;  // CRASH: Division by zero in fuzzing!

之后(安全):

#ifndef FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION
if (!validate_config(&config)) {
    return -1;
}
#else
// During fuzzing, use safe defaults for failed validation
if (!validate_config(&config)) {
    config.x = 1;  // Prevent division by zero
    config.y = 1;
}
#endif

int32_t result = 100 / config.x;  // Safe in both builds

误报风险: 已缓解 — 提供安全默认值,而不是直接跳过

模式:绕过复杂的格式验证

用例: 多步骤验证让生成有效输入变得几乎不可能

Rust 示例:

// Before: Multiple validation stages
pub fn parse_message(data: &[u8]) -> Result<Message, Error> {
    validate_magic_bytes(data)?;
    validate_structure(data)?;
    validate_checksums(data)?;
    validate_crypto_signature(data)?;

    deserialize_message(data)
}

// After: Skip expensive validation during fuzzing
pub fn parse_message(data: &[u8]) -> Result<Message, Error> {
    validate_magic_bytes(data)?;  // Keep cheap checks

    if !cfg!(fuzzing) {
        validate_structure(data)?;
        validate_checksums(data)?;
        validate_crypto_signature(data)?;
    }

    deserialize_message(data)
}

误报风险: 中 — 反序列化必须能够妥善处理格式错误的数据

高级用法

提示与技巧
提示帮助原因
保留开销低的验证魔数和大小检查能以较低成本引导模糊测试器
为 PRNG 使用固定种子探索所有代码路径的同时,让行为保持确定性
逐步打补丁每次跳过一个障碍,并衡量对覆盖率的影响
添加防御性默认值跳过验证时,提供安全的回退值
记录所有补丁方便未来的维护者了解模糊测试与生产环境之间的差异
真实案例

OpenSSL: 使用 FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION 来修改密码算法的行为。例如,在 crypto/cmp/cmp_vfy.c 中,模糊测试期间会放宽某些签名检查,以便更深入地探索证书验证逻辑。

ogg crate(Rust): 使用 cfg!(fuzzing) 在模糊测试期间跳过校验和验证。这样,模糊测试器无需费力猜测正确的校验和,就能探索音频处理代码。

衡量补丁效果

应用补丁后,量化改进情况:

  1. 行覆盖率: 使用 llvm-cov 或 cargo-cov 查看新增的可达代码行
  2. 基本块覆盖率: 比行覆盖率更精细
  3. 函数覆盖率: 现在还能触达多少个函数?
  4. 语料库大小: 模糊测试器是否生成了更多样的输入?

有效的补丁通常能将覆盖率提高 10-50% 或更多。

与其他技术结合使用

障碍补丁可以与以下方法很好地结合:

  • 语料库种子: 提供能够通过初始解析的有效输入
  • 字典: 帮助模糊测试器学习魔数和常见值
  • 结构感知模糊测试: 对于复杂格式,使用 protobuf 或语法定义
  • 测试框架改进: 更好的测试框架有时可以彻底避开障碍

反模式

反模式问题正确做法
跳过所有验证会产生误报,并导致模糊测试不稳定仅跳过阻碍覆盖率提升的特定障碍
未进行风险评估误报会浪费时间,并掩盖真正的漏洞分析下游代码中的假设
忘记记录补丁后续维护者无法理解差异添加注释,说明补丁为何安全
未衡量效果就打补丁不知道补丁是否有效比较打补丁前后的覆盖率
打补丁过多模糊测试构建与生产构建的差异过大尽量减小两种构建之间的差异

工具专项指导

libFuzzer

libFuzzer 会在编译时自动定义 FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION。

# C++ compilation
clang++ -g -fsanitize=fuzzer,address -DFUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION \
    harness.cc target.cc -o fuzzer

# The macro is usually defined automatically by -fsanitize=fuzzer
clang++ -g -fsanitize=fuzzer,address harness.cc target.cc -o fuzzer

集成提示:

  • 该宏会自动定义;通常无需手动定义
  • 使用 #ifdef 检查该宏
  • 配合 Sanitizer 使用,以检测新近可触达代码中的漏洞
AFL++

使用其编译器包装器时,AFL++ 也会定义 FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION。

# Compilation with AFL++ wrappers
afl-clang-fast++ -g -fsanitize=address target.cc harness.cc -o fuzzer

# The macro is defined automatically by afl-clang-fast

集成提示:

  • 使用 afl-clang-fast 或 afl-clang-lto 自动定义该宏
  • 持久模式的测试桩最能受益于障碍修补
  • 可考虑使用 AFL_LLVM_LAF_ALL,执行额外的输入到状态转换
honggfuzz

构建目标时,honggfuzz 也支持该宏。

# Compilation
hfuzz-clang++ -g -fsanitize=address target.cc harness.cc -o fuzzer

集成提示:

  • 使用 hfuzz-clang 或 hfuzz-clang++ 包装器
  • 可通过该宏进行条件编译
  • 与 honggfuzz 的反馈驱动模糊测试结合使用
cargo-fuzz(Rust)

cargo-fuzz 会在构建期间自动设置 fuzzing cfg 选项。

# Build fuzz target (cfg!(fuzzing) is automatically set)
cargo fuzz build fuzz_target_name

# Run fuzz target
cargo fuzz run fuzz_target_name

集成提示:

  • 在生产构建中使用 cfg!(fuzzing) 进行运行时检查
  • 使用 #[cfg(fuzzing)] 进行编译时条件编译
  • fuzzing cfg 仅在 cargo fuzz 构建期间设置,常规 cargo build 不会设置
  • 可通过 RUSTFLAGS="--cfg fuzzing" 手动启用以进行测试
LibAFL

对于以 C/C++ 编写的目标,LibAFL 支持该 C/C++ 宏。

# Compilation
clang++ -g -fsanitize=address -DFUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION \
    target.cc -c -o target.o

集成提示:

  • 手动定义该宏或使用编译器标志
  • 用法与 libFuzzer 相同
  • 在构建基于 LibAFL 的自定义模糊测试工具时很有用

故障排查

问题原因解决方案
打补丁后覆盖率没有提升识别错了障碍对执行过程进行性能分析,找出真正的瓶颈
出现大量误报崩溃下游代码中存在假设添加防御性默认值或部分验证
代码编译结果不同并非所有构建配置都定义了宏检查所有源文件和依赖项中的宏
模糊测试工具在打过补丁的代码中发现漏洞补丁引入了无效状态检查补丁是否遵守状态不变量;考虑采用更安全的方法
无法重现生产环境中的漏洞构建差异过大尽量减少补丁;保留对状态关键检查的验证

相关技能

使用此技术的工具
技能应用方式
libfuzzer自动定义 FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION
aflpp通过编译器包装器支持该宏
honggfuzz使用该宏进行条件编译
cargo-fuzz为 Rust 条件编译设置 cfg!(fuzzing)
相关技术
技能关联
fuzz-harness-writing更好的测试桩可能无需绕过障碍;打补丁则有助于进行更深入的探索
coverage-analysis使用覆盖率识别障碍并衡量补丁效果
corpus-seeding种子语料库可能无需打补丁就能克服障碍
dictionary-generation字典有助于处理魔术字节,但无法应对校验和或复杂验证

资源

关键外部资源

OpenSSL 模糊测试文档 OpenSSL 的模糊测试基础设施展示了 FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION 的大规模应用。该项目使用此宏修改加密验证、证书解析和其他安全关键代码路径,以便进行更深入的模糊测试,同时确保生产环境中的正确性。

LibFuzzer 标志文档 LLVM 官方文档,介绍 libFuzzer 如何定义编译器宏,以及如何有效使用这些宏。涵盖与消毒器和覆盖率插桩的集成。

Rust cfg 属性参考 Rust 条件编译的完整参考,涵盖 cfg!(fuzzing) 和 cfg!(test)。说明编译时与运行时条件编译的区别及最佳实践。

评论

支持 Markdown