From f95a03d4f8f929d0b50c3652535262632f6aa625 Mon Sep 17 00:00:00 2001 From: Matt Schwager Date: Wed, 24 Jan 2024 09:47:31 -0700 Subject: [PATCH] Add initial README, and other minor changes --- Dockerfile | 5 +- README.md | 162 +++++++++++++++++++++++++++++++++++++++++--- Rakefile | 2 + ext/cruzzy/cruzzy.c | 2 +- ruzzy.gemspec | 1 + 5 files changed, 160 insertions(+), 12 deletions(-) diff --git a/Dockerfile b/Dockerfile index 206428b..88f0c91 100644 --- a/Dockerfile +++ b/Dockerfile @@ -3,6 +3,7 @@ FROM debian:12-slim RUN apt update && apt install -y \ binutils \ gcc \ + g++ \ libc-dev \ make \ ruby \ @@ -45,9 +46,9 @@ ENV ASAN_STRIPPED_LIB "/tmp/libclang_rt.asan.a" ENV ASAN_MERGED_LIB "/tmp/asan_with_fuzzer.so" # https://github.com/google/atheris/blob/master/native_extension_fuzzing.md#why-this-is-necessary -RUN cp "$ASAN_LIB" /tmp +RUN cp "$ASAN_LIB" "$ASAN_STRIPPED_LIB" RUN ar d "$ASAN_STRIPPED_LIB" asan_preinit.cc.o asan_preinit.cpp.o -RUN "$CC" \ +RUN "$CXX" \ -Wl,--whole-archive \ "$FUZZER_NO_MAIN_LIB" \ "$ASAN_STRIPPED_LIB" \ diff --git a/README.md b/README.md index f6f0ca1..2e754de 100644 --- a/README.md +++ b/README.md @@ -1,12 +1,156 @@ -# ruzzy +# Ruzzy -A Ruby C extension fuzzer +A Ruby C extension fuzzer. -# Reading list +Ruzzy is based on Google's [Atheris Python fuzzer](https://github.com/google/atheris). Unlike Atheris, Ruzzy is focused on fuzzing Ruby C extensions and not Ruby code itself. This may change in the future as the project gains traction. -- https://guides.rubygems.org/gems-with-extensions/ -- https://www.rubyguides.com/2018/03/write-ruby-c-extension/ -- https://rubyreferences.github.io/rubyref/advanced/extensions.html -- https://ruby-doc.org/3.3.0/stdlibs/mkmf/MakeMakefile.html -- https://github.com/ruby/ruby/blob/master/lib/mkmf.rb -- https://github.com/flavorjones/ruby-c-extensions-explained +# Installing + +Ruzzy relies on Docker for both development and production fuzzer usage. + +You can build the Ruzzy Docker image with the following command: + +```bash +docker build --tag ruzzy . +``` + +_You may want to grab a cup of coffee, the initial build can take a while._ + +# Using + +## Getting started + +Ruzzy includes a [toy example](https://llvm.org/docs/LibFuzzer.html#toy-example) to demonstrate how it works. + +You can run the example with the following command: + +```bash +$ docker run -v $(pwd):/tmp/output/ ruzzy -artifact_prefix=/tmp/output/ +INFO: Running with entropic power schedule (0xFF, 100). +INFO: Seed: 1250491632 +... +==2==ABORTING +MS: 1 CopyPart-; base unit: 253420c1158bc6382093d409ce2e9cff5806e980 +0x48,0x49,0x48,0x49, +HIHI +artifact_prefix='/tmp/output/'; Test unit written to /tmp/output/crash-53551f97ce4b956f4bfcdeec9eb8d01b5d5533a7 +Base64: SElISQ== +``` + +This should produce a crash relatively quickly. We can inspect the crash with the following command: + +```bash +$ xxd crash-53551f97ce4b956f4bfcdeec9eb8d01b5d5533a7 +00000000: 4849 4849 HIHI +``` + +The Docker volume and `-artifact_prefix` flag will persist any crashes within the container into the host's filesystem. This highlights one of Ruzzy's features: flags passed to the Docker container are then [sent to libFuzzer](https://llvm.org/docs/LibFuzzer.html#options). You can use this functionality to re-run crash files: + +```bash +$ docker run -v $(pwd):/tmp/output/ ruzzy /tmp/output/crash-53551f97ce4b956f4bfcdeec9eb8d01b5d5533a7 +INFO: Running with entropic power schedule (0xFF, 100). +INFO: Seed: 1672214264 +... +Running: /tmp/output/crash-53551f97ce4b956f4bfcdeec9eb8d01b5d5533a7 +... +Executed /tmp/output/crash-53551f97ce4b956f4bfcdeec9eb8d01b5d5533a7 in 2 ms +*** +*** NOTE: fuzzing was not performed, you have only +*** executed the target code on a fixed set of inputs. +*** +``` + +You can also use this functionality to pass in a fuzzing corpus: + +```bash +docker run -v $(pwd)/corpus:/tmp/corpus -v $(pwd):/tmp/output/ ruzzy /tmp/corpus +``` + +## Fuzzing third-party libraries + +There are two primary ways you may want to fuzz third-party libraries: 1) modify the `Dockerfile` and `entrypoint.sh` script, and/or 2) shell into a Ruzzy container. This section will focus on (2). + +You can get a shell in the Ruzzy environment with the following command: + +```bash +docker run -it -v $(pwd):/app/ruzzy --entrypoint /bin/bash ruzzy +``` + +Let's fuzz the [`msgpack-ruby`](https://github.com/msgpack/msgpack-ruby) library as an example. First, install the gem: + +```bash +gem install --verbose msgpack +``` + +Next, we need a fuzzing target for `msgpack`. + +The following is a basic example that should be familiar to those with [libFuzzer experience](https://llvm.org/docs/LibFuzzer.html#fuzz-target): + +```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) +``` + +Let's call this file `fuzz_msgpack.rb`. + +You can run this file and start fuzzing with the following command: + +```bash +LD_PRELOAD=${ASAN_MERGED_LIB} ruby -Ilib fuzz_msgpack.rb +``` + +_`LD_PRELOAD` is required for the same reasons [as Atheris](https://github.com/google/atheris/blob/master/native_extension_fuzzing.md#option-a-sanitizerlibfuzzer-preloads)._ + +# Developing + +Development is done primarily within the Docker container. + +First, shell into the container using the `docker run ... --entrypoint` command above. + +## Testing + +We use `rake` unit tests to test Ruby code. + +You can run the tests within the container with the following command: + +```bash +rake test +``` + +## Linting + +We use `rubocop` to lint Ruby code. + +You can run `rubocop` within the container with the following command: + +```bash +rubocop +``` + +# Recommended reading + +- Ruby C extensions + - https://guides.rubygems.org/gems-with-extensions/ + - https://www.rubyguides.com/2018/03/write-ruby-c-extension/ + - https://rubyreferences.github.io/rubyref/advanced/extensions.html + - https://ruby-doc.org/3.3.0/stdlibs/mkmf/MakeMakefile.html + - https://github.com/flavorjones/ruby-c-extensions-explained + - https://github.com/ruby/ruby/blob/v3_1_2/lib/mkmf.rb +- Atheris + - https://github.com/google/atheris/blob/master/native_extension_fuzzing.md + - https://security.googleblog.com/2020/12/how-atheris-python-fuzzer-works.html + - https://github.com/google/atheris/blob/2.3.0/setup.py + - https://github.com/google/atheris/blob/2.3.0/src/native/core.cc diff --git a/Rakefile b/Rakefile index ad73624..ab8d64f 100644 --- a/Rakefile +++ b/Rakefile @@ -4,6 +4,8 @@ require 'rake/testtask' require 'rake/extensiontask' Rake::TestTask.new do |t| + # This is required for tests that use cruzzy functionality + ENV['LD_PRELOAD'] = ENV['ASAN_MERGED_LIB'] t.verbose = true end diff --git a/ext/cruzzy/cruzzy.c b/ext/cruzzy/cruzzy.c index d2e8ad1..6e85daf 100644 --- a/ext/cruzzy/cruzzy.c +++ b/ext/cruzzy/cruzzy.c @@ -117,7 +117,7 @@ static VALUE c_dummy_test_one_input(VALUE self, VALUE data) void Init_cruzzy() { - VALUE ruzzy = rb_const_get(rb_cObject, rb_intern("Ruzzy"));; + VALUE ruzzy = rb_const_get(rb_cObject, rb_intern("Ruzzy")); rb_define_module_function(ruzzy, "c_fuzz", &c_fuzz, 2); rb_define_module_function(ruzzy, "c_libfuzzer_is_loaded", &c_libfuzzer_is_loaded, 0); rb_define_module_function(ruzzy, "c_dummy_test_one_input", &c_dummy_test_one_input, 1); diff --git a/ruzzy.gemspec b/ruzzy.gemspec index 14c322b..e030915 100644 --- a/ruzzy.gemspec +++ b/ruzzy.gemspec @@ -16,4 +16,5 @@ Gem::Specification.new do |s| s.add_development_dependency 'rake', '~> 13.0' s.add_development_dependency 'rake-compiler', '~> 1.2' + s.add_development_dependency 'rubocop', '~> 1.60' end