* Add doc on parsing logs and scripts to unpack dataset * Reorganize scripts * Reorg markdown files.
13 KiB
Writing Custom Challenges
Buttercup is an automated cyber reasoning system originally developed for DARPA's AI Cyber Challenge. While it was designed to work seamlessly with existing oss-fuzz projects, you can also configure it to analyze custom projects with some additional setup.
This guide walks you through creating custom challenges for Buttercup, covering everything from the basic input format to debugging common issues.
Prerequisites
To analyze a custom project, you'll need to provide:
- Fuzzing harnesses that serve as entry points for analysis
- Build configuration in a forked oss-fuzz repository
- Project metadata defining how to compile and run your fuzzers
Challenge Input Format
Buttercup accepts challenges through a JSON configuration that specifies both the target project and the fuzzing infrastructure. Here's a complete example:
{
"challenge_repo_url": "https://github.com/tob-challenges/integration-test.git",
"challenge_repo_head_ref": "challenges/integration-test-delta-01",
"fuzz_tooling_url": "https://github.com/tob-challenges/oss-fuzz-aixcc",
"fuzz_tooling_ref": "challenge-state/integration-test-delta-01",
"fuzz_tooling_project_name": "integration-test",
"duration": 1800,
}
Field Descriptions
Project Repository Fields:
challenge_repo_url: URL to the repository containing your source codechallenge_repo_head_ref: Git branch, tag, or commit to analyzechallenge_repo_base_ref: (Delta mode only) The "clean" commit before vulnerabilities were introduced
Fuzzing Infrastructure Fields:
fuzz_tooling_url: URL to your forked oss-fuzz repository with build instructionsfuzz_tooling_ref: Git reference in the oss-fuzz fork that can build your projectfuzz_tooling_project_name: Directory name underprojects/in your oss-fuzz fork
Analysis Configuration:
duration: Analysis time limit in seconds (typically 1800-86400)
For additional context, consult the official oss-fuzz documentation on "Setting Up a new project".
Writing Fuzzing Harnesses
Fuzzing harnesses are small programs that act as entry points for Buttercup's analysis. They define how to feed random input into your code to discover vulnerabilities.
Key Principles
- Coverage is critical: Buttercup can only find bugs in code paths reached by your harnesses
- Keep them simple: Complex harnesses can introduce their own bugs, which will confuse the analysis
- Optimize for speed: The faster your harness runs, the more test cases Buttercup can explore and the better it can find vulnerabilities
- Handle edge cases: Your harness should gracefully handle empty inputs, malformed data, etc.
C/C++ Projects (LibFuzzer)
For C/C++ projects, Buttercup looks for harnesses that implement the LLVMFuzzerTestOneInput function:
#include <stdint.h>
#include <stddef.h>
int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) {
// Your fuzzing logic here
// Call the functions you want to test with the provided data
return 0;
}
Java Projects (Jazzer)
For Java projects, Buttercup looks for harnesses that implement the fuzzerTestOneInput method:
import com.code_intelligence.jazzer.api.FuzzedDataProvider;
public class MyFuzzer {
public static void fuzzerTestOneInput(FuzzedDataProvider data) {
// Your fuzzing logic here
// Use data.consume*() methods to extract data for testing
}
}
Best Practices
- Keep harnesses simple and focused on one API or functionality
- Avoid complex setup/teardown that could introduce bugs
- Handle edge cases (empty input, null pointers, etc.) gracefully
- Use appropriate data extraction methods for structured input
- Ensure the harness can handle the full range of possible inputs
See the libFuzzer Tutorial and Jazzer Documentation for detailed guidance.
Setting Up Your OSS-Fuzz Fork
To integrate your project with Buttercup, you'll need to create a fork of the oss-fuzz repository and add build instructions for your specific project.
This setup tells Buttercup how to compile your code and harnesses in a consistent, isolated environment.
Directory Structure
In your oss-fuzz fork, create a new directory at projects/your-project-name/ containing these files:
| File | Purpose |
|---|---|
Dockerfile |
Defines the build environment and dependencies |
build.sh |
Compiles your project and creates fuzzer binaries |
project.yaml |
Metadata about your project (recommended) |
Creating the Dockerfile
The Dockerfile sets up your build environment. Start with the oss-fuzz base image and add your project's dependencies:
FROM gcr.io/oss-fuzz-base/base-builder
# Install build dependencies
RUN apt-get update && apt-get install -y \
make autoconf automake libtool pkg-config
# Clone your project
RUN git clone https://github.com/your-org/your-project.git
WORKDIR /src/your-project
# Copy the build script
COPY build.sh $SRC/
Writing the Build Script
The build.sh script compiles your project and creates the fuzzer executables. OSS-Fuzz provides environment variables for consistent builds:
#!/bin/bash -eu
# Build your project's libraries
make clean
make CC="$CC" CXX="$CXX" CFLAGS="$CFLAGS" CXXFLAGS="$CXXFLAGS"
# Compile each fuzzing harness
# $CC, $CXX, $CFLAGS, $CXXFLAGS are provided by oss-fuzz
# $LIB_FUZZING_ENGINE links the fuzzing engine
# $OUT is where fuzzer binaries should be placed
$CC $CFLAGS -I. -c harness.c -o harness.o
$CXX $CXXFLAGS $LIB_FUZZING_ENGINE harness.o \
-L. -lyourlib -o $OUT/harness_fuzzer
# Include seed corpus and dictionaries if available
if [ -d "seeds" ]; then
cp seeds/* $OUT/ 2>/dev/null || true
fi
Project Metadata
The project.yaml file provides important metadata about your project:
homepage: "https://github.com/your-org/your-project"
language: c # or jvm
primary_contact: "maintainer@example.com"
# Fuzzing configuration
fuzzing_engines:
- libfuzzer
sanitizers:
- address # detects memory corruption
- undefined # detects undefined behavior
- memory # detects uninitialized reads
Submitting Your Challenge
With your project repository and oss-fuzz fork ready, you can now submit your challenge to Buttercup for analysis. There are three ways to do this:
Option 1: Using the Challenge Script
First, add your challenge configuration to the CHALLENGE_MAP in orchestrator/scripts/challenge.py:
"my_custom_challenge": {
"challenge_repo_url": "https://github.com/your-org/your-project.git",
"challenge_repo_head_ref": "main",
"fuzz_tooling_url": "https://github.com/your-org/oss-fuzz-fork.git",
"fuzz_tooling_ref": "main",
"fuzz_tooling_project_name": "your-project",
"duration": 3600,
},
Then run your challenge:
cd orchestrator/scripts
python challenge.py single my_custom_challenge 3600
Option 2: Direct API Call
For one-off submissions or when testing new configurations, use the HTTP API directly:
curl -X 'POST' 'http://localhost:31323/webhook/trigger_task' \
-H 'Content-Type: application/json' \
-d '{
"challenge_repo_url": "https://github.com/your-org/your-project.git",
"challenge_repo_head_ref": "main",
"fuzz_tooling_url": "https://github.com/your-org/oss-fuzz-fork.git",
"fuzz_tooling_ref": "main",
"fuzz_tooling_project_name": "your-project",
"duration": 3600
}'
Option 3: Web Interface
For a user-friendly approach, run make web-ui and open http://localhost:31323 in your browser. The web interface provides a form where you can enter all the challenge parameters and submit them with a single click.
Troubleshooting Your Challenge
When a challenge doesn't behave as expected, Buttercup provides several tools to help you diagnose and fix issues. Here's a systematic approach to debugging:
Step 1: Check System Status
Start by verifying that all Buttercup components are running properly:
make status
Step 2: Examine Logs
Logs are your primary source of diagnostic information:
# View recent logs for a specific component
kubectl logs -n crs -l app=<service-name> --tail=100
# Watch logs in real-time to see what's happening
kubectl logs -n crs -l app=scheduler --tail=-1 -f
# Collect comprehensive logs from all components
./deployment/collect-logs.sh
Step 3: Interactive Debugging
When you need to examine the environment directly:
# Access a running container's shell
kubectl exec -it -n crs <pod-name> -- /bin/bash
# Examine files, run commands, check environment variables
Step 4: Track Workflow Progress
Monitor how your challenge moves through Buttercup's processing pipeline:
# Watch the scheduler's state transitions
kubectl logs -n crs -l app=scheduler --tail=-1 --prefix | \
grep "WAIT_PATCH_PASS -> SUBMIT_BUNDLE"
# This helps identify where processing gets stuck
Common Issues and Solutions
| Problem | Symptoms | Solution |
|---|---|---|
| Build failures | Fuzzer compilation errors | Verify your build.sh works with oss-fuzz base images. Test locally with docker run gcr.io/oss-fuzz-base/base-builder |
| Missing harnesses | "No harnesses found" errors | Ensure harness files contain the expected function signatures |
| Timeouts | Analysis stops prematurely | Increase the duration parameter, especially for large codebases |
| Resource limits | Pods getting killed (OOMKilled) | Check pod memory/CPU limits and adjust Kubernetes resource constraints |
| Git access issues | Clone/fetch failures | Verify repository URLs are accessible and authentication is configured |
| Path problems | File not found errors | Ensure WORKDIR in Dockerfile matches your project structure |
Analysis Modes
Buttercup supports two analysis modes, each optimized for different use cases:
Full Mode Analysis
Full mode analyzes an entire project:
| Aspect | Details |
|---|---|
| Scope | Analyzes the complete codebase |
| Best for | Initial assessment |
| Configuration | Only requires challenge_repo_head_ref |
Example configuration:
{
"challenge_repo_url": "https://github.com/example/project.git",
"challenge_repo_head_ref": "v1.0.0",
"fuzz_tooling_url": "https://github.com/example/oss-fuzz-fork.git",
"fuzz_tooling_ref": "main",
"fuzz_tooling_project_name": "project",
"duration": 86400
}
Delta Mode Analysis
Delta mode focuses specifically on changes between two commits, making it ideal for targeted analysis:
| Aspect | Details |
|---|---|
| Scope | Analyzes only the differences between base and head commits |
| Best for | Analyzing specific changes |
| Configuration | Requires both challenge_repo_base_ref and challenge_repo_head_ref |
Example configuration:
{
"challenge_repo_url": "https://github.com/example/project.git",
"challenge_repo_base_ref": "abc123",
"challenge_repo_head_ref": "def456",
"fuzz_tooling_url": "https://github.com/example/oss-fuzz-fork.git",
"fuzz_tooling_ref": "main",
"fuzz_tooling_project_name": "project",
"duration": 28800
}
How Mode Detection Works
Buttercup determines which mode to use based on your challenge configuration:
- Delta mode is activated when you provide both
challenge_repo_base_refandchallenge_repo_head_ref - Internally, Buttercup creates diff files and the
is_delta_mode()method detects their presence - Before building, delta mode applies patches using
git applyto introduce the target changes - This targeted approach makes delta mode more efficient for analyzing specific code changes
Choosing the Right Mode
Choose Full Mode when you want to:
- Perform an initial assessment of a new project
- Search for vulnerabilities in all code reachable from harnesses
- Analyze a project without specific change targets
Choose Delta Mode when you want to:
- Test specific code changes
- Reproduce known vulnerabilities
Next Steps
Now that you understand how to create custom challenges, you can:
- Start simple: Create a basic challenge with one harness to test your setup
- Test locally: Verify your oss-fuzz configuration builds correctly before submitting
- Monitor progress: Use the debugging techniques to track your challenge's execution
- Iterate: Refine your harnesses and build scripts based on the results
For additional help, consult the main Buttercup documentation or check the troubleshooting guide.