Skip to content

Fuzz

Fuzz #1279

Workflow file for this run

name: Fuzz
on:
schedule:
- cron: "0 */4 * * *" # every 4 hours
workflow_dispatch:
jobs:
io_fuzz:
name: "IO Fuzz"
timeout-minutes: 230 # almost 4 hours
runs-on:
- runs-on=${{ github.run_id }}
- family=m8g.large
- image=ubuntu24-full-arm64
- disk=large
- extras=s3-cache
- tag=io-fuzz
outputs:
crashes_found: ${{ steps.check.outputs.crashes_found }}
crash_count: ${{ steps.check.outputs.crash_count }}
first_crash_name: ${{ steps.check.outputs.first_crash_name }}
steps:
- uses: runs-on/action@v2
with:
sccache: s3
- uses: actions/checkout@v5
- uses: ./.github/actions/setup-rust
with:
repo-token: ${{ secrets.GITHUB_TOKEN }}
toolchain: nightly
- name: Install llvm
uses: aminya/setup-cpp@v1
with:
compiler: llvm
- name: Install cargo fuzz
run: cargo install --locked cargo-fuzz
- name: Restore corpus
shell: bash
run: |
aws s3api head-object --bucket vortex-fuzz-corpus --key "io_corpus.tar.zst" --query ETag --output text > current_etag
aws s3 cp s3://vortex-fuzz-corpus/io_corpus.tar.zst .
tar -xf io_corpus.tar.zst
env:
AWS_ACCESS_KEY_ID: ${{ secrets.R2_FUZZ_ACCESS_KEY_ID }}
AWS_SECRET_ACCESS_KEY: ${{ secrets.R2_FUZZ_SECRET_ACCESS_KEY }}
AWS_REGION: "us-east-1"
AWS_ENDPOINT_URL: "https://01e9655179bbec953276890b183039bc.r2.cloudflarestorage.com"
- name: Run fuzzing target
id: fuzz
run: |
RUST_BACKTRACE=1 cargo +nightly fuzz run --release --debug-assertions file_io -- -max_total_time=7200 2>&1 | tee fuzz_output.log
continue-on-error: true
- name: Check for crashes
id: check
run: |
if [ -d "fuzz/artifacts" ] && [ "$(ls -A fuzz/artifacts 2>/dev/null)" ]; then
echo "crashes_found=true" >> $GITHUB_OUTPUT
# Get the first crash file only
FIRST_CRASH=$(find fuzz/artifacts -type f \( -name "crash-*" -o -name "leak-*" -o -name "timeout-*" -o -name "oom-*" \) | head -1)
if [ -n "$FIRST_CRASH" ]; then
echo "first_crash=$FIRST_CRASH" >> $GITHUB_OUTPUT
echo "first_crash_name=$(basename $FIRST_CRASH)" >> $GITHUB_OUTPUT
# Count all crashes for reporting
CRASH_COUNT=$(find fuzz/artifacts -type f \( -name "crash-*" -o -name "leak-*" -o -name "timeout-*" -o -name "oom-*" \) | wc -l)
echo "crash_count=$CRASH_COUNT" >> $GITHUB_OUTPUT
echo "Found $CRASH_COUNT crash(es), will process first: $(basename $FIRST_CRASH)"
fi
else
echo "crashes_found=false" >> $GITHUB_OUTPUT
echo "crash_count=0" >> $GITHUB_OUTPUT
echo "No crashes found"
fi
- name: Archive crash artifacts
if: steps.check.outputs.crashes_found == 'true'
uses: actions/upload-artifact@v4
with:
name: io-fuzzing-crash-artifacts
path: fuzz/artifacts
retention-days: 30
- name: Archive fuzzer output log
if: steps.check.outputs.crashes_found == 'true'
uses: actions/upload-artifact@v4
with:
name: io-fuzzing-logs
path: fuzz_output.log
retention-days: 30
- name: Persist corpus
shell: bash
run: |
tar -acf io_corpus.tar.zst fuzz/corpus/file_io
aws s3api put-object --bucket vortex-fuzz-corpus --key "io_corpus.tar.zst" --body io_corpus.tar.zst --checksum-algorithm CRC32 --if-match "$(cat current_etag)"
env:
AWS_ACCESS_KEY_ID: ${{ secrets.R2_FUZZ_ACCESS_KEY_ID }}
AWS_SECRET_ACCESS_KEY: ${{ secrets.R2_FUZZ_SECRET_ACCESS_KEY }}
AWS_REGION: "us-east-1"
AWS_ENDPOINT_URL: "https://01e9655179bbec953276890b183039bc.r2.cloudflarestorage.com"
- name: Fail job if fuzz run found a bug
if: steps.check.outputs.crashes_found == 'true'
run: exit 1
report-io-fuzz-failures:
name: "Report IO Fuzz Failures"
needs: io_fuzz
if: always() && needs.io_fuzz.outputs.crashes_found == 'true'
runs-on: ubuntu-latest
permissions:
issues: write
contents: read
id-token: write # Required for Claude Code Action OIDC authentication
pull-requests: read # For gh api commands
steps:
- name: Checkout repository
uses: actions/checkout@v5
- name: Download fuzzer logs
uses: actions/download-artifact@v4
with:
name: io-fuzzing-logs
path: ./logs
# Single Claude Code Action that does everything:
# - Analyzes crash from logs
# - Checks for duplicate issues
# - Creates new issue OR comments on existing issue
- name: Analyze and report crash with Claude
env:
CRASH_FILE: ${{ needs.io_fuzz.outputs.first_crash_name }}
CRASH_COUNT: ${{ needs.io_fuzz.outputs.crash_count }}
WORKFLOW_RUN: https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}
BRANCH: ${{ github.ref_name }}
COMMIT: ${{ github.sha }}
uses: anthropics/claude-code-action@v1
with:
claude_code_oauth_token: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }}
github_token: ${{ secrets.GITHUB_TOKEN }}
show_full_output: true
prompt: |
# Fuzzer Crash Analysis and Reporting
A fuzzing run for the `file_io` target has detected a crash. Analyze it and report by creating or updating a GitHub issue.
## Step 1: Analyze the Crash
1. Read the fuzzer log: `logs/fuzz_output.log`
2. Extract:
- Stack trace (lines with `#0`, `#1`, etc.)
- Error message ("panicked at" or "ERROR:")
- Crash location (top user code frame, not std/core/libfuzzer)
3. Read the source code at the crash location to understand root cause
## Step 2: Check for Duplicates
1. List existing fuzzer issues:
```bash
gh issue list --repo ${{ github.repository }} --label fuzzer --state open --json number,title,body --limit 50
```
2. For each existing issue, compare:
- **Crash location**: Same file + function? (line numbers can differ)
- **Error pattern**: Same error after normalizing values?
- "index 5 out of bounds" = "index 12 out of bounds" (SAME)
- "len is 100" = "len is 5" (SAME)
- Read source code if needed to verify same root cause
3. Determine duplication level:
- **EXACT DUPLICATE**: Same crash location + same error pattern → Add reaction
- **SIMILAR**: Same general area but unclear → Add comment
- **NEW BUG**: Different location or different error type → Create new issue
## Step 3: Take Action
### If EXACT DUPLICATE (high confidence):
Just add a 👍 reaction to the existing issue:
```bash
gh issue view ISSUE_NUM --repo ${{ github.repository }} --json reactions
gh api repos/${{ github.repository }}/issues/ISSUE_NUM/reactions -f content='+1'
```
### If SIMILAR (medium confidence):
Comment on the existing issue with analysis:
```bash
gh issue comment ISSUE_NUM --repo ${{ github.repository }} --body "..."
```
Include in comment:
- Note that another crash was detected
- Your confidence level and reasoning
- Key differences if any
- Crash file: $CRASH_FILE
- Workflow: $WORKFLOW_RUN
### If NEW BUG (not a duplicate):
Create a new issue with `gh issue create`:
```bash
gh issue create --repo ${{ github.repository }} \
--title "Fuzzing Crash: [brief description]" \
--label "bug,fuzzer" \
--body "..."
```
Issue body must include:
```markdown
## Fuzzing Crash Report
### Analysis
**Crash Location**: `file.rs:function_name`
**Error Message**:
```
[error message]
```
**Stack Trace**:
```
[top 5-7 frames - keep in code block to prevent markdown rendering issues]
```
Note: Keep stack traces in code blocks to prevent `#0`, `#1` from being interpreted as markdown headers.
**Root Cause**: [Your analysis]
### Summary
- **Target**: `file_io`
- **Crash File**: `$CRASH_FILE`
- **Total Crashes**: $CRASH_COUNT
- **Branch**: $BRANCH
- **Commit**: $COMMIT
- **Timestamp**: [current UTC time]
- **Workflow**: $WORKFLOW_RUN
### Reproduction
1. Download the crash artifact from the workflow run:
- **Workflow run**: $WORKFLOW_RUN
- Look for the artifact named `io-fuzzing-crash-artifacts` at the bottom of the workflow run page
- Download and extract the zip file
2. Reproduce locally:
```bash
# The artifact contains file_io/$CRASH_FILE
cargo +nightly fuzz run file_io file_io/$CRASH_FILE
```
3. Get full backtrace:
```bash
RUST_BACKTRACE=full cargo +nightly fuzz run file_io file_io/$CRASH_FILE
```
---
*Auto-created by fuzzing workflow with Claude analysis*
```
## Important Guidelines
- Be conservative: when unsure, prefer creating a new issue over marking as duplicate
- Focus on ROOT CAUSE, not specific values in error messages
- You have full repo access - read source code to understand crashes
- Only use +1 reaction for truly identical crashes
## Environment Variables
- CRASH_FILE: $CRASH_FILE
- CRASH_COUNT: $CRASH_COUNT
- WORKFLOW_RUN: $WORKFLOW_RUN
- BRANCH: $BRANCH
- COMMIT: $COMMIT
Start by reading `logs/fuzz_output.log`.
claude_args: |
--model claude-sonnet-4-5-20250929
--max-turns 25
--allowedTools "Read,Write,Bash(gh issue list:*),Bash(gh issue view:*),Bash(gh issue create:*),Bash(gh issue comment:*),Bash(gh api:*),Bash(echo:*),Bash(cat:*),Bash(cargo +nightly fuzz run:*),Bash(RUST_BACKTRACE=* cargo +nightly fuzz run:*)"
ops_fuzz:
name: "Array Operations Fuzz"
timeout-minutes: 230 # almost 4 hours
runs-on:
- runs-on=${{ github.run_id }}
- family=m8g.large
- image=ubuntu24-full-arm64
- disk=large
- extras=s3-cache
- tag=ops-fuzz
steps:
- uses: runs-on/action@v2
with:
sccache: s3
- uses: actions/checkout@v5
- uses: ./.github/actions/setup-rust
with:
repo-token: ${{ secrets.GITHUB_TOKEN }}
toolchain: nightly
- name: Install llvm
uses: aminya/setup-cpp@v1
with:
compiler: llvm
- name: Install cargo fuzz
run: cargo install --locked cargo-fuzz
- name: Restore corpus
shell: bash
run: |
aws s3api head-object --bucket vortex-fuzz-corpus --key "array_ops_corpus.tar.zst" --query ETag --output text > current_etag
aws s3 cp s3://vortex-fuzz-corpus/array_ops_corpus.tar.zst .
tar -xf array_ops_corpus.tar.zst
env:
AWS_ACCESS_KEY_ID: ${{ secrets.R2_FUZZ_ACCESS_KEY_ID }}
AWS_SECRET_ACCESS_KEY: ${{ secrets.R2_FUZZ_SECRET_ACCESS_KEY }}
AWS_REGION: "us-east-1"
AWS_ENDPOINT_URL: "https://01e9655179bbec953276890b183039bc.r2.cloudflarestorage.com"
- name: Run fuzzing target
id: fuzz
run: RUST_BACKTRACE=1 cargo +nightly fuzz run --release --debug-assertions array_ops -- -max_total_time=7200
continue-on-error: true
- name: Archive crash artifacts
uses: actions/upload-artifact@v4
with:
name: operations-fuzzing-crash-artifacts
path: fuzz/artifacts
- name: Persist corpus
shell: bash
run: |
tar -acf array_ops_corpus.tar.zst fuzz/corpus/array_ops
aws s3api put-object --bucket vortex-fuzz-corpus --key "array_ops_corpus.tar.zst" --body array_ops_corpus.tar.zst --checksum-algorithm CRC32 --if-match "$(cat current_etag)"
env:
AWS_ACCESS_KEY_ID: ${{ secrets.R2_FUZZ_ACCESS_KEY_ID }}
AWS_SECRET_ACCESS_KEY: ${{ secrets.R2_FUZZ_SECRET_ACCESS_KEY }}
AWS_REGION: "us-east-1"
AWS_ENDPOINT_URL: "https://01e9655179bbec953276890b183039bc.r2.cloudflarestorage.com"
- name: Fail job if fuzz run found a bug
if: steps.fuzz.outcome == 'failure'
run: exit 1