Ruzzy fuzzing campaign
Contract
| Field |
Bound contract |
| Trigger |
User needs Ruzzy to run coverage-guided fuzzing on Ruby code or a Ruby native extension. |
| Authority |
Reversible local: writes harness, tracer, sanitizer preload, corpus, and installed gem files to the working directory or the local Ruby gem path. Record the installed-gem list before install; rollback is file deletion plus uninstalling exactly the gem version this campaign added. No remote mutation. |
| Side effect |
Write Ruzzy harness scripts, tracer scripts, sanitizer LD_PRELOAD paths, corpus files, and install the target gem with clang and sanitizer flags. |
| Done |
Ruzzy executes the intended Ruby target with the correct tracer or extension setup and reproduces saved failures. |
Inputs
Required: Ruby target path or gem name, sanitizer selection (ASan or UBSan).
Optional: corpus directory path, libFuzzer arguments (e.g., -max_len=1024), crash file to reproduce.
Procedure
- Confirm target: pure Ruby (requires tracer) or C extension (single harness). Done when: the target kind is confirmed as pure Ruby or C extension.
- Confirm sanitizer: ASan (
Ruzzy::ASAN_PATH) or UBSan (Ruzzy::UBSAN_PATH). Done when: the sanitizer is selected and the selected Ruzzy::ASAN_PATH or Ruzzy::UBSAN_PATH constant is confirmed.
- For pure Ruby targets, write a tracer script calling
Ruzzy.trace('harness.rb') and a separate harness script calling Ruzzy.fuzz(test_one_input). For C extensions, write one harness script calling Ruzzy.fuzz(test_one_input); no tracer required. Done when: the harness and, for pure Ruby, the tracer script are written.
- Write the harness as a lambda named
test_one_input that accepts data and returns 0. Catch Ruby exceptions in C extension harnesses; let them propagate in pure Ruby harnesses. Done when: the harness lambda is written with the correct exception handling for the target kind.
- Set
ASAN_OPTIONS=allocator_may_return_null=1:detect_leaks=0:use_sigaltstack=0. Do not export LD_PRELOAD; use it inline with the ruby command. Done when: ASAN_OPTIONS is set and LD_PRELOAD is prepared for inline use.
- Record
gem list --local (each gem's name and version) before any install. Then install the gem with clang and sanitizer flags: set CC=clang, CXX=clang++, LDSHARED="clang -shared", and LDSHAREDXX="clang++ -shared"; set CFLAGS and CXXFLAGS to -fsanitize=address,fuzzer-no-link -fno-omit-frame-pointer -fno-common -fPIC -g for an ASan build, or -fsanitize=undefined,fuzzer-no-link -fno-omit-frame-pointer -fno-common -fPIC -g for a UBSan build, matching the sanitizer selected in step 2 so the instrumented library the run preloads matches the build. Also set MAKE="make --environment-overrides V=1" so GNU make lets environment CC, CFLAGS, and related values override the mkmf Makefile; without --environment-overrides, Ruby's extension build can ignore those flags and install an uninstrumented native library. The -shared flag is required for LDSHARED and LDSHAREDXX; without it the native extension link fails. Done when: the pre-install gem list is recorded and the gem is installed with clang and the flags matching the selected sanitizer.
- Run:
LD_PRELOAD=$(ruby -e 'require "ruzzy"; print Ruzzy::ASAN_PATH') ruby "<harness-or-tracer>.rb" [corpus] [libfuzzer-options] for ASan, or set SAN_PATH=$(ruby -e 'require "ruzzy"; print Ruzzy::UBSAN_PATH') and run LD_PRELOAD="$SAN_PATH" ruby "<harness-or-tracer>.rb" [corpus] [libfuzzer-options] for UBSan. Done when: the fuzzer runs with the correct LD_PRELOAD and harness.
- On
ERROR: AddressSanitizer: or ERROR: UndefinedBehaviorSanitizer:, capture the crash file path, Base64 content, and reproducer command. Write crash-* files to the working directory. Done when: any sanitizer error is captured with crash file path, content, and reproducer command, or the campaign completes without error.
- To reproduce a saved failure, run the same command passing the crash file path as the final argument. Done when: the saved failure is reproduced or confirmed non-reproducible.
Failure and recovery
| Failure class |
Meaning |
Recovery |
platform-missing |
Platform is not Linux x86-64/ARM64, clang is unavailable, or Ruby is not installed |
Halt; suggest Docker environment |
dependency-missing |
Gem not installed or wrong clang |
Install gem with sanitizer flags; verify the selected Ruzzy::ASAN_PATH or Ruzzy::UBSAN_PATH constant resolves |
harness-error |
Ruby exception exits the fuzzer |
Adjust exception handling for a C extension harness; a pure Ruby harness must not catch exceptions |
sanitizer-report |
ASan or UBSan error detected |
Capture crash file; report class, address, reproducer |
no-crashes-found |
Fuzzer ran without sanitizer violations |
Report campaign completed cleanly |
env-misconfigured |
Missing ASAN_OPTIONS or LD_PRELOAD |
Set ASAN_OPTIONS and re-run inline LD_PRELOAD |
Rollback: delete written harness, tracer, and corpus files. Diff the pre-install gem list --local against the current list and gem uninstall <name> -v <version> only the name-version pair this campaign added. Leave every pre-existing gem version in place. No VCS mutation.
Output
Fuzzing campaign report containing:
- Target gem or file fuzzed
- Sanitizer and version
- libFuzzer options used
- Execution duration and corpus state
- For each crash: sanitizer error type, crash file path, Base64 input, reproducer command
- If clean: total inputs, exec/s, coverage summary lines
1---2name: ruzzy3description: Use when asked to set up and run coverage-guided fuzzing of Ruby code or C extensions with Ruzzy, producing crash reports or clean campaign summaries. Not for C/C++ fuzzing: use libfuzzer or libfuzzer.4---5
6# Ruzzy fuzzing campaign
7
8## Contract
9
10| Field | Bound contract |
11|---|---|
12| Trigger | User needs Ruzzy to run coverage-guided fuzzing on Ruby code or a Ruby native extension. |
13| Authority | Reversible local: writes harness, tracer, sanitizer preload, corpus, and installed gem files to the working directory or the local Ruby gem path. Record the installed-gem list before install; rollback is file deletion plus uninstalling exactly the gem version this campaign added. No remote mutation. |
14| Side effect | Write Ruzzy harness scripts, tracer scripts, sanitizer LD_PRELOAD paths, corpus files, and install the target gem with clang and sanitizer flags. |
15| Done | Ruzzy executes the intended Ruby target with the correct tracer or extension setup and reproduces saved failures. |
16
17## Inputs
18
19Required: Ruby target path or gem name, sanitizer selection (ASan or UBSan).
20
21Optional: corpus directory path, libFuzzer arguments (e.g., `-max_len=1024`), crash file to reproduce.
22
23## Procedure
24
251. Confirm target: pure Ruby (requires tracer) or C extension (single harness). Done when: the target kind is confirmed as pure Ruby or C extension.
262. Confirm sanitizer: ASan (`Ruzzy::ASAN_PATH`) or UBSan (`Ruzzy::UBSAN_PATH`). Done when: the sanitizer is selected and the selected `Ruzzy::ASAN_PATH` or `Ruzzy::UBSAN_PATH` constant is confirmed.
273. For pure Ruby targets, write a tracer script calling `Ruzzy.trace('harness.rb')` and a separate harness script calling `Ruzzy.fuzz(test_one_input)`. For C extensions, write one harness script calling `Ruzzy.fuzz(test_one_input)`; no tracer required. Done when: the harness and, for pure Ruby, the tracer script are written.
284. Write the harness as a lambda named `test_one_input` that accepts data and returns `0`. Catch Ruby exceptions in C extension harnesses; let them propagate in pure Ruby harnesses. Done when: the harness lambda is written with the correct exception handling for the target kind.
295. Set `ASAN_OPTIONS=allocator_may_return_null=1:detect_leaks=0:use_sigaltstack=0`. Do not export `LD_PRELOAD`; use it inline with the ruby command. Done when: `ASAN_OPTIONS` is set and `LD_PRELOAD` is prepared for inline use.
306. Record `gem list --local` (each gem's name and version) before any install. Then install the gem with clang and sanitizer flags: set `CC=clang`, `CXX=clang++`, `LDSHARED="clang -shared"`, and `LDSHAREDXX="clang++ -shared"`; set `CFLAGS` and `CXXFLAGS` to `-fsanitize=address,fuzzer-no-link -fno-omit-frame-pointer -fno-common -fPIC -g` for an ASan build, or `-fsanitize=undefined,fuzzer-no-link -fno-omit-frame-pointer -fno-common -fPIC -g` for a UBSan build, matching the sanitizer selected in step 2 so the instrumented library the run preloads matches the build. Also set `MAKE="make --environment-overrides V=1"` so GNU make lets environment `CC`, `CFLAGS`, and related values override the mkmf Makefile; without `--environment-overrides`, Ruby's extension build can ignore those flags and install an uninstrumented native library. The `-shared` flag is required for `LDSHARED` and `LDSHAREDXX`; without it the native extension link fails. Done when: the pre-install gem list is recorded and the gem is installed with clang and the flags matching the selected sanitizer.
317. Run: `LD_PRELOAD=$(ruby -e 'require "ruzzy"; print Ruzzy::ASAN_PATH') ruby "<harness-or-tracer>.rb" [corpus] [libfuzzer-options]` for ASan, or set `SAN_PATH=$(ruby -e 'require "ruzzy"; print Ruzzy::UBSAN_PATH')` and run `LD_PRELOAD="$SAN_PATH" ruby "<harness-or-tracer>.rb" [corpus] [libfuzzer-options]` for UBSan. Done when: the fuzzer runs with the correct `LD_PRELOAD` and harness.
328. On `ERROR: AddressSanitizer:` or `ERROR: UndefinedBehaviorSanitizer:`, capture the crash file path, Base64 content, and reproducer command. Write `crash-*` files to the working directory. Done when: any sanitizer error is captured with crash file path, content, and reproducer command, or the campaign completes without error.
339. To reproduce a saved failure, run the same command passing the crash file path as the final argument. Done when: the saved failure is reproduced or confirmed non-reproducible.
34
35## Failure and recovery
36| Failure class | Meaning | Recovery |
37|---|---|---|
38| `platform-missing` | Platform is not Linux x86-64/ARM64, clang is unavailable, or Ruby is not installed | Halt; suggest Docker environment |
39| `dependency-missing` | Gem not installed or wrong clang | Install gem with sanitizer flags; verify the selected `Ruzzy::ASAN_PATH` or `Ruzzy::UBSAN_PATH` constant resolves |
40| `harness-error` | Ruby exception exits the fuzzer | Adjust exception handling for a C extension harness; a pure Ruby harness must not catch exceptions |
41| `sanitizer-report` | ASan or UBSan error detected | Capture crash file; report class, address, reproducer |
42| `no-crashes-found` | Fuzzer ran without sanitizer violations | Report campaign completed cleanly |
43| `env-misconfigured` | Missing ASAN_OPTIONS or LD_PRELOAD | Set ASAN_OPTIONS and re-run inline LD_PRELOAD |
44
45Rollback: delete written harness, tracer, and corpus files. Diff the pre-install `gem list --local` against the current list and `gem uninstall <name> -v <version>` only the name-version pair this campaign added. Leave every pre-existing gem version in place. No VCS mutation.
46
47## Output
48Fuzzing campaign report containing:
49
50- Target gem or file fuzzed
51- Sanitizer and version
52- libFuzzer options used
53- Execution duration and corpus state
54- For each crash: sanitizer error type, crash file path, Base64 input, reproducer command
55- If clean: total inputs, exec/s, coverage summary lines