Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions infra/base-images/base-builder-ruby/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,9 @@ RUN git clone https://github.com/trailofbits/ruzzy.git $SRC/ruzzy
RUN /usr/local/bin/install_ruby.sh
RUN /usr/local/bin/gem update --system 3.5.11

# Install simplecov for coverage builds
RUN gem install simplecov

# Install ruzzy
WORKDIR $SRC/ruzzy

Expand Down Expand Up @@ -51,3 +54,8 @@ ENV GEM_HOME="$OUT/fuzz-gem"
ENV GEM_PATH="/install/ruzzy"

COPY ruzzy-build /usr/bin/ruzzy-build
COPY ruzzy-build-coverage /usr/bin/ruzzy-build-coverage
COPY build_ruby_fuzzer /usr/bin/build_ruby_fuzzer
COPY ruzzy /usr/bin/ruzzy
COPY coverage_helper.rb /usr/local/lib/coverage_helper.rb
COPY ossfuzz_helper.rb /usr/local/lib/ossfuzz_helper.rb
54 changes: 54 additions & 0 deletions infra/base-images/base-builder-ruby/build_ruby_fuzzer
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
#!/bin/bash
# Copyright 2025 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
################################################################################
#
# Unified Ruby fuzzer builder - handles both normal fuzzing and coverage modes.
#
# Usage: build_ruby_fuzzer <fuzzer_harness.rb> <output_dir> <project_name>
#
# This script automatically detects the SANITIZER environment variable and:
# - For coverage: Creates coverage-instrumented wrapper via ruzzy-build-coverage
# - For normal fuzzing: Creates standard fuzzer wrapper via ruzzy-build
#
# Arguments:
# $1 - Path to the Ruby fuzzer harness file (e.g., /src/harnesses/fuzz_parse.rb)
# $2 - Output directory where the wrapper will be created (e.g., $OUT)
# $3 - Project name for corpus directory creation (e.g., ox-ruby)
#

fuzz_target_path="$1"
out_dir="$2"
project_name="$3"

if [ -z "$fuzz_target_path" ] || [ -z "$out_dir" ] || [ -z "$project_name" ]; then
echo "Error: Missing required arguments"
echo "Usage: build_ruby_fuzzer <fuzzer_harness.rb> <output_dir> <project_name>"
exit 1
fi

if [ ! -f "$fuzz_target_path" ]; then
echo "Error: Fuzzer harness not found: $fuzz_target_path"
exit 1
fi

if [[ "$SANITIZER" == "coverage" ]]; then
# Coverage mode: Use coverage wrapper builder
/usr/bin/ruzzy-build-coverage "$fuzz_target_path" "$out_dir" "$project_name"
else
# Normal fuzzing mode: Copy harness and use ruzzy-build
cp "$fuzz_target_path" "$out_dir/"
/usr/bin/ruzzy-build "$fuzz_target_path" "$out_dir"
fi
52 changes: 52 additions & 0 deletions infra/base-images/base-builder-ruby/coverage_helper.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
# frozen_string_literal: true
# Copyright 2024 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
################################################################################

# Coverage helper for Ruby fuzzers
# This script sets up SimpleCov for code coverage collection

require 'simplecov'
require 'json'

# Configure SimpleCov
SimpleCov.start do
# Set coverage directory from environment or use default
coverage_dir ENV['COVERAGE_DIR'] || File.join(Dir.pwd, 'coverage')

# Use simple formatter for now, we'll customize the output
formatter SimpleCov::Formatter::SimpleFormatter

# Track all files
track_files '**/*.rb'

# Add filters to exclude test files and gems
add_filter '/spec/'
add_filter '/test/'
add_filter 'ossfuzz_helper'
add_filter 'coverage_helper'

# Enable branch coverage for better insights
enable_coverage :branch

# Merge results from multiple runs
use_merging true
merge_timeout 3600
end

# Store coverage command name based on fuzzer
if ENV['FUZZER_NAME']
SimpleCov.command_name ENV['FUZZER_NAME']
end
48 changes: 48 additions & 0 deletions infra/base-images/base-builder-ruby/ossfuzz_helper.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
# Copyright 2025 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
################################################################################

# Only require ruzzy in normal fuzzing mode (not in coverage mode)
if ENV["COVERAGE_MODE"] != "true"
begin
require 'ruzzy'
rescue LoadError
# Ruzzy not available, will fail later if needed
end
end

module OSSFuzz
# Unified fuzzing entry point that handles both normal fuzzing and coverage modes.
#
# In normal fuzzing mode: calls Ruzzy.fuzz() to start the fuzzing engine
# In coverage mode: reads corpus file from ARGV[0] and calls the target directly
#
# Usage:
# fuzz_target = lambda do |data|
# # your fuzzing logic
# end
# OSSFuzz.fuzz(fuzz_target)
#
def self.fuzz(fuzz_target)
if ENV["COVERAGE_MODE"] == "true"
# Coverage mode: execute target on input file
data = File.binread(ARGV[0])
fuzz_target.call(data)
else
# Normal fuzzing mode
Ruzzy.fuzz(fuzz_target)
end
end
end
20 changes: 20 additions & 0 deletions infra/base-images/base-builder-ruby/ruzzy
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
#!/usr/bin/env bash
# Copyright 2024 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
################################################################################

ASAN_OPTIONS="allocator_may_return_null=1:detect_leaks=0:use_sigaltstack=0" \
LD_PRELOAD=$(ruby -e 'require "ruzzy"; print Ruzzy::ASAN_PATH') \
ruby $@
12 changes: 8 additions & 4 deletions infra/base-images/base-builder-ruby/ruzzy-build
Original file line number Diff line number Diff line change
Expand Up @@ -20,11 +20,15 @@ harness_sh=${fuzz_target::-3}

cp $1 $OUT/$fuzz_target

echo """#!/usr/bin/env bash
# Copy ossfuzz_helper.rb for OSSFuzz.fuzz() support
cp /usr/local/lib/ossfuzz_helper.rb $OUT/ossfuzz_helper.rb

cat > $OUT/$harness_sh << EOF
#!/usr/bin/env bash
# LLVMFuzzerTestOneInput for fuzzer detection.
this_dir=\$(dirname \"\$0\")
this_dir=\$(dirname "\$0")
export GEM_HOME=\$this_dir/fuzz-gem

ruzzy \$this_dir/$fuzz_target \$@
""" > $OUT/$harness_sh
ruzzy -I\$this_dir \$this_dir/$fuzz_target \$@
EOF
chmod +x $OUT/$harness_sh
94 changes: 94 additions & 0 deletions infra/base-images/base-builder-ruby/ruzzy-build-coverage
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
#!/bin/bash
# Copyright 2025 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
################################################################################
#
# Generic coverage wrapper generator for Ruby fuzzers.
#
# Usage: ruzzy-build-coverage <fuzzer_harness.rb> <output_dir> <project_name>
#
# This script creates a coverage-instrumented wrapper for a Ruby fuzzer harness.
# It modifies the harness to support both normal fuzzing (via Ruzzy.fuzz) and
# coverage mode (via direct execution on corpus files).
#
# Arguments:
# $1 - Path to the Ruby fuzzer harness file (e.g., /src/harnesses/fuzz_memory.rb)
# $2 - Output directory where the wrapper will be created (e.g., $OUT)
# $3 - Project name for corpus directory creation (e.g., ffi)
#

fuzz_target_path="$1"
out_dir="$2"
project_name="$3"

if [ ! -f "$fuzz_target_path" ]; then
echo "Error: Fuzzer harness not found: $fuzz_target_path"
exit 1
fi

fuzzer_name=$(basename "$fuzz_target_path" .rb)

echo "Building coverage wrapper for $fuzzer_name"

# Create corpus directory for this fuzzer
mkdir -p "../../build/corpus/${project_name}/${fuzzer_name}"

# Copy the harness as-is (no modification needed with OSSFuzz.fuzz())
cp "$fuzz_target_path" "$out_dir/${fuzzer_name}.rb"

# Ensure ossfuzz_helper.rb and coverage_helper.rb are available
cp /usr/local/lib/ossfuzz_helper.rb "$out_dir/"
cp /usr/local/lib/coverage_helper.rb "$out_dir/"

# Create bash wrapper that runs the modified harness with Ruby coverage
# IMPORTANT: Include OSSFuzz.fuzz comment for fuzzer detection by base-runner coverage script
cat > "$out_dir/$fuzzer_name" << 'WRAPPER_EOF'
#!/usr/bin/env bash
# Coverage wrapper for Ruby fuzzer
# OSSFuzz.fuzz - This comment is needed for fuzzer detection
this_dir=$(dirname "$0")
export GEM_HOME=$this_dir/fuzz-gem
export GEM_PATH=$this_dir/fuzz-gem:/usr/local/lib/ruby/gems/3.3.0

# If corpus directory provided, run fuzzer on each corpus file with coverage
if [ -n "$FUZZ_CORPUS_DIR" ] && [ -d "$FUZZ_CORPUS_DIR" ]; then
export COVERAGE_MODE=true
# COVERAGE_DIR is set by the coverage script
mkdir -p "$COVERAGE_DIR"

# Iterate over corpus files and run harness with coverage
for corpus_file in "$FUZZ_CORPUS_DIR"/*; do
if [ -f "$corpus_file" ]; then
ruby -I"$this_dir" -I/usr/local/lib -rcoverage_helper "$this_dir/FUZZER_NAME_PLACEHOLDER.rb" "$corpus_file" 2>/dev/null || true
fi
done

# Copy resultset to DUMPS_DIR root for coverage script to find
if [ -f "$COVERAGE_DIR/.resultset.json" ]; then
cp "$COVERAGE_DIR/.resultset.json" "$(dirname $COVERAGE_DIR)/FUZZER_NAME_PLACEHOLDER.resultset.json"
fi
else
# No corpus directory, just exit
echo "No corpus directory provided"
exit 0
fi
WRAPPER_EOF

# Replace placeholder with actual fuzzer name
sed -i "s/FUZZER_NAME_PLACEHOLDER/${fuzzer_name}/g" "$out_dir/$fuzzer_name"

chmod +x "$out_dir/$fuzzer_name"

echo "Created coverage wrapper: $out_dir/$fuzzer_name"
3 changes: 3 additions & 0 deletions infra/base-images/base-builder/install_ruby.sh
Original file line number Diff line number Diff line change
Expand Up @@ -30,4 +30,7 @@ cd ../
# Clean up the sources.
rm -rf ./ruby-$RUBY_VERSION ruby-$RUBY_VERSION.tar.gz

# Install simplecov for coverage builds
gem install simplecov

echo "Finished installing ruby"
7 changes: 7 additions & 0 deletions infra/base-images/base-runner/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -117,9 +117,15 @@ COPY --from=base-ruby /usr/local/bin/gem /usr/local/bin/gem
COPY --from=base-ruby /usr/local/lib/ruby /usr/local/lib/ruby
COPY --from=base-ruby /usr/local/include/ruby-3.3.0 /usr/local/include/ruby-3.3.0

# Install libyaml and simplecov for Ruby coverage reporting
RUN apt-get update && apt-get install -y libyaml-0-2 && \
gem install simplecov && \
apt-get clean && rm -rf /var/lib/apt/lists/*

# Do this last to make developing these files easier/faster due to caching.
COPY bad_build_check \
consolidate_ruby_coverage.sh \
consolidate_html.py \
coverage \
coverage_helper \
download_corpus \
Expand All @@ -128,6 +134,7 @@ COPY bad_build_check \
rcfilt \
reproduce \
run_fuzzer \
ruzzy \
parse_options.py \
generate_differential_cov_report.py \
profraw_update.py \
Expand Down
Loading
Loading