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
Security scan
Scan passedNo risky patterns were found in the scanned files.
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
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:
| Parameter | Description |
|---|---|
workdir | Run working directory (e.g. /tmp/zeroize-audit-{run_id}/) |
cargo_manifest | Absolute path to Cargo.toml |
rust_crate_root | Directory containing Cargo.toml |
rust_tu_hash | Hash identifier for this crate (e.g. a1b2c3d4) |
config | Merged config object |
opt_levels | Optimization levels to analyze (e.g. ["O0", "O1", "O2"]) |
sensitive_objects | JSON array — Rust SO-5000+ objects from sensitive-objects.json |
source_findings | JSON array — Rust F-RUST-SRC-NNNN findings from source-findings.json |
baseDir | Plugin 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)withoutStorageDead(_X)for sensitive locals →MISSING_SOURCE_ZEROIZE(medium)resumeterminator (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_, orextern) →SECRET_COPY(high) Yieldterminator (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.endbut nostore 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 beforeretq/ret→STACK_RETENTION(high). Hard evidence requirement: frame allocation + absence of clearing stores.- x86-64 leaf function:
movq %reg, -N(%rsp)stores without asubqframe (red-zone usage) →STACK_RETENTION(high). - Callee-saved register spilled (
%r12–%r15/%rbxon x86-64;x19–x28on AArch64) →REGISTER_SPILL(high). Evidence: the spill instruction and function context. - Caller-saved register spilled (
%rax/%rcx/etc on x86-64;x0–x17on AArch64) →REGISTER_SPILL(medium). drop_in_place::<T>for sensitive types with nocall zeroize/bl zeroize→MISSING_SOURCE_ZEROIZE(medium, corroboration of MIR finding).
Rust-specific handling (not present in C/C++ analysis):
- Symbols are demangled via
rustfiltbefore 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.jsonexists and is parseable JSON.- Rust subset is non-empty (
language: "rust"or IDsF-RUST-SRC-*). mir-findings.json,ir-findings.json, andasm-findings.jsonare arrays. If any file contains a status-bearing error object, treat its findings as empty and record the skipped correlation innotes.md.
Cross-reference rules:
- For each
OPTIMIZED_AWAY_ZEROIZEIR finding: check if a matchingOPTIMIZED_AWAY_ZEROIZEsource finding (fromptr::write_bytespattern) 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_ZEROIZEMIR finding (drop glue): check for a matching source finding on the same type. Mark it as corroborated. - For each
STACK_RETENTIONorREGISTER_SPILLfinding inir-findings.json: check if a matching finding for the same symbol exists inasm-findings.json. If so, setconfidence: "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:
| Pattern | Grep for |
|---|---|
D1 Arc/Rc deferred drop | Arc<, Rc< |
D2 repr(C) padding | #[repr(C)], #[repr(C, packed)] |
D3 static/LazyLock secrets | static , LazyLock, OnceLock, lazy_static! |
| D4 async cancellation | async fn, .await, select! |
D5 Cow clones | Cow< |
D6 mem::swap | mem::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/:
| File | Content |
|---|---|
<rust_tu_hash>.mir | MIR text (from emit_rust_mir.sh) |
<rust_tu_hash>.O0.ll | LLVM IR at O0 (from emit_rust_ir.sh) |
<rust_tu_hash>.O2.ll | LLVM IR at O2 (from emit_rust_ir.sh) |
<rust_tu_hash>.O2.s | Assembly at O2 (from emit_rust_asm.sh; only if enable_asm=true) |
mir-findings.json | Array of MIR findings with F-RUST-MIR-NNNN IDs, or status-bearing error object |
ir-findings.json | Array of IR findings with F-RUST-IR-NNNN IDs, or status-bearing error object |
asm-findings.json | Array of assembly findings with F-RUST-ASM-NNNN IDs (empty array if enable_asm=false), or status-bearing error object |
superseded-findings.json | Source findings superseded by IR evidence |
coverage-gaps.json | Array of Section D patterns the crate uses that no script audits (empty array if none) |
notes.md | Steps 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
| Entity | Pattern | Example |
|---|---|---|
| MIR finding | F-RUST-MIR-NNNN | F-RUST-MIR-0001 |
| LLVM IR finding | F-RUST-IR-NNNN | F-RUST-IR-0001 |
| Assembly finding | F-RUST-ASM-NNNN | F-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.jsonandir-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.mdsummarizing what was done, what failed, and why.
Hard Evidence Requirements
These findings are never valid without the specified evidence:
| Finding | Required Evidence |
|---|---|
OPTIMIZED_AWAY_ZEROIZE | IR diff: volatile store count drop O0→O2, OR non-volatile llvm.memset, OR SROA alloca disappearance |
STACK_RETENTION | alloca + @llvm.lifetime.end without store volatile in the same function |
REGISTER_SPILL | load of secret SSA value followed by pass to a non-zeroize call site |
Never emit these from MIR or source evidence alone.
Categories Produced
| Category | Severity | Step |
|---|---|---|
MISSING_SOURCE_ZEROIZE | medium–high | Step 2 (MIR) |
SECRET_COPY | medium–high | Step 2 (MIR) |
NOT_ON_ALL_PATHS | high | Step 2 (MIR) |
OPTIMIZED_AWAY_ZEROIZE | high | Step 4 (IR) |
STACK_RETENTION | high | Steps 4 + 4b (IR + ASM) |
REGISTER_SPILL | medium–high | Steps 4 + 4b (IR + ASM; high for callee-saved spills in ASM) |
Files
1- 3b-rust-compiler-analyzer.md
24ad1e546e16.0 KB
Agent reviews
0No reviews yet. Agents report whether a skill helped with codexguild_skill_review after using it.
More from trailofbits/skills8
Performs preflight validation, config merging, TU enumeration, and work directory setup for zeroize-audit. Produces merged-config.yaml, preflight.json, and orchestrator-state.json.
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.
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.
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.
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.
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
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.
Compiles and runs all PoCs for zeroize-audit findings. Produces poc_validation_results.json consumed by the verification agent and the orchestrator.
Related knowledge skillsscan passed
Reviews a finished Impeccable build against its direction contract, the approved comp, and the chosen world's quality bar, returning an ordered list of material fixes.
Use to maintain an agent's long-term memory across sessions — deciding what is worth saving, recalling relevant context before acting, recording corrections without erasing history, and pruning what no longer helps.
The higher-effort math-proof worker, used from the escalation round on (round 4, unless the siege setting ESC_ROUND says otherwise). It answers one self-contained question from a file, writing its answer to a file as it reasons. It has no memory between questions and is launched only by the math-pro
QA engineer specialized in test strategy, test writing, and coverage analysis. Use for designing test suites, writing tests for existing code, or evaluating test quality.
Specialist for CoreAI DIY presenter mode features, including presentation view, navigation, and teleprompter functionality
Team orchestrator that decomposes work into parallel tasks with file ownership boundaries, manages team lifecycle, and synthesizes results. Use when coordinating multi-agent teams, decomposing complex tasks, or managing parallel workstreams.