关于 ruzzy
ruzzy 是基于 libFuzzer 的 Ruby 覆盖引导模糊测试工具,适用于 Ruby 应用和库。它可测试纯 Ruby 代码及 C 扩展,并通过 Sanitizer 检测内存破坏和未定义行为。
主要功能
- 01Ruby 覆盖引导模糊测试
- 02支持纯 Ruby 与 C 扩展测试框架
- 03支持 AddressSanitizer 和 UBSan
- 04传入 libFuzzer 选项和语料库
适用场景
- 01模糊测试 Ruby 应用或库
- 02检查 Ruby C 扩展的内存安全
- 03重现已保存输入触发的崩溃
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ trailofbits/ skills --skill ruzzy安装后在 Claude Code 中输入/ruzzy确认已加载。
附带文件agents/openai.yamlassets/trail-of-bits-mark.svg
详细介绍
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
验证
运行玩具示例以验证安装(请参阅“快速开始”部分)。
编写 Harness
对纯 Ruby 代码进行模糊测试
由于 Ruby 解释器的实现细节,纯 Ruby 模糊测试需要两个脚本。
跟踪器脚本(test_tracer.rb):
# frozen_string_literal: true
require 'ruzzy'
Ruzzy.trace('test_harness.rb')
Harness 脚本(test_harness.rb):
# frozen_string_literal: true
require 'ruzzy'
def fuzzing_target(input)
# Your code to fuzz here
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 扩展可使用单个 Harness 文件进行模糊测试,无需跟踪器。
msgpack 的 Harness 示例(fuzz_msgpack.rb):
# frozen_string_literal: true
require 'msgpack'
require 'ruzzy'
test_one_input = lambda do |data|
begin
MessagePack.unpack(data)
rescue Exception
# We're looking for memory corruption, not Ruby exceptions
end
return 0
end
Ruzzy.fuzz(test_one_input)
运行命令:
LD_PRELOAD=$(ruby -e 'require "ruzzy"; print Ruzzy::ASAN_PATH') \
ruby fuzz_msgpack.rb
Harness 规则
| 应做 | 不要做 |
|---|---|
| 测试 C 扩展时捕获 Ruby 异常 | 让 Ruby 异常导致模糊测试器崩溃 |
| 从 test_one_input lambda 返回 0 | 返回其他值 |
| 保持 Harness 确定性 | 使用随机性或基于时间的逻辑 |
| 对纯 Ruby 使用跟踪器脚本 | 对纯 Ruby 代码跳过跟踪器 |
**另请参阅:**有关详细的 Harness 编写技巧、处理复杂输入的模式, 以及高级策略,请参阅 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
# We're looking for memory corruption, not Ruby exceptions
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
# Expected exceptions from malformed input
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 模糊测试所需跟踪器模式的详细指南。