Skip to content
Merged
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 .clusterfuzzlite/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
FROM gcr.io/oss-fuzz-base/base-builder-rust

# Copy the project source code
COPY . $SRC/precis
WORKDIR $SRC/precis

# Build script will be executed by ClusterFuzzLite
COPY .clusterfuzzlite/build.sh $SRC/
39 changes: 39 additions & 0 deletions .clusterfuzzlite/build.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
#!/bin/bash -eu

cd $SRC/precis

# Build fuzzers for precis-core
if [ -d "precis-core/fuzz" ]; then
cd precis-core
cargo fuzz build --release

# Copy fuzzers to $OUT
for fuzzer in fuzz/target/x86_64-unknown-linux-gnu/release/*; do
if [ -f "$fuzzer" ] && [ -x "$fuzzer" ]; then
fuzzer_name=$(basename $fuzzer)
# Skip build artifacts that aren't actual fuzzers
if [[ ! "$fuzzer_name" =~ ^(build|deps|incremental|\.fingerprint)$ ]]; then
cp $fuzzer $OUT/precis_core_${fuzzer_name}
fi
fi
done

cd $SRC/precis
fi

# Build fuzzers for precis-profiles
if [ -d "precis-profiles/fuzz" ]; then
cd precis-profiles
cargo fuzz build --release

# Copy fuzzers to $OUT
for fuzzer in fuzz/target/x86_64-unknown-linux-gnu/release/*; do
if [ -f "$fuzzer" ] && [ -x "$fuzzer" ]; then
fuzzer_name=$(basename $fuzzer)
# Skip build artifacts that aren't actual fuzzers
if [[ ! "$fuzzer_name" =~ ^(build|deps|incremental|\.fingerprint)$ ]]; then
cp $fuzzer $OUT/precis_profiles_${fuzzer_name}
fi
fi
done
fi
57 changes: 57 additions & 0 deletions .github/workflows/clusterfuzzlite.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
name: ClusterFuzzLite PR fuzzing

on:
pull_request:
workflow_dispatch:

permissions: read-all

jobs:
PR:
runs-on: ubuntu-latest
concurrency:
group: ${{ github.workflow }}-${{ matrix.sanitizer }}-${{ github.ref }}
cancel-in-progress: true
strategy:
fail-fast: false
matrix:
sanitizer:
- address
# Uncomment to enable additional sanitizers:
# - undefined
# - memory
steps:
- name: Build Fuzzers (${{ matrix.sanitizer }})
id: build
uses: google/clusterfuzzlite/actions/build_fuzzers@v1
with:
language: rust
github-token: ${{ secrets.GITHUB_TOKEN }}
sanitizer: ${{ matrix.sanitizer }}
# Optional: Enable storage repo for corpus persistence
# storage-repo: https://${{ secrets.PERSONAL_ACCESS_TOKEN }}@github.com/OWNER/STORAGE-REPO-NAME.git
# storage-repo-branch: main
# storage-repo-branch-coverage: gh-pages

- name: Run Fuzzers (${{ matrix.sanitizer }})
id: run
uses: google/clusterfuzzlite/actions/run_fuzzers@v1
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
fuzz-seconds: 300
mode: 'code-change'
sanitizer: ${{ matrix.sanitizer }}
output-sarif: true
parallel-fuzzing: true
# Optional: Enable storage repo for corpus download
# storage-repo: https://${{ secrets.PERSONAL_ACCESS_TOKEN }}@github.com/OWNER/STORAGE-REPO-NAME.git
# storage-repo-branch: main
# storage-repo-branch-coverage: gh-pages

# Upload crashes as artifacts if fuzzing fails
- name: Upload Crashes
if: failure() && steps.run.outcome == 'failure'
uses: actions/upload-artifact@v4
with:
name: clusterfuzzlite-crashes-${{ matrix.sanitizer }}
path: build/out/artifacts
26 changes: 26 additions & 0 deletions .github/workflows/rust_checks.yml
Original file line number Diff line number Diff line change
Expand Up @@ -107,3 +107,29 @@ jobs:

- name: Run cargo-deny
run: cargo deny check

fuzz_checks:

runs-on: ubuntu-latest

steps:
- uses: actions/checkout@v4

- name: Install nightly toolchain
uses: dtolnay/rust-toolchain@nightly
with:
components: clippy

- uses: taiki-e/install-action@v2
with:
tool: cargo-fuzz

- name: Check precis-core fuzzers
working-directory: precis-core
run: |
cargo +nightly clippy --manifest-path fuzz/Cargo.toml --all-targets -- -D warnings

- name: Check precis-profiles fuzzers
working-directory: precis-profiles
run: |
cargo +nightly clippy --manifest-path fuzz/Cargo.toml --all-targets -- -D warnings
6 changes: 6 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,9 @@ Cargo.lock

# Property-based testing regression files
**/proptest-regressions/

# Fuzzing artifacts and corpus
**/fuzz/target/
**/fuzz/corpus/
**/fuzz/artifacts/
**/fuzz/coverage/
61 changes: 61 additions & 0 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -355,6 +355,60 @@ cargo test -- --nocapture
cargo tarpaulin --workspace --exclude precis-tools --timeout 120 --out Html
```

## Fuzzing

Fuzzing is an automated testing technique that generates random inputs to discover bugs, panics, and edge cases.

### When to Run Fuzzing

Run fuzzing when:
- Adding new profile implementations
- Modifying string processing logic
- Investigating potential edge cases
- Before major releases

### Quick Start

```bash
# Install cargo-fuzz
cargo install cargo-fuzz

# Run fuzzing for 60 seconds
cd precis-profiles
cargo +nightly fuzz run nickname_enforce -- -max_total_time=60

# List available targets
cargo +nightly fuzz list
```

### Available Fuzz Targets

The project has comprehensive fuzz targets covering:

**precis-core:**
- FreeformClass and IdentifierClass: `allows()`, `get_value_from_char()`, `get_value_from_codepoint()`

**precis-profiles:**
- **Nickname**: enforce, prepare, compare, arbitrary (invalid UTF-8)
- **OpaqueString**: enforce, prepare, compare
- **UsernameCaseMapped**: enforce, prepare, compare
- **UsernameCasePreserved**: enforce, prepare, compare

### If Fuzzing Finds a Bug

1. Crash artifacts are saved to `fuzz/artifacts/`
2. Create a minimal unit test reproducing the issue
3. Fix the bug
4. Verify the fix with the same input
5. Keep the corpus (it found a real bug!)

See [FUZZING.md](FUZZING.md) for complete fuzzing guide including:
- Detailed target descriptions
- Advanced fuzzing options
- Corpus management
- CI/CD integration
- Troubleshooting

## Benchmarking

### When to Add Benchmarks
Expand Down Expand Up @@ -504,9 +558,16 @@ Your PR will automatically run:
- **Coverage** (`coverage.yml`): Verifies test coverage doesn't decrease
- **Security Audit** (`security_audit.yml`): Checks for known vulnerabilities
- **Benchmarks** (`benchmarks.yml`): Tracks performance changes with CodSpeed
- **ClusterFuzzLite** (`clusterfuzzlite.yml`): Fuzzes changed code for 5 minutes per target

All checks must pass before merging.

**ClusterFuzzLite Note:**
- Runs automatically when code in `precis-core/src/` or `precis-profiles/src/` changes
- Fuzzes for 5 minutes per target to catch panics and bugs
- If crashes are found, artifacts are uploaded and PR is blocked
- Fix any crashes before merging

### Review Process

1. Maintainers will review your PR
Expand Down
1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
resolver = "2"

members = ["precis-core", "precis-profiles", "precis-tools"]
exclude = ["precis-core/fuzz", "precis-profiles/fuzz"]

[workspace.dependencies]
precis-core = { path = "precis-core" }
Expand Down
Loading