diff --git a/Makefile.am b/Makefile.am index 8b43e02d..01eec6d2 100644 --- a/Makefile.am +++ b/Makefile.am @@ -337,6 +337,21 @@ distclean-local: -rm -rf $(top_builddir)/test-arena -rm -f lib-built +# Fuzzing targets - delegate to separate makefile +fuzz-help fuzz-corpus fuzz-test fuzz-analyze fuzz-clean fuzz-check fuzz-ci fuzz-stats: + @if [ -f Makefile.fuzz ]; then \ + $(MAKE) -f Makefile.fuzz $@; \ + else \ + echo "Fuzzing not available - Makefile.fuzz not found"; \ + fi + +fuzz-filterdiff fuzz-interdiff fuzz-rediff fuzz-grepdiff fuzz-lsdiff: + @if [ -f Makefile.fuzz ]; then \ + $(MAKE) -f Makefile.fuzz $@; \ + else \ + echo "Fuzzing not available - Makefile.fuzz not found"; \ + fi + EXTRA_DIST = $(man_MANS) \ tests/common.sh tests/soak-test \ $(TESTS) $(XFAIL_TESTS) \ @@ -347,6 +362,9 @@ EXTRA_DIST = $(man_MANS) \ patchview/README.patchview \ scripts/move-to-front \ m4/gnulib-cache.m4 \ + Makefile.fuzz \ + fuzz/README.md fuzz/generate_corpus.sh fuzz/run_fuzz.sh fuzz/analyze_crashes.sh \ + fuzz/patch.dict \ bash-completion-patchutils tag: diff --git a/Makefile.fuzz b/Makefile.fuzz new file mode 100644 index 00000000..2ca0add5 --- /dev/null +++ b/Makefile.fuzz @@ -0,0 +1,197 @@ +# Makefile for patchutils fuzzing infrastructure + +FUZZ_DIR = fuzz +CORPUS_DIR = $(FUZZ_DIR)/corpus +RESULTS_DIR = $(FUZZ_DIR)/results +TOOLS = filterdiff interdiff rediff grepdiff lsdiff + +.PHONY: fuzz-help fuzz-corpus fuzz-clean fuzz-test fuzz-all fuzz-analyze +.PHONY: $(addprefix fuzz-, $(TOOLS)) + +# Default target - show help +fuzz-help: + @echo "Patchutils Fuzzing Targets:" + @echo "" + @echo " fuzz-corpus - Generate/update fuzzing corpus" + @echo " fuzz-test - Quick 60-second fuzz test of filterdiff" + @echo " fuzz-all - Run extended fuzzing on all tools (background)" + @echo " fuzz-analyze - Analyze fuzzing results and crashes" + @echo " fuzz-clean - Clean fuzzing results (keeps corpus)" + @echo "" + @echo "Individual tool fuzzing:" + @echo " fuzz-filterdiff - Fuzz filterdiff" + @echo " fuzz-interdiff - Fuzz interdiff" + @echo " fuzz-rediff - Fuzz rediff" + @echo " fuzz-grepdiff - Fuzz grepdiff" + @echo " fuzz-lsdiff - Fuzz lsdiff" + @echo "" + @echo "Prerequisites:" + @echo " - AFL++ installed (american-fuzzy-lop package)" + @echo " - Tools built (run 'make' first)" + @echo "" + @echo "Quick start: make fuzz-corpus && make fuzz-test" + +# Generate or update the fuzzing corpus +fuzz-corpus: $(FUZZ_DIR)/generate_corpus.sh + @echo "Generating/updating fuzzing corpus..." + @if [ ! -d "$(CORPUS_DIR)" ] || [ -z "$$(ls -A $(CORPUS_DIR) 2>/dev/null)" ]; then \ + echo "Creating new corpus..."; \ + $(FUZZ_DIR)/generate_corpus.sh; \ + else \ + echo "Corpus exists with $$(ls -1 $(CORPUS_DIR) | wc -l) files"; \ + echo "Updating with new git diffs..."; \ + $(FUZZ_DIR)/generate_corpus.sh; \ + fi + @echo "Corpus ready: $$(ls -1 $(CORPUS_DIR) | wc -l) files" + +# Quick fuzzing test (60 seconds) +fuzz-test: fuzz-corpus src/filterdiff + @echo "Running quick fuzz test (60 seconds)..." + @echo "This will test basic fuzzing functionality" + timeout 60s $(FUZZ_DIR)/run_fuzz.sh filterdiff || true + @echo "" + @echo "Quick test completed. Check fuzz/results/filterdiff/ for results" + @if [ -d "$(RESULTS_DIR)/filterdiff/crashes" ] && [ -n "$$(ls -A $(RESULTS_DIR)/filterdiff/crashes 2>/dev/null)" ]; then \ + echo "⚠️ Crashes found! Run 'make fuzz-analyze' to investigate"; \ + else \ + echo "✅ No crashes in quick test"; \ + fi + +# Extended fuzzing on all tools (runs in background) +fuzz-all: fuzz-corpus $(addprefix src/, $(TOOLS)) + @echo "Starting extended fuzzing on all tools..." + @echo "This will run fuzzing sessions in the background" + @echo "Monitor with: ps aux | grep afl-fuzz" + @echo "Stop with: pkill afl-fuzz" + @echo "" + @for tool in $(TOOLS); do \ + if [ -f "src/$$tool" ]; then \ + echo "Starting $$tool fuzzing in background..."; \ + nohup $(FUZZ_DIR)/run_fuzz.sh $$tool > $(RESULTS_DIR)/$$tool.log 2>&1 & \ + sleep 2; \ + fi; \ + done + @echo "" + @echo "All fuzzing sessions started. Logs in $(RESULTS_DIR)/*.log" + @echo "Run 'make fuzz-analyze' periodically to check for crashes" + +# Individual tool fuzzing targets +fuzz-filterdiff: fuzz-corpus src/filterdiff + @echo "Starting filterdiff fuzzing (interactive)..." + $(FUZZ_DIR)/run_fuzz.sh filterdiff + +fuzz-interdiff: fuzz-corpus src/interdiff + @echo "Starting interdiff fuzzing (interactive)..." + $(FUZZ_DIR)/run_fuzz.sh interdiff + +fuzz-rediff: fuzz-corpus src/rediff + @echo "Starting rediff fuzzing (interactive)..." + $(FUZZ_DIR)/run_fuzz.sh rediff + +fuzz-grepdiff: fuzz-corpus src/grepdiff + @echo "Starting grepdiff fuzzing (interactive)..." + $(FUZZ_DIR)/run_fuzz.sh grepdiff + +fuzz-lsdiff: fuzz-corpus src/lsdiff + @echo "Starting lsdiff fuzzing (interactive)..." + $(FUZZ_DIR)/run_fuzz.sh lsdiff + +# Analyze fuzzing results +fuzz-analyze: $(FUZZ_DIR)/analyze_crashes.sh + @echo "Analyzing fuzzing results..." + @$(FUZZ_DIR)/analyze_crashes.sh + @echo "" + @echo "Summary of results:" + @for tool in $(TOOLS); do \ + if [ -d "$(RESULTS_DIR)/$$tool" ]; then \ + crashes=$$(find $(RESULTS_DIR)/$$tool/crashes -name 'id:*' 2>/dev/null | wc -l); \ + hangs=$$(find $(RESULTS_DIR)/$$tool/hangs -name 'id:*' 2>/dev/null | wc -l); \ + echo " $$tool: $$crashes crashes, $$hangs hangs"; \ + fi; \ + done + +# Clean fuzzing results but preserve corpus +fuzz-clean: + @echo "Cleaning fuzzing results (preserving corpus)..." + @if [ -d "$(RESULTS_DIR)" ]; then \ + rm -rf $(RESULTS_DIR)/*; \ + echo "Results cleaned"; \ + else \ + echo "No results to clean"; \ + fi + +# Clean everything including corpus (nuclear option) +fuzz-clean-all: + @echo "WARNING: This will delete ALL fuzzing data including corpus!" + @read -p "Are you sure? [y/N] " -n 1 -r; \ + echo; \ + if [[ $$REPLY =~ ^[Yy]$$ ]]; then \ + rm -rf $(FUZZ_DIR)/corpus $(FUZZ_DIR)/results; \ + echo "All fuzzing data deleted"; \ + else \ + echo "Cancelled"; \ + fi + +# Check fuzzing prerequisites +fuzz-check: + @echo "Checking fuzzing prerequisites..." + @echo -n "AFL++ installed: " + @if command -v afl-fuzz >/dev/null 2>&1; then \ + echo "✅ Yes ($$(afl-fuzz --help 2>&1 | head -1))"; \ + else \ + echo "❌ No - install american-fuzzy-lop package"; \ + fi + @echo -n "Tools built: " + @missing=""; \ + for tool in $(TOOLS); do \ + if [ ! -f "src/$$tool" ]; then \ + missing="$$missing $$tool"; \ + fi; \ + done; \ + if [ -z "$$missing" ]; then \ + echo "✅ All tools present"; \ + else \ + echo "❌ Missing:$$missing - run 'make' first"; \ + fi + @echo -n "Corpus ready: " + @if [ -d "$(CORPUS_DIR)" ] && [ -n "$$(ls -A $(CORPUS_DIR) 2>/dev/null)" ]; then \ + echo "✅ Yes ($$(ls -1 $(CORPUS_DIR) | wc -l) files)"; \ + else \ + echo "❌ No - run 'make fuzz-corpus' first"; \ + fi + +# Continuous integration target - quick validation +fuzz-ci: fuzz-corpus + @echo "Running CI fuzzing validation..." + @echo "Quick 30-second test to catch obvious issues" + @for tool in filterdiff rediff; do \ + echo "Testing $$tool..."; \ + timeout 30s $(FUZZ_DIR)/run_fuzz.sh $$tool >/dev/null 2>&1 || true; \ + if [ -d "$(RESULTS_DIR)/$$tool/crashes" ] && [ -n "$$(ls -A $(RESULTS_DIR)/$$tool/crashes 2>/dev/null)" ]; then \ + echo "❌ $$tool: Crashes found in CI test!"; \ + exit 1; \ + else \ + echo "✅ $$tool: No crashes in CI test"; \ + fi; \ + done + @echo "CI fuzzing validation passed" + +# Show fuzzing statistics +fuzz-stats: + @echo "Fuzzing Statistics:" + @echo "===================" + @if [ -d "$(CORPUS_DIR)" ]; then \ + echo "Corpus: $$(ls -1 $(CORPUS_DIR) | wc -l) files ($$(du -sh $(CORPUS_DIR) | cut -f1))"; \ + fi + @if [ -d "$(RESULTS_DIR)" ]; then \ + echo "Results: $$(du -sh $(RESULTS_DIR) | cut -f1)"; \ + echo ""; \ + for tool in $(TOOLS); do \ + if [ -f "$(RESULTS_DIR)/$$tool/fuzzer_stats" ]; then \ + echo "$$tool:"; \ + echo " Execs: $$(grep execs_done $(RESULTS_DIR)/$$tool/fuzzer_stats | cut -d: -f2 | tr -d ' ')"; \ + echo " Crashes: $$(grep unique_crashes $(RESULTS_DIR)/$$tool/fuzzer_stats | cut -d: -f2 | tr -d ' ')"; \ + echo " Coverage: $$(grep bitmap_cvg $(RESULTS_DIR)/$$tool/fuzzer_stats | cut -d: -f2 | tr -d ' ')%"; \ + fi; \ + done; \ + fi diff --git a/fuzz/README.md b/fuzz/README.md new file mode 100644 index 00000000..58ee4303 --- /dev/null +++ b/fuzz/README.md @@ -0,0 +1,97 @@ +# Patchutils Fuzzing Infrastructure + +This directory contains fuzz testing infrastructure for patchutils. + +## Overview + +The fuzz testing setup targets the main patch processing tools: +- `filterdiff` - patch filtering and manipulation +- `interdiff` - incremental patch generation +- `rediff` - patch correction +- `grepdiff` - regex matching in patches +- `lsdiff` - patch file listing + +## Fuzzing Approaches + +### 1. Input-Based Fuzzing +- **Target**: Command-line tools with file inputs +- **Method**: Generate malformed patch files and feed them to tools +- **Coverage**: Tests argument parsing, file I/O, and patch parsing + +### 2. Library Function Fuzzing +- **Target**: Core parsing functions in `diff.c` and `util.c` +- **Method**: Direct function calls with generated inputs +- **Coverage**: Deep testing of parsing logic without CLI overhead + +### 3. Property-Based Testing +- **Target**: Invariant validation (building on existing test-invariants.sh) +- **Method**: Generate patches that should maintain specific properties +- **Coverage**: Semantic correctness and consistency + +## Files + +- `generate_corpus.sh` - Creates initial seed corpus from existing tests +- `run_fuzz.sh` - Main fuzzing script +- `analyze_crashes.sh` - Analyzes and categorizes crashes +- `patch.dict` - AFL++ dictionary for patch-specific mutations +- `corpus/` - Seed files for fuzzing +- `crashes/` - Discovered crash cases +- `hangs/` - Discovered hang cases + +## Usage + +### Via Makefile (Recommended) + +```bash +# Quick start - see all available targets +make fuzz-help + +# Generate/update corpus (includes latest git diffs) +make fuzz-corpus + +# Quick 60-second test +make fuzz-test + +# Fuzz specific tools +make fuzz-filterdiff # Most important +make fuzz-interdiff +make fuzz-rediff +make fuzz-grepdiff +make fuzz-lsdiff + +# Analyze results +make fuzz-analyze + +# Check prerequisites +make fuzz-check +``` + +### Direct Script Usage + +```bash +# Generate initial corpus +./generate_corpus.sh + +# Run fuzzing for filterdiff +./run_fuzz.sh filterdiff + +# Run fuzzing for interdiff +./run_fuzz.sh interdiff + +# Analyze crashes +./analyze_crashes.sh +``` + +### Continuous Integration + +```bash +# Quick validation for CI/CD +make fuzz-ci +``` + +## Integration + +The fuzzer integrates with the existing testing infrastructure: +- Uses existing test cases as seed corpus +- Validates discovered issues against invariant tests +- Generates regression tests for fixed bugs diff --git a/fuzz/analyze_crashes.sh b/fuzz/analyze_crashes.sh new file mode 100755 index 00000000..05d13883 --- /dev/null +++ b/fuzz/analyze_crashes.sh @@ -0,0 +1,121 @@ +#!/bin/bash +# Analyze crashes found by AFL++ fuzzing + +set -e + +SCRIPT_DIR="$(dirname "$0")" +RESULTS_DIR="$SCRIPT_DIR/results" +PROJECT_DIR="$(dirname "$SCRIPT_DIR")" + +echo "Analyzing AFL++ fuzzing results..." + +if [ ! -d "$RESULTS_DIR" ]; then + echo "No results directory found. Run fuzzing first with ./run_fuzz.sh" + exit 1 +fi + +# Function to analyze crashes for a specific tool +analyze_tool_crashes() { + local tool="$1" + local tool_results="$RESULTS_DIR/$tool" + + if [ ! -d "$tool_results" ]; then + echo "No results found for $tool" + return + fi + + echo "" + echo "=== Analysis for $tool ===" + + # Check for crashes + local crashes_dir="$tool_results/crashes" + if [ -d "$crashes_dir" ] && [ "$(ls -A "$crashes_dir" 2>/dev/null | wc -l)" -gt 0 ]; then + local crash_count=$(ls -1 "$crashes_dir" | grep -v README | wc -l) + echo "Found $crash_count crash(es):" + + for crash_file in "$crashes_dir"/*; do + if [ -f "$crash_file" ] && [ "$(basename "$crash_file")" != "README.txt" ]; then + echo " - $(basename "$crash_file")" + + # Try to reproduce the crash + echo " Reproducing crash..." + if timeout 10s "$PROJECT_DIR/src/$tool" < "$crash_file" >/dev/null 2>&1; then + echo " ⚠ Crash not reproduced (may be timing-dependent)" + else + local exit_code=$? + echo " ✓ Crash reproduced (exit code: $exit_code)" + + # Get more details with gdb if available + if command -v gdb >/dev/null 2>&1; then + echo " Getting stack trace..." + timeout 30s gdb -batch -ex run -ex bt -ex quit \ + --args "$PROJECT_DIR/src/$tool" < "$crash_file" 2>/dev/null | \ + grep -A 20 "Program received signal" | head -20 || true + fi + fi + echo "" + fi + done + else + echo "No crashes found" + fi + + # Check for hangs + local hangs_dir="$tool_results/hangs" + if [ -d "$hangs_dir" ] && [ "$(ls -A "$hangs_dir" 2>/dev/null | wc -l)" -gt 0 ]; then + local hang_count=$(ls -1 "$hangs_dir" | grep -v README | wc -l) + echo "Found $hang_count hang(s):" + + for hang_file in "$hangs_dir"/*; do + if [ -f "$hang_file" ] && [ "$(basename "$hang_file")" != "README.txt" ]; then + echo " - $(basename "$hang_file")" + + # Check file size to understand the hang + local size=$(stat -c%s "$hang_file") + echo " File size: $size bytes" + + if [ "$size" -gt 1000000 ]; then + echo " ⚠ Large input file - may cause memory exhaustion" + else + echo " Testing with timeout..." + if timeout 5s "$PROJECT_DIR/src/$tool" < "$hang_file" >/dev/null 2>&1; then + echo " ✓ Completed within timeout" + else + echo " ⚠ Still hangs or crashes" + fi + fi + echo "" + fi + done + else + echo "No hangs found" + fi + + # Show fuzzer stats if available + local stats_file="$tool_results/fuzzer_stats" + if [ -f "$stats_file" ]; then + echo "Fuzzer statistics:" + echo " Total execs: $(grep "execs_done" "$stats_file" | cut -d: -f2 | tr -d ' ')" + echo " Crashes: $(grep "unique_crashes" "$stats_file" | cut -d: -f2 | tr -d ' ')" + echo " Hangs: $(grep "unique_hangs" "$stats_file" | cut -d: -f2 | tr -d ' ')" + echo " Coverage: $(grep "bitmap_cvg" "$stats_file" | cut -d: -f2 | tr -d ' ')%" + fi +} + +# Analyze all tools that have results +for tool_dir in "$RESULTS_DIR"/*; do + if [ -d "$tool_dir" ]; then + tool_name=$(basename "$tool_dir") + analyze_tool_crashes "$tool_name" + fi +done + +echo "" +echo "=== Summary ===" +echo "To minimize a crash case:" +echo " afl-tmin -i path/to/crash -o minimized_crash -- /path/to/tool @@" +echo "" +echo "To get more detailed crash info:" +echo " gdb --args /path/to/tool" +echo " (gdb) run < path/to/crash" +echo " (gdb) bt" diff --git a/fuzz/generate_corpus.sh b/fuzz/generate_corpus.sh new file mode 100755 index 00000000..2aac880a --- /dev/null +++ b/fuzz/generate_corpus.sh @@ -0,0 +1,164 @@ +#!/bin/bash +# Generate seed corpus for fuzzing patchutils from existing test cases + +set -e + +CORPUS_DIR="$(dirname "$0")/corpus" +TESTS_DIR="$(dirname "$0")/../tests" +STRESS_RESULTS_DIR="$(dirname "$0")/../stress-test-results" + +echo "Generating seed corpus for patchutils fuzzing..." + +# Clean and create corpus directory +rm -rf "$CORPUS_DIR" +mkdir -p "$CORPUS_DIR" + +# Counter for naming files +counter=0 + +# Function to add a file to corpus with a unique name +add_to_corpus() { + local file="$1" + local name="$2" + if [ -f "$file" ] && [ -s "$file" ]; then + cp "$file" "$CORPUS_DIR/seed_$(printf "%04d" $counter)_${name}" + counter=$((counter + 1)) + echo "Added: $name" + fi +} + +echo "1. Collecting patch files from test cases..." + +# Collect .patch and .diff files from tests +find "$TESTS_DIR" -name "*.patch" -o -name "*.diff" | while read -r file; do + basename_file=$(basename "$file") + test_name=$(basename "$(dirname "$file")") + add_to_corpus "$file" "${test_name}_${basename_file}" +done + +echo "2. Collecting patches from stress test results..." + +# Collect diff files from stress testing if available +if [ -d "$STRESS_RESULTS_DIR" ]; then + find "$STRESS_RESULTS_DIR" -name "*.diff" | head -20 | while read -r file; do + basename_file=$(basename "$file") + add_to_corpus "$file" "stress_${basename_file}" + done +fi + +echo "3. Generating git diffs from repository history..." + +# Generate some git diffs from the repository itself +cd "$(dirname "$0")/.." +git log --oneline -50 | while read -r commit rest; do + if git show --format="" "$commit" > "/tmp/git_diff_$commit.patch" 2>/dev/null; then + if [ -s "/tmp/git_diff_$commit.patch" ]; then + add_to_corpus "/tmp/git_diff_$commit.patch" "git_${commit}.patch" + fi + rm -f "/tmp/git_diff_$commit.patch" + fi +done + +echo "4. Creating minimal test cases..." + +# Create some minimal valid patches +cat > "$CORPUS_DIR/seed_minimal_unified.patch" << 'EOF' +--- a/test.txt 2024-01-01 00:00:00.000000000 +0000 ++++ b/test.txt 2024-01-01 00:00:01.000000000 +0000 +@@ -1,3 +1,3 @@ + line1 +-line2 ++modified line2 + line3 +EOF + +cat > "$CORPUS_DIR/seed_minimal_context.patch" << 'EOF' +*** a/test.txt 2024-01-01 00:00:00.000000000 +0000 +--- b/test.txt 2024-01-01 00:00:01.000000000 +0000 +*************** +*** 1,3 **** + line1 +! line2 + line3 +--- 1,3 ---- + line1 +! modified line2 + line3 +EOF + +cat > "$CORPUS_DIR/seed_minimal_git.patch" << 'EOF' +diff --git a/test.txt b/test.txt +index 1234567..abcdefg 100644 +--- a/test.txt ++++ b/test.txt +@@ -1,3 +1,3 @@ + line1 +-line2 ++modified line2 + line3 +EOF + +# Create some edge cases +cat > "$CORPUS_DIR/seed_empty.patch" << 'EOF' +EOF + +cat > "$CORPUS_DIR/seed_binary.patch" << 'EOF' +diff --git a/binary.bin b/binary.bin +index 1234567..abcdefg 100644 +GIT binary patch +delta 10 +Rcmcc61;2c{000000000000 + +EOF + +cat > "$CORPUS_DIR/seed_new_file.patch" << 'EOF' +diff --git a/newfile.txt b/newfile.txt +new file mode 100644 +index 0000000..1234567 +--- /dev/null ++++ b/newfile.txt +@@ -0,0 +1,3 @@ ++new line 1 ++new line 2 ++new line 3 +EOF + +cat > "$CORPUS_DIR/seed_deleted_file.patch" << 'EOF' +diff --git a/oldfile.txt b/oldfile.txt +deleted file mode 100644 +index 1234567..0000000 +--- a/oldfile.txt ++++ /dev/null +@@ -1,3 +0,0 @@ +-old line 1 +-old line 2 +-old line 3 +EOF + +echo "5. Creating malformed/edge case patches for robustness testing..." + +# Some malformed patches to test error handling +cat > "$CORPUS_DIR/seed_malformed_header.patch" << 'EOF' +--- ++++ b/test.txt +@@ -1,3 +1,3 @@ + line1 +-line2 ++modified line2 + line3 +EOF + +cat > "$CORPUS_DIR/seed_malformed_hunk.patch" << 'EOF' +--- a/test.txt ++++ b/test.txt +@@ -1,3 +1,999 @@ + line1 +-line2 ++modified line2 + line3 +EOF + +# Count final corpus +corpus_count=$(ls -1 "$CORPUS_DIR" | wc -l) +echo "Generated $corpus_count seed files in $CORPUS_DIR" +echo "Corpus generation complete!" diff --git a/fuzz/patch.dict b/fuzz/patch.dict new file mode 100644 index 00000000..dced4484 --- /dev/null +++ b/fuzz/patch.dict @@ -0,0 +1,82 @@ +# AFL++ dictionary for patch file fuzzing +# Common patch format keywords and patterns + +# Unified diff headers +"--- " +"+++ " +"@@" +"@@ -" +"+++ " + +# Context diff headers +"*** " +"--- " +"***************" + +# Git diff headers +"diff --git" +"index " +"new file mode" +"deleted file mode" +"similarity index" +"rename from" +"rename to" +"copy from" +"copy to" +"GIT binary patch" +"delta " +"literal " + +# Common file paths and extensions +"/dev/null" +"a/" +"b/" +".c" +".h" +".txt" +".patch" +".diff" + +# Hunk markers +" " +"+" +"-" +"\\" +"\ No newline at end of file" + +# Timestamps +"2024-01-01" +"00:00:00" +"+0000" +"UTC" + +# Mode changes +"100644" +"100755" +"120000" +"040000" + +# Binary indicators +"Binary files" +"differ" + +# Git hash patterns (short examples) +"1234567" +"abcdef0" +"0000000" + +# Line numbers and counts +",0" +",1" +",3" +",10" +"+1" +"-1" +"+0" +"-0" + +# Special characters that might cause issues +"\t" +"\r" +"\n" +"\0" diff --git a/fuzz/run_fuzz.sh b/fuzz/run_fuzz.sh new file mode 100755 index 00000000..ed7d4671 --- /dev/null +++ b/fuzz/run_fuzz.sh @@ -0,0 +1,130 @@ +#!/bin/bash +# Run AFL++ fuzzing on patchutils tools + +set -e + +SCRIPT_DIR="$(dirname "$0")" +PROJECT_DIR="$(dirname "$SCRIPT_DIR")" +CORPUS_DIR="$SCRIPT_DIR/corpus" +RESULTS_DIR="$SCRIPT_DIR/results" +DICT_FILE="$SCRIPT_DIR/patch.dict" + +# Default tool to fuzz +TOOL="${1:-filterdiff}" + +echo "Starting AFL++ fuzzing for $TOOL..." + +# Ensure we have a corpus +if [ ! -d "$CORPUS_DIR" ] || [ -z "$(ls -A "$CORPUS_DIR")" ]; then + echo "Generating corpus..." + "$SCRIPT_DIR/generate_corpus.sh" +fi + +# Ensure results directory exists +mkdir -p "$RESULTS_DIR/$TOOL" + +# Check if we have the normal binaries built +if [ ! -f "$PROJECT_DIR/src/$TOOL" ]; then + echo "Building patchutils tools..." + cd "$PROJECT_DIR" + make clean >/dev/null 2>&1 || true + + # Try to configure if not already done + if [ ! -f Makefile ]; then + if [ ! -f configure ]; then + ./bootstrap + fi + ./configure + fi + + make -j$(nproc) + cd - >/dev/null +fi + +# AFL++ system configuration check +echo "Checking AFL++ system configuration..." +if [ -f /proc/sys/kernel/core_pattern ]; then + if grep -q "|" /proc/sys/kernel/core_pattern; then + echo "Warning: Core pattern contains pipe - this may interfere with fuzzing" + echo "Consider running: echo core | sudo tee /proc/sys/kernel/core_pattern" + fi +fi + +# Set AFL++ environment variables for better performance +export AFL_SKIP_CPUFREQ=1 +export AFL_I_DONT_CARE_ABOUT_MISSING_CRASHES=1 + +echo "Starting fuzzing campaign for $TOOL..." +echo "Corpus: $CORPUS_DIR ($(ls -1 "$CORPUS_DIR" | wc -l) files)" +echo "Results: $RESULTS_DIR/$TOOL" +echo "Dictionary: $DICT_FILE" +echo +echo "Press Ctrl+C to stop fuzzing" +echo + +# Create specific fuzzing commands for different tools +case "$TOOL" in + filterdiff) + # Test filterdiff with list mode (most common usage) + afl-fuzz \ + -i "$CORPUS_DIR" \ + -o "$RESULTS_DIR/$TOOL" \ + -x "$DICT_FILE" \ + -t 5000 \ + -m none \ + -n \ + -- "$PROJECT_DIR/src/filterdiff" --list + ;; + interdiff) + # For interdiff, we need two patch files - use a simple approach + echo "Note: interdiff fuzzing requires two patch files" + echo "Using first corpus file as patch1, fuzzing patch2" + PATCH1=$(ls "$CORPUS_DIR" | head -1) + afl-fuzz \ + -i "$CORPUS_DIR" \ + -o "$RESULTS_DIR/$TOOL" \ + -x "$DICT_FILE" \ + -t 5000 \ + -m none \ + -n \ + -- "$PROJECT_DIR/src/interdiff" "$CORPUS_DIR/$PATCH1" @@ + ;; + rediff) + # Test rediff (patch correction) + afl-fuzz \ + -i "$CORPUS_DIR" \ + -o "$RESULTS_DIR/$TOOL" \ + -x "$DICT_FILE" \ + -t 5000 \ + -m none \ + -n \ + -- "$PROJECT_DIR/src/rediff" + ;; + grepdiff) + # Test grepdiff with a simple pattern + afl-fuzz \ + -i "$CORPUS_DIR" \ + -o "$RESULTS_DIR/$TOOL" \ + -x "$DICT_FILE" \ + -t 5000 \ + -m none \ + -n \ + -- "$PROJECT_DIR/src/grepdiff" ".*" + ;; + lsdiff) + # Test lsdiff (list files in patch) + afl-fuzz \ + -i "$CORPUS_DIR" \ + -o "$RESULTS_DIR/$TOOL" \ + -x "$DICT_FILE" \ + -t 5000 \ + -m none \ + -n \ + -- "$PROJECT_DIR/src/lsdiff" + ;; + *) + echo "Unknown tool: $TOOL" + echo "Supported tools: filterdiff, interdiff, rediff, grepdiff, lsdiff" + exit 1 + ;; +esac