Skip to content

External Tools Integration

Crucible's built-in triage handles the common case, but dedicated crash analysis tools provide additional capabilities: automated clustering, crash minimization, and root cause deduction. This guide covers integrating these tools into your workflow.

CASR — Crash Analysis and Severity Rating

CASR (v2.13.0+) provides automated crash clustering, deduplication, and severity estimation for sanitizer-detected crashes.

Installation

# Install from crates.io (requires Rust toolchain)
cargo install casr --locked

# Verify
casr-libfuzzer --version

Triaging libFuzzer Crashes

Use casr-libfuzzer to process a crash directory against a harness binary:

casr-libfuzzer \
  -i ./crashes/deep \
  -o ./casr-out/deep \
  -- ./harness/libfuzzer/crucible-libfuzzer-deep

This replays each crash file, captures the sanitizer output, and writes a .casrep JSON report per crash.

Clustering

Use casr-cluster to deduplicate and group crashes by stack similarity:

# Deduplicate
casr-cluster -d ./casr-out/deep

# Cluster into groups
casr-cluster -c ./casr-out/deep ./casr-clusters/deep

The cluster output directory contains one subdirectory per unique crash, with the representative .casrep and all duplicate crash files grouped together.

Interpreting Results

CASR reports include:

  • Crash severity — Exploitable, Probably Exploitable, Medium, Not Exploitable
  • Crash class — ASAN-specific crash type (e.g., heap-buffer-overflow)
  • Stack trace — Full sanitizer stack trace
  • Registers — CPU register state at crash point (when available)

CASR vs. Crucible Triage

CASR and Crucible triage are complementary. Crucible's crucible-triage focuses on CWE mapping, CVSS scoring, and SARIF export for CVE reporting. CASR excels at large-scale crash clustering when you have thousands of crash files to sort. Use CASR first to cluster, then feed unique representatives into crucible-triage for report generation.

Batch Processing Multiple Campaigns

# Process all campaign directories
for campaign in deep model rpc-commands rpc-race whisper-audio; do
  casr-libfuzzer \
    -i "./crashes/${campaign}" \
    -o "./casr-out/${campaign}" \
    -- "./harness/libfuzzer/crucible-libfuzzer-${campaign}"
  casr-cluster -c "./casr-out/${campaign}" "./casr-clusters/${campaign}"
done

# Summary of unique crashes per campaign
for d in ./casr-clusters/*/; do
  echo "$(basename "$d"): $(ls -d "$d"/*/ 2>/dev/null | wc -l) unique"
done

halfempty — Crash Minimization

halfempty uses bisection strategies to minimize crash reproducers. Smaller reproducers make root cause analysis easier and produce cleaner CVE reports.

Installation

# Build from source (requires glib2 development headers)
# Fedora/RHEL:
sudo dnf install glib2-devel

# Ubuntu/Debian:
sudo apt install libglib2.0-dev

git clone https://github.com/googleprojectzero/halfempty.git
cd halfempty
make
sudo cp halfempty /usr/local/bin/

Minimizing a Crash

halfempty requires a script that exits 0 for "interesting" (still crashes) and non-zero for "not interesting":

minimize.sh
#!/bin/bash
# Exit 0 if the input still triggers the crash
timeout 10 ./harness/libfuzzer/crucible-libfuzzer-deep "$1" 2>&1 | \
  grep -q "heap-buffer-overflow" && exit 0
exit 1
chmod +x minimize.sh
halfempty --strategy bisect ./minimize.sh crashes/deep/crash-abc123.gguf

halfempty will produce a minimized file that still triggers the same crash.

Strategies

Strategy Description Best For
bisect Binary search removal of byte ranges General purpose, fastest
zero Replace byte ranges with zeros Preserving file structure
delete Remove individual bytes Fine-grained minimization

Integration with Crucible Triage

Minimize crashes before triaging for cleaner reports:

Built-in minimization

crucible-triage --minimize natively recurses into crash subdirectories and preserves directory structure, reducing the need for external minimization tools in many workflows. Use halfempty when you need fine-grained control over the minimization strategy (bisect vs. zero vs. delete).

# Minimize all unique crashes in a cluster
for crash in ./casr-clusters/deep/*/crash-*; do
  halfempty --strategy bisect ./minimize.sh "$crash"
done

# Then triage the minimized set
crucible-triage --crashes ./minimized --output ./reports

Workflow: Full Pipeline

A complete triage pipeline combining all tools:

flowchart TD
    A[Fuzzing Campaign] --> B[Raw Crashes]
    B --> C[CASR: Cluster & Dedup]
    C --> D[Unique Representatives]
    D --> E[halfempty: Minimize]
    E --> F[Minimized Reproducers]
    F --> G[crucible-triage: Reports + SARIF]
    G --> H[Markdown Reports]
    G --> I[SARIF for CI]
# 1. Cluster with CASR
casr-libfuzzer -i ./crashes/deep -o ./casr-out -- ./harness/libfuzzer/crucible-libfuzzer-deep
casr-cluster -c ./casr-out ./casr-clusters

# 2. Minimize each unique crash
for cluster in ./casr-clusters/*/; do
  crash=$(ls "$cluster"/crash-* 2>/dev/null | head -1)
  [ -n "$crash" ] && halfempty --strategy bisect ./minimize.sh "$crash"
done

# 3. Generate reports and SARIF
crucible-triage \
  --crashes ./minimized \
  --output ./reports \
  --sarif ./results.sarif