名称: ruzzy 类型: fuzzer 描述: > Ruzzy 是由 Trail of Bits 开发的一个覆盖引导的 Ruby 模糊测试工具。 用于模糊测试纯 Ruby 代码和 Ruby C 扩展。
Ruzzy
Ruzzy 是一个基于 libFuzzer 构建的覆盖引导模糊测试器,适用于 Ruby。它支持模糊测试纯 Ruby 代码和 Ruby C 扩展,并集成消毒器以检测内存腐蚀和未定义行为。
使用时机
Ruzzy 是目前唯一生产就绪的覆盖引导模糊测试器,专为 Ruby 设计。
选择 Ruzzy 当您需要:
- 模糊测试 Ruby 应用程序或库
- 测试 Ruby C 扩展的内存安全问题
- 为 Ruby 代码进行覆盖引导模糊测试
- 处理具有原生扩展的 Ruby gem
快速开始
设置环境:
export ASAN_OPTIONS="allocator_may_return_null=1:detect_leaks=0:use_sigaltstack=0"
使用内置的示例进行测试:
LD_PRELOAD=$(ruby -e 'require "ruzzy"; print Ruzzy::ASAN_PATH') \
ruby -e 'require "ruzzy"; Ruzzy.dummy'
这应该快速找到一个崩溃,证明 Ruzzy 工作正常。
安装
平台支持
Ruzzy 支持 Linux x86-64 和 AArch64/ARM64。对于 macOS 或 Windows,使用 Dockerfile 或 开发环境。
前提条件
- Linux x86-64 或 AArch64/ARM64
- 最近版本的 clang(测试支持到 14.0.0,推荐使用最新版本)
- 安装有 gem 的 Ruby
安装命令
使用 clang 编译器标志安装 Ruzzy:
MAKE="make --environment-overrides V=1" \
CC="/path/to/clang" \
CXX="/path/to/clang++" \
LDSHARED="/path/to/clang -shared" \
LDSHAREDXX="/path/to/clang++ -shared" \
gem install ruzzy
环境变量解释:
MAKE:覆盖 make 以尊重后续的环境变量CC、CXX、LDSHARED、LDSHAREDXX:确保使用正确的 clang 二进制文件以支持最新功能
安装故障排除
如果安装失败,启用调试输出:
RUZZY_DEBUG=1 gem install --verbose ruzzy
验证
通过运行示例验证安装(见快速开始部分)。
编写测试套件
模糊测试纯 Ruby 代码
纯 Ruby 模糊测试由于 Ruby 解释器的实现细节,需要两个脚本。
跟踪脚本(test_tracer.rb):
# frozen_string_literal: true
require 'ruzzy'
Ruzzy.trace('test_harness.rb')
测试套件脚本(test_harness.rb):
# frozen_string_literal: true
require 'ruzzy'
def fuzzing_target(input)
# 您的模糊测试代码在这里
if input.length == 4
if input[0] == 'F'
if input[1] == 'U'
if input[2] == 'Z'
if input[3] == 'Z'
raise
end
end
end
end
end
end
test_one_input = lambda do |data|
fuzzing_target(data)
return 0
end
Ruzzy.fuzz(test_one_input)
运行:
LD_PRELOAD=$(ruby -e 'require "ruzzy"; print Ruzzy::ASAN_PATH') \
ruby test_tracer.rb
模糊测试 Ruby C 扩展
C 扩展可以用单个测试套件文件进行模糊测试,无需跟踪脚本。
msgpack 示例测试套件(fuzz_msgpack.rb):
# frozen_string_literal: true
require 'msgpack'
require 'ruzzy'
test_one_input = lambda do |data|
begin
MessagePack.unpack(data)
rescue Exception
# 我们关注内存腐蚀,而非 Ruby 异常
end
return 0
end
Ruzzy.fuzz(test_one_input)
运行:
LD_PRELOAD=$(ruby -e 'require "ruzzy"; print Ruzzy::ASAN_PATH') \
ruby fuzz_msgpack.rb
测试套件规则
| 做 | 不 |
|---|---|
| 如果测试 C 扩展,捕获 Ruby 异常 | 让 Ruby 异常导致模糊测试器崩溃 |
| 从 test_one_input lambda 返回 0 | 返回其他值 |
| 保持测试套件确定性 | 使用随机性或基于时间的逻辑 |
| 纯 Ruby 代码使用跟踪脚本 | 纯 Ruby 代码跳过跟踪脚本 |
另请参见: 关于详细的测试套件编写技巧、处理复杂输入的模式和高级策略,请参阅 fuzz-harness-writing 技能。
编译
使用消毒器安装 Gems
为模糊测试安装带有 C 扩展的 Ruby gem 时,使用消毒器标志编译:
MAKE="make --environment-overrides V=1" \
CC="/path/to/clang" \
CXX="/path/to/clang++" \
LDSHARED="/path/to/clang -shared" \
LDSHAREDXX="/path/to/clang++ -shared" \
CFLAGS="-fsanitize=address,fuzzer-no-link -fno-omit-frame-pointer -fno-common -fPIC -g" \
CXXFLAGS="-fsanitize=address,fuzzer-no-link -fno-omit-frame-pointer -fno-common -fPIC -g" \
gem install <gem-name>
构建标志
| 标志 | 目的 |
|---|---|
-fsanitize=address,fuzzer-no-link |
启用 AddressSanitizer 和模糊测试器仪器 |
-fno-omit-frame-pointer |
提高堆栈跟踪质量 |
-fno-common |
与消毒器更好的兼容性 |
-fPIC |
位置无关代码,适用于共享库 |
-g |
包含调试符号 |
运行测试活动
环境设置
在运行任何模糊测试活动之前,设置 ASAN_OPTIONS:
export ASAN_OPTIONS="allocator_may_return_null=1:detect_leaks=0:use_sigaltstack=0"
选项解释:
allocator_may_return_null=1:跳过常见的低影响分配失败(DoS)detect_leaks=0:Ruby 解释器泄漏数据,暂时忽略这些use_sigaltstack=0:Ruby 建议在使用 ASan 时禁用 sigaltstack
基本运行
LD_PRELOAD=$(ruby -e 'require "ruzzy"; print Ruzzy::ASAN_PATH') \
ruby harness.rb
注意: LD_PRELOAD 是消毒器注入所必需的。与 ASAN_OPTIONS 不同,不要导出它,因为它可能干扰其他程序。
使用语料库
LD_PRELOAD=$(ruby -e 'require "ruzzy"; print Ruzzy::ASAN_PATH') \
ruby harness.rb /path/to/corpus
传递 libFuzzer 选项
所有 libFuzzer 选项可以作为参数传递:
LD_PRELOAD=$(ruby -e 'require "ruzzy"; print Ruzzy::ASAN_PATH') \
ruby harness.rb /path/to/corpus -max_len=1024 -timeout=10
参见 libFuzzer 选项 获取完整参考。
重现崩溃
通过传递崩溃文件重新运行崩溃案例:
LD_PRELOAD=$(ruby -e 'require "ruzzy"; print Ruzzy::ASAN_PATH') \
ruby harness.rb ./crash-253420c1158bc6382093d409ce2e9cff5806e980
解释输出
| 输出 | 含义 |
|---|---|
INFO: Running with entropic power schedule |
模糊测试活动已启动 |
ERROR: AddressSanitizer: heap-use-after-free |
检测到内存腐蚀 |
SUMMARY: libFuzzer: fuzz target exited |
发生 Ruby 异常 |
artifact_prefix='./'; Test unit written to ./crash-* |
崩溃输入已保存 |
Base64: ... |
崩溃输入的 Base64 编码 |
消毒器集成
AddressSanitizer (ASan)
Ruzzy 包含预编译的 AddressSanitizer 库:
LD_PRELOAD=$(ruby -e 'require "ruzzy"; print Ruzzy::ASAN_PATH') \
ruby harness.rb
使用 ASan 检测:
- 堆缓冲区溢出
- 栈缓冲区溢出
- 使用后释放
- 双重释放
- 内存泄漏(Ruzzy 中默认禁用)
UndefinedBehaviorSanitizer (UBSan)
Ruzzy 也包含 UBSan:
LD_PRELOAD=$(ruby -e 'require "ruzzy"; print Ruzzy::UBSAN_PATH') \
ruby harness.rb
使用 UBSan 检测:
- 有符号整数溢出
- 空指针解引用
- 内存访问未对齐
- 除以零
常见消毒器问题
| 问题 | 解决方案 |
|---|---|
| Ruby 解释器泄漏警告 | 使用 ASAN_OPTIONS=detect_leaks=0 |
| Sigaltstack 冲突 | 使用 ASAN_OPTIONS=use_sigaltstack=0 |
| 分配失败垃圾信息 | 使用 ASAN_OPTIONS=allocator_may_return_null=1 |
| LD_PRELOAD 干扰工具 | 不要导出;在 ruby 命令中内联设置 |
另请参见: 关于详细的消毒器配置、常见问题和高级标志,请参阅 address-sanitizer 和 undefined-behavior-sanitizer 技能。
真实世界示例
示例: msgpack-ruby
模糊测试 msgpack MessagePack 解析器以检测内存腐蚀。
使用消毒器安装:
MAKE="make --environment-overrides V=1" \
CC="/path/to/clang" \
CXX="/path/to/clang++" \
LDSHARED="/path/to/clang -shared" \
LDSHAREDXX="/path/to/clang++ -shared" \
CFLAGS="-fsanitize=address,fuzzer-no-link -fno-omit-frame-pointer -fno-common -fPIC -g" \
CXXFLAGS="-fsanitize=address,fuzzer-no-link -fno-omit-frame-pointer -fno-common -fPIC -g" \
gem install msgpack
测试套件(fuzz_msgpack.rb):
# frozen_string_literal: true
require 'msgpack'
require 'ruzzy'
test_one_input = lambda do |data|
begin
MessagePack.unpack(data)
rescue Exception
# 我们关注内存腐蚀,而非 Ruby 异常
end
return 0
end
Ruzzy.fuzz(test_one_input)
运行:
export ASAN_OPTIONS="allocator_may_return_null=1:detect_leaks=0:use_sigaltstack=0"
LD_PRELOAD=$(ruby -e 'require "ruzzy"; print Ruzzy::ASAN_PATH') \
ruby fuzz_msgpack.rb
示例: 纯 Ruby 目标
模糊测试带有自定义解析器的纯 Ruby 代码。
跟踪脚本(test_tracer.rb):
# frozen_string_literal: true
require 'ruzzy'
Ruzzy.trace('test_harness.rb')
测试套件(test_harness.rb):
# frozen_string_literal: true
require 'ruzzy'
require_relative 'my_parser'
test_one_input = lambda do |data|
begin
MyParser.parse(data)
rescue StandardError
# 来自格式错误输入的预期异常
end
return 0
end
Ruzzy.fuzz(test_one_input)
运行:
export ASAN_OPTIONS="allocator_may_return_null=1:detect_leaks=0:use_sigaltstack=0"
LD_PRELOAD=$(ruby -e 'require "ruzzy"; print Ruzzy::ASAN_PATH') \
ruby test_tracer.rb
故障排除
| 问题 | 原因 | 解决方案 |
|---|---|---|
| 安装失败 | 错误的 clang 版本或路径 | 验证 clang 路径,使用 clang 14.0.0+ |
cannot open shared object file |
LD_PRELOAD 未设置 | 在 ruby 命令中内联设置 LD_PRELOAD |
| 模糊测试器立即退出 | 缺少语料库目录 | 创建语料库目录或作为参数传递 |
| 无覆盖进展 | 纯 Ruby 需要跟踪脚本 | 纯 Ruby 代码使用跟踪脚本 |
| 泄漏检测垃圾信息 | Ruby 解释器泄漏 | 设置 ASAN_OPTIONS=detect_leaks=0 |
| 需要安装调试 | 编译错误 | 使用 RUZZY_DEBUG=1 gem install --verbose ruzzy |
相关技能
技术技能
| 技能 | 用例 |
|---|---|
| fuzz-harness-writing | 编写有效测试套件的详细指导 |
| address-sanitizer | 模糊测试期间的内存错误检测 |
| undefined-behavior-sanitizer | 检测 C 扩展中的未定义行为 |
| libfuzzer | 理解 libFuzzer 选项(Ruzzy 基于 libFuzzer 构建) |
相关模糊测试器
| 技能 | 何时考虑 |
|---|---|
| libfuzzer | 直接在 C/C++ 中模糊测试 Ruby C 扩展代码时 |
| aflpp | 通过仪器 Ruby 解释器模糊测试 Ruby 的替代方法 |
资源
关键外部资源
介绍 Ruzzy,一个覆盖引导的 Ruby 模糊测试器 Trail of Bits 官方博客文章,介绍 Ruzzy 的动机、架构和初步结果。
Ruzzy GitHub 仓库 源代码、额外示例和开发说明。
libFuzzer 文档 由于 Ruzzy 基于 libFuzzer 构建,理解 libFuzzer 选项和行为很有价值。
模糊测试 Ruby C 扩展 关于使用编译标志和示例模糊测试 C 扩展的详细指南。
模糊测试纯 Ruby 代码 关于纯 Ruby 模糊测试所需的跟踪模式的详细指南。