subagents/ trailofbits/skills

3b-rust-compiler-analyzer

Performs crate-level MIR and LLVM IR analysis for Rust in zeroize-audit. A single instance runs per crate (unlike 3-tu-compiler-analyzer which runs one per C/C++ TU). Detects dead-store elimination of wipes, stack retention, and other compiler-level zeroization failures.

0
Installs
—
Rating
—
Success rate
1
Files scanned
Scan passedknowledge
Source on GitHub

Security scan

Scan passed

No risky patterns were found in the scanned files.

1 files scannedscanner v1.2.0Oct 11, 2026

Content sha256 74700b901396deb9… — run codexguild_scan_skills after installing to verify your local copy.

Static analysis is a first line of defense, not a guarantee. Read the source

3b-rust-compiler-analyzer.md

exact scanned copy

3b-rust-compiler-analyzer

Perform crate-level compiler analysis for a Rust crate: MIR pattern detection and LLVM IR comparison across optimization levels. A single instance of this agent handles the entire crate (Rust compilation is crate-granular, not per-source-file like C/C++).

Input

You receive these values from the orchestrator:

ParameterDescription
workdirRun working directory (e.g. /tmp/zeroize-audit-{run_id}/)
cargo_manifestAbsolute path to Cargo.toml
rust_crate_rootDirectory containing Cargo.toml
rust_tu_hashHash identifier for this crate (e.g. a1b2c3d4)
configMerged config object
opt_levelsOptimization levels to analyze (e.g. ["O0", "O1", "O2"])
sensitive_objectsJSON array — Rust SO-5000+ objects from sensitive-objects.json
source_findingsJSON array — Rust F-RUST-SRC-NNNN findings from source-findings.json
baseDirPlugin base directory (for tool paths)

Process

Output directory: {workdir}/rust-compiler-analysis/

Section C of {baseDir}/references/rust-zeroization-patterns.md documents 12 of the patterns the three scripts below match — C-MIR1–C-MIR3 for Step 2, C-IR1–C-IR5 for Step 4, C-ASM1–C-ASM4 for Step 4b. Every entry explains why source-level analysis is blind to the flaw and carries a Detection line naming the compiler artifact that proves it; most also give a reproducing snippet. When a script reports a pattern that has an entry, that Detection line is the evidence the finding must carry, and checking the artifact against it separates a genuine hit from a match on a coincidental symbol name.

Section C is a subset, not an index. check_mir_patterns.py and check_llvm_patterns.py each emit classes with no entry — secrets passed to FFI calls, secrets live on Err paths, secret return values, and by-value aggregate arguments among them. A pattern absent from Section C is still a valid finding: report it with the evidence the script itself produced. Never drop or downgrade a finding because the reference does not describe it.

Section D lists patterns no current script detects — Arc/Rc deferred drop, repr(C) padding bytes, static/LazyLock secrets, async cancellation, Cow clones, and mem::swap. A clean run does not rule these out, so Step 7 surveys the crate for them and writes coverage-gaps.json. The report assembler reads that file and surfaces it under Analysis Coverage; notes.md is not read by any downstream agent, so a gap recorded only there never reaches the reader.

Step 1 — MIR Emission

Emit MIR (Mid-level Intermediate Representation) for the crate. MIR is lower-level than Rust source but higher-level than LLVM IR, and preserves drop semantics and borrow information.

{baseDir}/tools/emit_rust_mir.sh \
  --manifest <cargo_manifest> \
  --out {workdir}/rust-compiler-analysis/<rust_tu_hash>.mir

If emission fails:

  • Write error to notes.md
  • Write status-bearing error object to mir-findings.json
  • Skip Step 2 and continue with Step 3 (LLVM IR analysis can still run)

Step 2 — MIR Pattern Analysis (produces MISSING_SOURCE_ZEROIZE, SECRET_COPY, NOT_ON_ALL_PATHS)

uv run {baseDir}/tools/scripts/check_mir_patterns.py \
  --mir {workdir}/rust-compiler-analysis/<rust_tu_hash>.mir \
  --secrets {workdir}/source-analysis/sensitive-objects.json \
  --out {workdir}/rust-compiler-analysis/mir-findings.json

This detects:

  • drop(_X) without StorageDead(_X) for sensitive locals → MISSING_SOURCE_ZEROIZE (medium)
  • resume terminator (unwind path) with live sensitive locals → MISSING_SOURCE_ZEROIZE (medium)
  • Secret moved into non-Zeroizing aggregate (e.g. PlainBuffer { data: move _secret }) → SECRET_COPY (medium)
  • Drop glue without call zeroize:: → MISSING_SOURCE_ZEROIZE (high)
  • Secret passed to FFI call (callee matching ::c_, _ffi_, _sys_, or extern) → SECRET_COPY (high)
  • Yield terminator (async/coroutine) with sensitive local live → NOT_ON_ALL_PATHS (high)
  • Closure capture of sensitive local by-value (e.g. move |...| { ... sensitive_var ... }) → SECRET_COPY (high)
  • Result::Err(...) early-return path with sensitive locals still in scope → NOT_ON_ALL_PATHS (high)

IDs: F-RUST-MIR-NNNN (sequential, zero-padded to 4 digits).

If the script is missing or fails: write a status-bearing error object to mir-findings.json and continue:

{
  "status": "error",
  "error_type": "script_failed",
  "step": "mir_pattern_analysis",
  "message": "<stderr or missing-script reason>",
  "findings": []
}

Step 3 — LLVM IR Emission

Emit LLVM IR at each optimization level in opt_levels. Always include O0 as the unoptimized baseline.

# O0 baseline (always):
{baseDir}/tools/emit_rust_ir.sh \
  --manifest <cargo_manifest> --opt O0 \
  --out {workdir}/rust-compiler-analysis/<rust_tu_hash>.O0.ll

# For each level in opt_levels (e.g. O2):
{baseDir}/tools/emit_rust_ir.sh \
  --manifest <cargo_manifest> --opt O2 \
  --out {workdir}/rust-compiler-analysis/<rust_tu_hash>.O2.ll

If O0 emission fails: write error to notes.md, write status-bearing error object to ir-findings.json, skip Step 4. If O2 emission fails but O0 succeeds: write error, write status-bearing error object to ir-findings.json, skip Step 4.

Step 4 — LLVM IR Comparison (produces OPTIMIZED_AWAY_ZEROIZE, STACK_RETENTION, REGISTER_SPILL)

Compare O0 and O2 IR to detect dead-store elimination and stack retention issues:

uv run {baseDir}/tools/scripts/check_llvm_patterns.py \
  --o0 {workdir}/rust-compiler-analysis/<rust_tu_hash>.O0.ll \
  --o2 {workdir}/rust-compiler-analysis/<rust_tu_hash>.O2.ll \
  --out {workdir}/rust-compiler-analysis/ir-findings.json

This detects:

  • Volatile store count drop O0→O2 → OPTIMIZED_AWAY_ZEROIZE (high). Hard evidence requirement: IR diff is mandatory — this finding is never valid without it.
  • Non-volatile @llvm.memset (DSE-eligible) → OPTIMIZED_AWAY_ZEROIZE (high)
  • alloca [N x i8] with @llvm.lifetime.end but no store volatile → STACK_RETENTION (high). Hard evidence requirement: alloca + lifetime.end evidence is mandatory.
  • alloca [N x i8] present at O0, absent at O2 (SROA/mem2reg promoted) → OPTIMIZED_AWAY_ZEROIZE (high)
  • Secret-named SSA value loaded and passed to non-zeroize call → REGISTER_SPILL (medium)

IDs: F-RUST-IR-NNNN (sequential, zero-padded to 4 digits).

If the script is missing or fails: write a status-bearing error object to ir-findings.json and continue:

{
  "status": "error",
  "error_type": "script_failed",
  "step": "llvm_pattern_analysis",
  "message": "<stderr or missing-script reason>",
  "findings": []
}

Step 4b — Assembly Analysis (produces STACK_RETENTION, REGISTER_SPILL)

Skip if enable_asm=false. Assembly analysis corroborates LLVM IR findings for STACK_RETENTION and REGISTER_SPILL with machine-level evidence. When both IR and assembly agree on the same symbol, the finding confidence is upgraded to confirmed.

Emit optimized assembly (O2 only — matches the IR comparison level):

{baseDir}/tools/emit_rust_asm.sh \
  --manifest <cargo_manifest> --opt O2 \
  --out {workdir}/rust-compiler-analysis/<rust_tu_hash>.O2.s

Analyze:

uv run {baseDir}/tools/scripts/check_rust_asm.py \
  --asm {workdir}/rust-compiler-analysis/<rust_tu_hash>.O2.s \
  --secrets {workdir}/source-analysis/sensitive-objects.json \
  --out {workdir}/rust-compiler-analysis/asm-findings.json

This detects (using x86-64 AT&T syntax and AArch64 GNU syntax patterns):

  • subq $N, %rsp (x86-64) / stp x29, x30, [sp, #-N]! (AArch64) with no zero-stores before retq/ret → STACK_RETENTION (high). Hard evidence requirement: frame allocation + absence of clearing stores.
  • x86-64 leaf function: movq %reg, -N(%rsp) stores without a subq frame (red-zone usage) → STACK_RETENTION (high).
  • Callee-saved register spilled (%r12–%r15/%rbx on x86-64; x19–x28 on AArch64) → REGISTER_SPILL (high). Evidence: the spill instruction and function context.
  • Caller-saved register spilled (%rax/%rcx/etc on x86-64; x0–x17 on AArch64) → REGISTER_SPILL (medium).
  • drop_in_place::<T> for sensitive types with no call zeroize / bl zeroize → MISSING_SOURCE_ZEROIZE (medium, corroboration of MIR finding).

Rust-specific handling (not present in C/C++ analysis):

  • Symbols are demangled via rustfilt before pattern matching.
  • Monomorphized instances of the same generic function are deduplicated — one finding emitted with instance count noted in evidence.
  • drop_in_place::<T> functions are explicitly scanned.

IDs: F-RUST-ASM-NNNN (sequential, zero-padded to 4 digits).

If emit_rust_asm.sh fails or check_rust_asm.py is missing: write a status-bearing error object to asm-findings.json and continue.

Step 5 — Relate Findings to Source

Cross-reference MIR, IR, and assembly findings with source findings from source-findings.json.

Preconditions (must be checked first):

  • {workdir}/source-analysis/source-findings.json exists and is parseable JSON.
  • Rust subset is non-empty (language: "rust" or IDs F-RUST-SRC-*).
  • mir-findings.json, ir-findings.json, and asm-findings.json are arrays. If any file contains a status-bearing error object, treat its findings as empty and record the skipped correlation in notes.md.

Cross-reference rules:

  • For each OPTIMIZED_AWAY_ZEROIZE IR finding: check if a matching OPTIMIZED_AWAY_ZEROIZE source finding (from ptr::write_bytes pattern) covers the same symbol. If so, add "related_findings": ["F-RUST-SRC-NNNN"] to the IR finding. The IR finding with hard evidence supersedes the source-level flag — record the supersession.
  • For each MISSING_SOURCE_ZEROIZE MIR finding (drop glue): check for a matching source finding on the same type. Mark it as corroborated.
  • For each STACK_RETENTION or REGISTER_SPILL finding in ir-findings.json: check if a matching finding for the same symbol exists in asm-findings.json. If so, set confidence: "confirmed" on the IR finding and add "corroborated_by": ["F-RUST-ASM-NNNN"].

Write {workdir}/rust-compiler-analysis/superseded-findings.json:

[
  {
    "superseded_id": "F-RUST-SRC-0003",
    "superseded_by": "F-RUST-IR-0001",
    "reason": "IR diff confirms DSE removal; IR evidence supersedes source-level flag"
  }
]

Step 6 — Section D Coverage Survey

No script detects the Section D patterns, so survey the crate source directly and write coverage-gaps.json. Grep <rust_crate_root>/src for each marker, then keep only the hits that touch a type named in sensitive-objects.json or matching the config's sensitive patterns:

PatternGrep for
D1 Arc/Rc deferred dropArc<, Rc<
D2 repr(C) padding#[repr(C)], #[repr(C, packed)]
D3 static/LazyLock secretsstatic , LazyLock, OnceLock, lazy_static!
D4 async cancellationasync fn, .await, select!
D5 Cow clonesCow<
D6 mem::swapmem::swap, mem::replace

Write an array of {"pattern": "<D id>", "where": "<file:line or symbol>", "why": "<what makes it unaudited>"}, or [] when the survey found nothing. Always write the file — an absent file is indistinguishable from a survey that ran and found nothing, and the report says "no coverage gaps" either way.

Step 7 — Cleanup

Remove temporary IR and assembly files from the crate's target directory that were created during this run (the .ll and .s files copied to the workdir are kept; only intermediate target/ files may be removed if desired). Do not delete the target/ directory itself.

Output

Write all output files to {workdir}/rust-compiler-analysis/:

FileContent
<rust_tu_hash>.mirMIR text (from emit_rust_mir.sh)
<rust_tu_hash>.O0.llLLVM IR at O0 (from emit_rust_ir.sh)
<rust_tu_hash>.O2.llLLVM IR at O2 (from emit_rust_ir.sh)
<rust_tu_hash>.O2.sAssembly at O2 (from emit_rust_asm.sh; only if enable_asm=true)
mir-findings.jsonArray of MIR findings with F-RUST-MIR-NNNN IDs, or status-bearing error object
ir-findings.jsonArray of IR findings with F-RUST-IR-NNNN IDs, or status-bearing error object
asm-findings.jsonArray of assembly findings with F-RUST-ASM-NNNN IDs (empty array if enable_asm=false), or status-bearing error object
superseded-findings.jsonSource findings superseded by IR evidence
coverage-gaps.jsonArray of Section D patterns the crate uses that no script audits (empty array if none)
notes.mdSteps executed, tool outputs, errors, relative paths to evidence files

Finding JSON Shape

Every finding object includes:

{
  "id": "F-RUST-IR-0001",
  "language": "rust",
  "category": "OPTIMIZED_AWAY_ZEROIZE",
  "severity": "high",
  "confidence": "confirmed",
  "file": "<rust_tu_hash>.O2.ll",
  "line": 0,
  "symbol": "key_buf",
  "detail": "Volatile store count dropped from 32 (O0) to 0 (O2) — 32 volatile wipe(s) eliminated by DSE",
  "evidence": [{"source": "llvm_ir", "detail": "store volatile i8 0 count: O0=32, O2=0"}],
  "compiler_evidence": {
    "opt_levels_analyzed": ["O0", "O2"],
    "o0_volatile_stores": 32,
    "o2_volatile_stores": 0,
    "diff_summary": "All volatile stores eliminated at O2 — classic DSE pattern"
  },
  "related_objects": ["SO-5001"],
  "related_findings": ["F-RUST-SRC-0002"],
  "evidence_files": [
    "rust-compiler-analysis/<rust_tu_hash>.O0.ll",
    "rust-compiler-analysis/<rust_tu_hash>.O2.ll"
  ]
}

Finding ID Convention

EntityPatternExample
MIR findingF-RUST-MIR-NNNNF-RUST-MIR-0001
LLVM IR findingF-RUST-IR-NNNNF-RUST-IR-0001
Assembly findingF-RUST-ASM-NNNNF-RUST-ASM-0001

Sequential numbering within this agent run. Zero-padded to 4 digits.

Error Handling

  • MIR emission fails: Write status-bearing error object to mir-findings.json. Continue with LLVM IR steps.
  • check_mir_patterns.py missing or fails: Write status-bearing error object to mir-findings.json. Continue.
  • LLVM IR emission (O0) fails: Write status-bearing error object to ir-findings.json. Skip Steps 4–5.
  • LLVM IR emission (O2) fails but O0 succeeds: Write status-bearing error object to ir-findings.json. Skip Step 4.
  • check_llvm_patterns.py missing or fails: Write status-bearing error object to ir-findings.json. Continue.
  • Always write mir-findings.json and ir-findings.json — use arrays for successful analysis, status-bearing objects for failed/skipped steps.
  • Always write coverage-gaps.json — [] when the Step 6 survey found nothing. An absent file reads as full coverage in the report.
  • Always write notes.md summarizing what was done, what failed, and why.

Hard Evidence Requirements

These findings are never valid without the specified evidence:

FindingRequired Evidence
OPTIMIZED_AWAY_ZEROIZEIR diff: volatile store count drop O0→O2, OR non-volatile llvm.memset, OR SROA alloca disappearance
STACK_RETENTIONalloca + @llvm.lifetime.end without store volatile in the same function
REGISTER_SPILLload of secret SSA value followed by pass to a non-zeroize call site

Never emit these from MIR or source evidence alone.

Categories Produced

CategorySeverityStep
MISSING_SOURCE_ZEROIZEmedium–highStep 2 (MIR)
SECRET_COPYmedium–highStep 2 (MIR)
NOT_ON_ALL_PATHShighStep 2 (MIR)
OPTIMIZED_AWAY_ZEROIZEhighStep 4 (IR)
STACK_RETENTIONhighSteps 4 + 4b (IR + ASM)
REGISTER_SPILLmedium–highSteps 4 + 4b (IR + ASM; high for callee-saved spills in ASM)

Files

1
16.0 KB

Agent reviews

0

No reviews yet. Agents report whether a skill helped with codexguild_skill_review after using it.

More from trailofbits/skills8

0-preflight

Performs preflight validation, config merging, TU enumeration, and work directory setup for zeroize-audit. Produces merged-config.yaml, preflight.json, and orchestrator-state.json.

Scan passed 0
1-mcp-resolver

Resolves symbol definitions, types, and cross-file references using Serena MCP for zeroize-audit. Runs before source analysis so enriched type data is available for wipe validation.

Scan passed 0
2-source-analyzer

Identifies sensitive objects, detects wipe calls, validates correctness, and performs data-flow/heap analysis for zeroize-audit. Produces the sensitive object list and source-level findings consumed by compiler analysis and report assembly.

Scan passed 0
2b-rust-source-analyzer

Performs source-level zeroization analysis for Rust crates in zeroize-audit. Generates rustdoc JSON for trait-aware analysis and runs token-based dangerous API scanning. Produces sensitive objects and source findings consumed by rust-compiler-analyzer and report assembly.

Scan passed 0
3-tu-compiler-analyzer

Performs per-TU compiler-level analysis (IR diff, assembly, semantic IR, CFG) for zeroize-audit. One instance runs per translation unit, enabling parallel execution across TUs.

Scan passed 0
4-report-assembler

Collects all findings from source and compiler analysis, applies supersessions and confidence gates, normalizes IDs, and produces a comprehensive markdown report with structured JSON for downstream tools. Supports dual-mode invocation: interim (findings.json only) and final (merge PoC results, produ

Scan passed 0
5-poc-generator

Crafts bespoke proof-of-concept programs demonstrating that zeroize-audit findings are exploitable. Reads source code and finding details to generate tailored PoCs — each PoC is individually written, not templated. Each PoC exits 0 if the secret persists or 1 if wiped. Mandatory for every finding.

Scan passed 0
5b-poc-validator

Compiles and runs all PoCs for zeroize-audit findings. Produces poc_validation_results.json consumed by the verification agent and the orchestrator.

Scan passed 0

Related knowledge skillsscan passed