skillby trailofbits

ruzzy

Ruzzy is a coverage-guided Ruby fuzzer by Trail of Bits. Use for fuzzing pure Ruby code and Ruby C extensions.

Installs: 0
Used in: 1 repos
Updated: 0mo ago
$npx ai-builder add skill trailofbits/ruzzy

Installs to .claude/skills/ruzzy/

# Ruzzy

Ruzzy is a coverage-guided fuzzer for Ruby built on libFuzzer. It enables fuzzing both pure Ruby code and Ruby C extensions with sanitizer support for detecting memory corruption and undefined behavior.

## When to Use

Ruzzy is currently the only production-ready coverage-guided fuzzer for Ruby.

**Choose Ruzzy when:**
- Fuzzing Ruby applications or libraries
- Testing Ruby C extensions for memory safety issues
- You need coverage-guided fuzzing for Ruby code
- Working with Ruby gems that have native extensions

## Quick Start

Set up environment:
```bash
export ASAN_OPTIONS="allocator_may_return_null=1:detect_leaks=0:use_sigaltstack=0"
```

Test with the included toy example:
```bash
LD_PRELOAD=$(ruby -e 'require "ruzzy"; print Ruzzy::ASAN_PATH') \
    ruby -e 'require "ruzzy"; Ruzzy.dummy'
```

This should quickly find a crash demonstrating that Ruzzy is working correctly.

## Installation

### Platform Support

Ruzzy supports Linux x86-64 and AArch64/ARM64. For macOS or Windows, use the [Dockerfile](https://github.com/trailofbits/ruzzy/blob/main/Dockerfile) or [development environment](https://github.com/trailofbits/ruzzy#developing).

### Prerequisites

- Linux x86-64 or AArch64/ARM64
- Recent version of clang (tested back to 14.0.0, latest release recommended)
- Ruby with gem installed

### Installation Command

Install Ruzzy with clang compiler flags:

```bash
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
```

**Environment variables explained:**
- `MAKE`: Overrides make to respect subsequent environment variables
- `CC`, `CXX`, `LDSHARED`, `LDSHAREDXX`: Ensure proper clang binaries are used for latest features

### Troubleshooting Installation

If installation fails, enable debug output:

```bash
RUZZY_DEBUG=1 gem install --verbose ruzzy
```

### Verification

Verify installation by running the toy example (see Quick Start section).

## Writing a Harness

### Fuzzing Pure Ruby Code

Pure Ruby fuzzing requires two scripts due to Ruby interpreter implementation details.

**Tracer script (`test_tracer.rb`):**

```ruby
# frozen_string_literal: true

require 'ruzzy'

Ruzzy.trace('test_harness.rb')
```

**Harness script (`test_harness.rb`):**

```ruby
# 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)
```

Run with:

```bash
LD_PRELOAD=$(ruby -e 'require "ruzzy"; print Ruzzy::ASAN_PATH') \
    ruby test_tracer.rb
```

### Fuzzing Ruby C Extensions

C extensions can be fuzzed with a single harness file, no tracer needed.

**Example harness for msgpack (`fuzz_msgpack.rb`):**

```ruby
# 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)
```

Run with:

```bash
LD_PRELOAD=$(ruby -e 'require "ruzzy"; print Ruzzy::ASAN_PATH') \
    ruby fuzz_msgpack.rb
```

### Harness Rules

| Do | Don't |
|----|-------|
| Catch Ruby exceptions if testing C extensions | Let Ruby exceptions crash the fuzzer |
| Return 0 from test_one_input lambda | Return other values |
| Keep harness deterministic | Use randomness or time-based logic |
| Use tracer script for pure Ruby | Skip tracer for pure Ruby code |

> **See Also:** For detailed harness writing techniques, patterns for handling complex inputs,
> and advanced strategies, see the **fuzz-harness-writing** technique skill.

## Compilation

### Installing Gems with Sanitizers

When installing Ruby gems with C extensions for fuzzing, compile with sanitizer flags:

```bash
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>
```

### Build Flags

| Flag | Purpose |
|------|---------|
| `-fsanitize=address,fuzzer-no-link` | Enable AddressSanitizer and fuzzer instrumentation |
| `-fno-omit-frame-pointer` | Improve stack trace quality |
| `-fno-common` | Better compatibility with sanitizers |
| `-fPIC` | Position-independent code for shared libraries |
| `-g` | Include debug symbols |

## Running Campaigns

### Environment Setup

Before running any fuzzing campaign, set ASAN_OPTIONS:

```bash
export ASAN_OPTIONS="allocator_may_return_null=1:detect_leaks=0:use_sigaltstack=0"
```

**Options explained:**
1. `allocator_may_return_null=1`: Skip common low-impact allocation failures (DoS)
2. `detect_leaks=0`: Ruby interpreter leaks data, ignore these for now
3. `use_sigaltstack=0`: Ruby recommends disabling sigaltstack with ASan

### Basic Run

```bash
LD_PRELOAD=$(ruby -e 'require "ruzzy"; print Ruzzy::ASAN_PATH') \
    ruby harness.rb
```

**Note:** `LD_PRELOAD` is required for sanitizer injection. Unlike `ASAN_OPTIONS`, do not export it as it may interfere with other programs.

### With Corpus

```bash
LD_PRELOAD=$(ruby -e 'require "ruzzy"; print Ruzzy::ASAN_PATH') \
    ruby harness.rb /path/to/corpus
```

### Passing libFuzzer Options

All libFuzzer options can be passed as arguments:

```bash
LD_PRELOAD=$(ruby -e 'require "ruzzy"; print Ruzzy::ASAN_PATH') \
    ruby harness.rb /path/to/corpus -max_len=1024 -timeout=10
```

See [libFuzzer options](https://llvm.org/docs/LibFuzzer.html#options) for full reference.

### Reproducing Crashes

Re-run a crash case by passing the crash file:

```bash
LD_PRELOAD=$(ruby -e 'require "ruzzy"; print Ruzzy::ASAN_PATH') \
    ruby harness.rb ./crash-253420c1158bc6382093d409ce2e9cff5806e980
```

### Interpreting Output

| Output | Meaning |
|--------|---------|
| `INFO: Running with entropic power schedule` | Fuzzing campaign started |
| `ERROR: AddressSanitizer: heap-use-after-free` | Memory corruption detected |
| `SUMMARY: libFuzzer: fuzz target exited` | Ruby exception occurred |
| `artifact_prefix='./'; Test unit written to ./crash-*` | Crash input saved |
| `Base64: ...` | Base64 encoding of crash input |

## Sanitizer Integration

### AddressSanitizer (ASan)

Ruzzy includes a pre-compiled AddressSanitizer library:

```bash
LD_PRELOAD=$(ruby -e 'require "ruzzy"; print Ruzzy::ASAN_PATH') \
    ruby harness.rb
```

Use ASan for detecting:
- Heap buffer overflows
- Stack buffer overflows
- Use-after-free
- Double-free
- Memory leaks (disabled by default in Ruzzy)

### UndefinedBehaviorSanitizer (UBSan)

Ruzzy also includes UBSan:

```bash
LD_PRELOAD=$(ruby -e 'require "ruzzy"; print Ruzzy::UBSAN_PATH') \
    ruby harness.rb
```

Use UBSan for detecting:
- Signed integer overflow
- Null pointer dereferences
- Misaligned memory access
- Division by zero

### Common Sanitizer Issues

| Issue | Solution |
|-------|----------|
| Ruby interpreter leak warnings | Use `ASAN_OPTIONS=detect_leaks=0` |
| Sigaltstack conflicts | Use `ASAN_OPTIONS=use_sigaltstack=0` |
| Allocation failure spam | Use `ASAN_OPTIONS=allocator_may_return_null=1` |
| LD_PRELOAD interferes with tools | Don't export it; set inline with ruby command |

> **See Also:** For detailed sanitizer configuration, common issues, and advanced flags,
> see the **address-sanitizer** and **undefined-behavior-sanitizer** technique skills.

## Real-World Examples

### Example: msgpack-ruby

Fuzzing the msgpack MessagePack parser for memory corruption.

**Install with sanitizers:**

```bash
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
```

**Harness (`fuzz_msgpack.rb`):**

```ruby
# 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)
```

**Run:**

```bash
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
```

### Example: Pure Ruby Target

Fuzzing pure Ruby code with a custom parser.

**Tracer (`test_tracer.rb`):**

```ruby
# frozen_string_literal: true

require 'ruzzy'

Ruzzy.trace('test_harness.rb')
```

**Harness (`test_harness.rb`):**

```ruby
# 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)
```

**Run:**

```bash
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
```

## Troubleshooting

| Problem | Cause | Solution |
|---------|-------|----------|
| Installation fails | Wrong clang version or path | Verify clang path, use clang 14.0.0+ |
| `cannot open shared object file` | LD_PRELOAD not set | Set LD_PRELOAD inline with ruby command |
| Fuzzer immediately exits | Missing corpus directory | Create corpus directory or pass as argument |
| No coverage progress | Pure Ruby needs tracer | Use tracer script for pure Ruby code |
| Leak detection spam | Ruby interpreter leaks | Set `ASAN_OPTIONS=detect_leaks=0` |
| Installation debug needed | Compilation errors | Use `RUZZY_DEBUG=1 gem install --verbose ruzzy` |

## Related Skills

### Technique Skills

| Skill | Use Case |
|-------|----------|
| **fuzz-harness-writing** | Detailed guidance on writing effective harnesses |
| **address-sanitizer** | Memory error detection during fuzzing |
| **undefined-behavior-sanitizer** | Detecting undefined behavior in C extensions |
| **libfuzzer** | Understanding libFuzzer options (Ruzzy is built on libFuzzer) |

### Related Fuzzers

| Skill | When to Consider |
|-------|------------------|
| **libfuzzer** | When fuzzing Ruby C extension code directly in C/C++ |
| **aflpp** | Alternative approach for fuzzing Ruby by instrumenting Ruby interpreter |

## Resources

### Key External Resources

**[Introducing Ruzzy, a coverage-guided Ruby fuzzer](https://blog.trailofbits.com/2024/03/29/introducing-ruzzy-a-coverage-guided-ruby-fuzzer/)**
Official Trail of Bits blog post announcing Ruzzy, covering motivation, architecture, and initial results.

**[Ruzzy GitHub Repository](https://github.com/trailofbits/ruzzy)**
Source code, additional examples, and development instructions.

**[libFuzzer Documentation](https://llvm.org/docs/LibFuzzer.html)**
Since Ruzzy is built on libFuzzer, understanding libFuzzer options and behavior is valuable.

**[Fuzzing Ruby C extensions](https://github.com/trailofbits/ruzzy#fuzzing-ruby-c-extensions)**
Detailed guide on fuzzing C extensions with compilation flags and examples.

**[Fuzzing pure Ruby code](https://github.com/trailofbits/ruzzy#fuzzing-pure-ruby-code)**
Detailed guide on the tracer pattern required for pure Ruby fuzzing.

Quick Install

$npx ai-builder add skill trailofbits/ruzzy

Details

Type
skill
Slug
trailofbits/ruzzy
Created
0mo ago