@vigolium/piolium 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +117 -0
- package/agents/access-auditor.md +300 -0
- package/agents/assumption-breaker.md +154 -0
- package/agents/attack-designer.md +116 -0
- package/agents/code-scanner.md +139 -0
- package/agents/concurrency-auditor.md +238 -0
- package/agents/confirm-writer.md +257 -0
- package/agents/context-reviewer.md +274 -0
- package/agents/cross-verifier.md +165 -0
- package/agents/cve-scout.md +381 -0
- package/agents/env-builder.md +282 -0
- package/agents/env-profiler.md +205 -0
- package/agents/evidence-collector.md +140 -0
- package/agents/finding-grader.md +142 -0
- package/agents/finding-writer.md +148 -0
- package/agents/flow-tracer.md +106 -0
- package/agents/goal-backtracer.md +146 -0
- package/agents/history-miner.md +467 -0
- package/agents/independent-verifier.md +118 -0
- package/agents/intent-mapper.md +183 -0
- package/agents/longshot-collector.md +128 -0
- package/agents/longshot-prober.md +126 -0
- package/agents/patch-auditor.md +73 -0
- package/agents/poc-author.md +124 -0
- package/agents/poc-runner.md +194 -0
- package/agents/probe-lead.md +269 -0
- package/agents/red-challenger.md +101 -0
- package/agents/report-composer.md +208 -0
- package/agents/review-adjudicator.md +216 -0
- package/agents/spec-auditor.md +155 -0
- package/agents/taint-tracer.md +265 -0
- package/agents/test-locator.md +209 -0
- package/agents/threat-modeler.md +132 -0
- package/agents/variant-scanner.md +108 -0
- package/agents/variant-spotter.md +110 -0
- package/bin/piolium.mjs +376 -0
- package/extensions/piolium/_vendor/yaml.bundle.d.mts +6 -0
- package/extensions/piolium/_vendor/yaml.bundle.mjs +139 -0
- package/extensions/piolium/agent-runner.ts +322 -0
- package/extensions/piolium/agents.ts +266 -0
- package/extensions/piolium/audit-state.ts +522 -0
- package/extensions/piolium/bundled-resources.ts +97 -0
- package/extensions/piolium/candidate-scan.ts +966 -0
- package/extensions/piolium/command-target.ts +177 -0
- package/extensions/piolium/console-stream.ts +57 -0
- package/extensions/piolium/export-results.ts +380 -0
- package/extensions/piolium/findings.ts +448 -0
- package/extensions/piolium/heartbeat.ts +182 -0
- package/extensions/piolium/help.ts +234 -0
- package/extensions/piolium/index.ts +1865 -0
- package/extensions/piolium/longshot.ts +530 -0
- package/extensions/piolium/matcher-suggestions.ts +196 -0
- package/extensions/piolium/matcher-utils.ts +83 -0
- package/extensions/piolium/modes/balanced.ts +750 -0
- package/extensions/piolium/modes/confirm-bootstrap.ts +186 -0
- package/extensions/piolium/modes/confirm.ts +697 -0
- package/extensions/piolium/modes/deep.ts +917 -0
- package/extensions/piolium/modes/diff.ts +177 -0
- package/extensions/piolium/modes/lite.ts +540 -0
- package/extensions/piolium/modes/longshot.ts +595 -0
- package/extensions/piolium/modes/merge.ts +204 -0
- package/extensions/piolium/modes/phase-runner.ts +267 -0
- package/extensions/piolium/modes/reinvest.ts +546 -0
- package/extensions/piolium/modes/revisit.ts +279 -0
- package/extensions/piolium/modes.ts +48 -0
- package/extensions/piolium/phase-labels.ts +123 -0
- package/extensions/piolium/phase-status-strip.ts +92 -0
- package/extensions/piolium/prompt-prefix-editor.ts +39 -0
- package/extensions/piolium/providers/anthropic-vertex.ts +836 -0
- package/extensions/piolium/recon.ts +409 -0
- package/extensions/piolium/result-stats.ts +105 -0
- package/extensions/piolium/retry.ts +120 -0
- package/extensions/piolium/scheduler.ts +212 -0
- package/extensions/piolium/secrets.ts +368 -0
- package/extensions/piolium/tools/web-tools.ts +148 -0
- package/package.json +77 -0
- package/skills/agentic-actions-auditor/SKILL.md +327 -0
- package/skills/agentic-actions-auditor/references/action-profiles.md +186 -0
- package/skills/agentic-actions-auditor/references/cross-file-resolution.md +209 -0
- package/skills/agentic-actions-auditor/references/foundations.md +94 -0
- package/skills/agentic-actions-auditor/references/vector-a-env-var-intermediary.md +77 -0
- package/skills/agentic-actions-auditor/references/vector-b-direct-expression-injection.md +83 -0
- package/skills/agentic-actions-auditor/references/vector-c-cli-data-fetch.md +83 -0
- package/skills/agentic-actions-auditor/references/vector-d-pr-target-checkout.md +88 -0
- package/skills/agentic-actions-auditor/references/vector-e-error-log-injection.md +88 -0
- package/skills/agentic-actions-auditor/references/vector-f-subshell-expansion.md +82 -0
- package/skills/agentic-actions-auditor/references/vector-g-eval-of-ai-output.md +91 -0
- package/skills/agentic-actions-auditor/references/vector-h-dangerous-sandbox-configs.md +102 -0
- package/skills/agentic-actions-auditor/references/vector-i-wildcard-allowlists.md +88 -0
- package/skills/audit/SKILL.md +562 -0
- package/skills/audit/assets/icon.svg +7 -0
- package/skills/audit/hooks/scripts/validate_phase_output.py +550 -0
- package/skills/audit/references/adversarial-review.md +148 -0
- package/skills/audit/references/architecture-aware-sast.md +306 -0
- package/skills/audit/references/audit-workflow.md +737 -0
- package/skills/audit/references/chamber-protocol.md +384 -0
- package/skills/audit/references/creative-attack-modes.md +221 -0
- package/skills/audit/references/deep-analysis.md +273 -0
- package/skills/audit/references/domain-attack-playbooks.md +1129 -0
- package/skills/audit/references/knowledge-base-template.md +513 -0
- package/skills/audit/references/real-env-validation.md +191 -0
- package/skills/audit/references/report-templates.md +417 -0
- package/skills/audit/references/triage-and-prereqs.md +134 -0
- package/skills/audit/scripts/consolidate_drafts.py +554 -0
- package/skills/audit/scripts/partition_findings.py +152 -0
- package/skills/audit/scripts/rg-hotspots.sh +121 -0
- package/skills/audit/scripts/stamp_file_state.py +349 -0
- package/skills/code-reviewer/SKILL.md +65 -0
- package/skills/codeql/SKILL.md +281 -0
- package/skills/codeql/references/build-fixes.md +90 -0
- package/skills/codeql/references/diagnostic-query-templates.md +339 -0
- package/skills/codeql/references/extension-yaml-format.md +209 -0
- package/skills/codeql/references/important-only-suite.md +153 -0
- package/skills/codeql/references/language-details.md +207 -0
- package/skills/codeql/references/macos-arm64e-workaround.md +179 -0
- package/skills/codeql/references/performance-tuning.md +111 -0
- package/skills/codeql/references/quality-assessment.md +172 -0
- package/skills/codeql/references/ruleset-catalog.md +63 -0
- package/skills/codeql/references/run-all-suite.md +92 -0
- package/skills/codeql/references/sarif-processing.md +79 -0
- package/skills/codeql/references/threat-models.md +51 -0
- package/skills/codeql/workflows/build-database.md +280 -0
- package/skills/codeql/workflows/create-data-extensions.md +261 -0
- package/skills/codeql/workflows/run-analysis.md +301 -0
- package/skills/differential-review/SKILL.md +220 -0
- package/skills/differential-review/adversarial.md +203 -0
- package/skills/differential-review/methodology.md +234 -0
- package/skills/differential-review/patterns.md +300 -0
- package/skills/differential-review/reporting.md +369 -0
- package/skills/fp-check/SKILL.md +125 -0
- package/skills/fp-check/references/bug-class-verification.md +114 -0
- package/skills/fp-check/references/deep-verification.md +143 -0
- package/skills/fp-check/references/evidence-templates.md +91 -0
- package/skills/fp-check/references/false-positive-patterns.md +115 -0
- package/skills/fp-check/references/gate-reviews.md +27 -0
- package/skills/fp-check/references/standard-verification.md +78 -0
- package/skills/insecure-defaults/SKILL.md +117 -0
- package/skills/insecure-defaults/references/examples.md +409 -0
- package/skills/last30days/SKILL.md +444 -0
- package/skills/sarif-parsing/SKILL.md +483 -0
- package/skills/sarif-parsing/resources/jq-queries.md +162 -0
- package/skills/sarif-parsing/resources/sarif_helpers.py +331 -0
- package/skills/security-threat-model/LICENSE.txt +201 -0
- package/skills/security-threat-model/SKILL.md +81 -0
- package/skills/security-threat-model/agents/openai.yaml +4 -0
- package/skills/security-threat-model/references/prompt-template.md +255 -0
- package/skills/security-threat-model/references/security-controls-and-assets.md +32 -0
- package/skills/semgrep/SKILL.md +212 -0
- package/skills/semgrep/references/rulesets.md +162 -0
- package/skills/semgrep/references/scan-modes.md +110 -0
- package/skills/semgrep/references/scanner-task-prompt.md +140 -0
- package/skills/semgrep/scripts/merge_sarif.py +203 -0
- package/skills/semgrep/workflows/scan-workflow.md +311 -0
- package/skills/semgrep-rule-creator/SKILL.md +168 -0
- package/skills/semgrep-rule-creator/references/quick-reference.md +202 -0
- package/skills/semgrep-rule-creator/references/workflow.md +240 -0
- package/skills/semgrep-rule-variant-creator/SKILL.md +205 -0
- package/skills/semgrep-rule-variant-creator/references/applicability-analysis.md +250 -0
- package/skills/semgrep-rule-variant-creator/references/language-syntax-guide.md +324 -0
- package/skills/semgrep-rule-variant-creator/references/workflow.md +518 -0
- package/skills/sharp-edges/SKILL.md +292 -0
- package/skills/sharp-edges/references/auth-patterns.md +252 -0
- package/skills/sharp-edges/references/case-studies.md +274 -0
- package/skills/sharp-edges/references/config-patterns.md +333 -0
- package/skills/sharp-edges/references/crypto-apis.md +190 -0
- package/skills/sharp-edges/references/lang-c.md +205 -0
- package/skills/sharp-edges/references/lang-csharp.md +285 -0
- package/skills/sharp-edges/references/lang-go.md +270 -0
- package/skills/sharp-edges/references/lang-java.md +263 -0
- package/skills/sharp-edges/references/lang-javascript.md +269 -0
- package/skills/sharp-edges/references/lang-kotlin.md +265 -0
- package/skills/sharp-edges/references/lang-php.md +245 -0
- package/skills/sharp-edges/references/lang-python.md +274 -0
- package/skills/sharp-edges/references/lang-ruby.md +273 -0
- package/skills/sharp-edges/references/lang-rust.md +272 -0
- package/skills/sharp-edges/references/lang-swift.md +287 -0
- package/skills/sharp-edges/references/language-specific.md +588 -0
- package/skills/spec-to-code-compliance/SKILL.md +357 -0
- package/skills/spec-to-code-compliance/resources/COMPLETENESS_CHECKLIST.md +69 -0
- package/skills/spec-to-code-compliance/resources/IR_EXAMPLES.md +417 -0
- package/skills/spec-to-code-compliance/resources/OUTPUT_REQUIREMENTS.md +105 -0
- package/skills/supply-chain-risk-auditor/SKILL.md +67 -0
- package/skills/supply-chain-risk-auditor/resources/results-template.md +41 -0
- package/skills/variant-analysis/METHODOLOGY.md +327 -0
- package/skills/variant-analysis/SKILL.md +142 -0
- package/skills/variant-analysis/resources/codeql/cpp.ql +119 -0
- package/skills/variant-analysis/resources/codeql/go.ql +69 -0
- package/skills/variant-analysis/resources/codeql/java.ql +71 -0
- package/skills/variant-analysis/resources/codeql/javascript.ql +63 -0
- package/skills/variant-analysis/resources/codeql/python.ql +80 -0
- package/skills/variant-analysis/resources/semgrep/cpp.yaml +98 -0
- package/skills/variant-analysis/resources/semgrep/go.yaml +63 -0
- package/skills/variant-analysis/resources/semgrep/java.yaml +61 -0
- package/skills/variant-analysis/resources/semgrep/javascript.yaml +60 -0
- package/skills/variant-analysis/resources/semgrep/python.yaml +72 -0
- package/skills/variant-analysis/resources/variant-report-template.md +75 -0
- package/skills/vuln-report/SKILL.md +137 -0
- package/skills/vuln-report/agents/openai.yaml +4 -0
- package/skills/vuln-report/references/report-template.md +135 -0
- package/skills/wooyun-legacy/SKILL.md +367 -0
- package/skills/wooyun-legacy/references/bank-penetration.md +222 -0
- package/skills/wooyun-legacy/references/checklists/command-execution-checklist.md +119 -0
- package/skills/wooyun-legacy/references/checklists/csrf-checklist.md +74 -0
- package/skills/wooyun-legacy/references/checklists/file-upload-checklist.md +108 -0
- package/skills/wooyun-legacy/references/checklists/info-disclosure-checklist.md +114 -0
- package/skills/wooyun-legacy/references/checklists/logic-flaws-checklist.md +95 -0
- package/skills/wooyun-legacy/references/checklists/misconfig-checklist.md +124 -0
- package/skills/wooyun-legacy/references/checklists/path-traversal-checklist.md +87 -0
- package/skills/wooyun-legacy/references/checklists/rce-checklist.md +93 -0
- package/skills/wooyun-legacy/references/checklists/sql-injection-checklist.md +97 -0
- package/skills/wooyun-legacy/references/checklists/ssrf-checklist.md +99 -0
- package/skills/wooyun-legacy/references/checklists/unauthorized-access-checklist.md +89 -0
- package/skills/wooyun-legacy/references/checklists/weak-password-checklist.md +115 -0
- package/skills/wooyun-legacy/references/checklists/xss-checklist.md +103 -0
- package/skills/wooyun-legacy/references/checklists/xxe-checklist.md +130 -0
- package/skills/wooyun-legacy/references/info-disclosure.md +975 -0
- package/skills/wooyun-legacy/references/logic-flaws.md +721 -0
- package/skills/wooyun-legacy/references/path-traversal.md +1191 -0
- package/skills/wooyun-legacy/references/telecom-penetration.md +156 -0
- package/skills/wooyun-legacy/references/unauthorized-access.md +980 -0
- package/skills/wooyun-legacy/references/xss.md +746 -0
- package/skills/zeroize-audit/SKILL.md +371 -0
- package/skills/zeroize-audit/configs/c.yaml +21 -0
- package/skills/zeroize-audit/configs/default.yaml +128 -0
- package/skills/zeroize-audit/configs/rust.yaml +83 -0
- package/skills/zeroize-audit/prompts/report_template.md +238 -0
- package/skills/zeroize-audit/prompts/system.md +163 -0
- package/skills/zeroize-audit/prompts/task.md +97 -0
- package/skills/zeroize-audit/references/compile-commands.md +231 -0
- package/skills/zeroize-audit/references/detection-strategy.md +191 -0
- package/skills/zeroize-audit/references/ir-analysis.md +252 -0
- package/skills/zeroize-audit/references/mcp-analysis.md +221 -0
- package/skills/zeroize-audit/references/poc-generation.md +470 -0
- package/skills/zeroize-audit/references/rust-zeroization-patterns.md +867 -0
- package/skills/zeroize-audit/schemas/input.json +83 -0
- package/skills/zeroize-audit/schemas/output.json +140 -0
- package/skills/zeroize-audit/tools/analyze_asm.sh +202 -0
- package/skills/zeroize-audit/tools/analyze_cfg.py +381 -0
- package/skills/zeroize-audit/tools/analyze_heap.sh +211 -0
- package/skills/zeroize-audit/tools/analyze_ir_semantic.py +429 -0
- package/skills/zeroize-audit/tools/diff_ir.sh +135 -0
- package/skills/zeroize-audit/tools/diff_rust_mir.sh +189 -0
- package/skills/zeroize-audit/tools/emit_asm.sh +67 -0
- package/skills/zeroize-audit/tools/emit_ir.sh +77 -0
- package/skills/zeroize-audit/tools/emit_rust_asm.sh +178 -0
- package/skills/zeroize-audit/tools/emit_rust_ir.sh +150 -0
- package/skills/zeroize-audit/tools/emit_rust_mir.sh +158 -0
- package/skills/zeroize-audit/tools/extract_compile_flags.py +284 -0
- package/skills/zeroize-audit/tools/generate_poc.py +1329 -0
- package/skills/zeroize-audit/tools/mcp/apply_confidence_gates.py +113 -0
- package/skills/zeroize-audit/tools/mcp/check_mcp.sh +68 -0
- package/skills/zeroize-audit/tools/mcp/normalize_mcp_evidence.py +125 -0
- package/skills/zeroize-audit/tools/scripts/check_llvm_patterns.py +481 -0
- package/skills/zeroize-audit/tools/scripts/check_mir_patterns.py +554 -0
- package/skills/zeroize-audit/tools/scripts/check_rust_asm.py +424 -0
- package/skills/zeroize-audit/tools/scripts/check_rust_asm_aarch64.py +300 -0
- package/skills/zeroize-audit/tools/scripts/check_rust_asm_x86.py +283 -0
- package/skills/zeroize-audit/tools/scripts/find_dangerous_apis.py +375 -0
- package/skills/zeroize-audit/tools/scripts/semantic_audit.py +923 -0
- package/skills/zeroize-audit/tools/track_dataflow.sh +196 -0
- package/skills/zeroize-audit/tools/validate_rust_toolchain.sh +298 -0
- package/skills/zeroize-audit/workflows/phase-0-preflight.md +150 -0
- package/skills/zeroize-audit/workflows/phase-1-source-analysis.md +144 -0
- package/skills/zeroize-audit/workflows/phase-2-compiler-analysis.md +139 -0
- package/skills/zeroize-audit/workflows/phase-3-interim-report.md +46 -0
- package/skills/zeroize-audit/workflows/phase-4-poc-generation.md +46 -0
- package/skills/zeroize-audit/workflows/phase-5-poc-validation.md +136 -0
- package/skills/zeroize-audit/workflows/phase-6-final-report.md +44 -0
- package/skills/zeroize-audit/workflows/phase-7-test-generation.md +42 -0
- package/themes/piolium-srcery.json +94 -0
|
@@ -0,0 +1,481 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
# /// script
|
|
3
|
+
# requires-python = ">=3.11"
|
|
4
|
+
# dependencies = []
|
|
5
|
+
# ///
|
|
6
|
+
"""
|
|
7
|
+
check_llvm_patterns.py — LLVM IR comparison for Rust dead-store-elimination findings.
|
|
8
|
+
|
|
9
|
+
Reads LLVM IR files emitted by emit_rust_ir.sh (required: O0 and O2; optional: O1/O3) and detects:
|
|
10
|
+
- Volatile store count drop O0→O2 (OPTIMIZED_AWAY_ZEROIZE)
|
|
11
|
+
- Non-volatile llvm.memset on secret-sized range (OPTIMIZED_AWAY_ZEROIZE)
|
|
12
|
+
- alloca with @llvm.lifetime.end but no store volatile (STACK_RETENTION)
|
|
13
|
+
- Secret alloca present at O0 but absent at O2 (SROA/mem2reg) (OPTIMIZED_AWAY_ZEROIZE)
|
|
14
|
+
- Secret value in argument registers at call site (REGISTER_SPILL)
|
|
15
|
+
|
|
16
|
+
Usage:
|
|
17
|
+
uv run check_llvm_patterns.py --o0 <file.O0.ll> --o2 <file.O2.ll> --out <findings.json>
|
|
18
|
+
uv run check_llvm_patterns.py \
|
|
19
|
+
--o0 <file.O0.ll> --o1 <file.O1.ll> --o2 <file.O2.ll> --o3 <file.O3.ll> \
|
|
20
|
+
--out <findings.json>
|
|
21
|
+
|
|
22
|
+
Exit codes:
|
|
23
|
+
0 — ran successfully (findings may be empty)
|
|
24
|
+
1 — input file not found
|
|
25
|
+
2 — argument error
|
|
26
|
+
"""
|
|
27
|
+
|
|
28
|
+
import argparse
|
|
29
|
+
import json
|
|
30
|
+
import re
|
|
31
|
+
import sys
|
|
32
|
+
from pathlib import Path
|
|
33
|
+
|
|
34
|
+
# ---------------------------------------------------------------------------
|
|
35
|
+
# Secret-sized alloca sizes (bytes) — common cryptographic key sizes
|
|
36
|
+
# ---------------------------------------------------------------------------
|
|
37
|
+
|
|
38
|
+
SECRET_ALLOCA_SIZES = {16, 24, 32, 48, 64, 96, 128}
|
|
39
|
+
|
|
40
|
+
# Sensitive variable name pattern (matches LLVM SSA names)
|
|
41
|
+
SENSITIVE_SSA_RE = re.compile(
|
|
42
|
+
r"(?i)%(\w*(?:key|secret|password|token|nonce|seed|priv|master|credential)\w*)"
|
|
43
|
+
)
|
|
44
|
+
|
|
45
|
+
# ---------------------------------------------------------------------------
|
|
46
|
+
# Finding counter
|
|
47
|
+
# ---------------------------------------------------------------------------
|
|
48
|
+
|
|
49
|
+
_finding_counter = [0]
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def make_finding(
|
|
53
|
+
category: str,
|
|
54
|
+
severity: str,
|
|
55
|
+
detail: str,
|
|
56
|
+
file: str,
|
|
57
|
+
line: int,
|
|
58
|
+
symbol: str = "",
|
|
59
|
+
confidence: str = "likely",
|
|
60
|
+
) -> dict:
|
|
61
|
+
_finding_counter[0] += 1
|
|
62
|
+
fid = f"F-RUST-IR-{_finding_counter[0]:04d}"
|
|
63
|
+
return {
|
|
64
|
+
"id": fid,
|
|
65
|
+
"language": "rust",
|
|
66
|
+
"category": category,
|
|
67
|
+
"severity": severity,
|
|
68
|
+
"confidence": confidence,
|
|
69
|
+
"detail": detail,
|
|
70
|
+
"symbol": symbol,
|
|
71
|
+
"location": {"file": file, "line": line},
|
|
72
|
+
"evidence": [{"source": "llvm_ir", "detail": detail}],
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
# ---------------------------------------------------------------------------
|
|
77
|
+
# IR helpers
|
|
78
|
+
# ---------------------------------------------------------------------------
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
def count_volatile_stores(ir_text: str) -> int:
|
|
82
|
+
return len(re.findall(r"\bstore volatile\b", ir_text))
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
def extract_volatile_stores_by_target(ir_text: str) -> dict[str, int]:
|
|
86
|
+
"""
|
|
87
|
+
Return volatile-store counts keyed by the destination symbol.
|
|
88
|
+
Example matches:
|
|
89
|
+
store volatile i8 0, ptr %key
|
|
90
|
+
store volatile i32 0, i32* %buf
|
|
91
|
+
"""
|
|
92
|
+
stores: dict[str, int] = {}
|
|
93
|
+
vol_re = re.compile(r"\bstore volatile\b[^,]*,\s*(?:ptr|i\d+\*)\s+%([\w\.\-]+)")
|
|
94
|
+
for m in vol_re.finditer(ir_text):
|
|
95
|
+
name = m.group(1)
|
|
96
|
+
stores[name] = stores.get(name, 0) + 1
|
|
97
|
+
return stores
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
def extract_allocas(ir_text: str) -> dict[str, int]:
|
|
101
|
+
"""
|
|
102
|
+
Return {alloca_name: size_bytes} for fixed-size byte array allocas.
|
|
103
|
+
Matches: %name = alloca [N x i8]
|
|
104
|
+
"""
|
|
105
|
+
alloca_re = re.compile(r"%(\w+)\s*=\s*alloca\s+\[(\d+)\s*x\s*i8\]")
|
|
106
|
+
allocas: dict[str, int] = {}
|
|
107
|
+
for m in alloca_re.finditer(ir_text):
|
|
108
|
+
allocas[m.group(1)] = int(m.group(2))
|
|
109
|
+
return allocas
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
def extract_lifetime_ends(ir_text: str) -> set[str]:
|
|
113
|
+
"""Return set of alloca names referenced in @llvm.lifetime.end calls."""
|
|
114
|
+
lifetime_re = re.compile(r"call void @llvm\.lifetime\.end[^(]*\([^,]+,\s*(?:ptr|i8\*)\s+%(\w+)")
|
|
115
|
+
return {m.group(1) for m in lifetime_re.finditer(ir_text)}
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
def extract_volatile_store_targets(ir_text: str) -> set[str]:
|
|
119
|
+
"""Return set of symbols that receive volatile stores."""
|
|
120
|
+
return set(extract_volatile_stores_by_target(ir_text).keys())
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
def find_nonvolatile_memsets(ir_text: str) -> list[tuple[int, str]]:
|
|
124
|
+
"""
|
|
125
|
+
Return (lineno, line) for non-volatile @llvm.memset calls.
|
|
126
|
+
Volatile variant is @llvm.memset.element.unordered.atomic or has i1 true volatile flag.
|
|
127
|
+
"""
|
|
128
|
+
results: list[tuple[int, str]] = []
|
|
129
|
+
memset_re = re.compile(r"call void @llvm\.memset\.")
|
|
130
|
+
volatile_flag_re = re.compile(r"i1\s+true") # old-style volatile flag in args
|
|
131
|
+
|
|
132
|
+
for lineno, line in enumerate(ir_text.splitlines(), start=1):
|
|
133
|
+
if not memset_re.search(line):
|
|
134
|
+
continue
|
|
135
|
+
# Skip if it's the volatile atomic variant
|
|
136
|
+
if "unordered.atomic" in line:
|
|
137
|
+
continue
|
|
138
|
+
# Skip if volatile flag (i1 true) is present in args
|
|
139
|
+
if volatile_flag_re.search(line):
|
|
140
|
+
continue
|
|
141
|
+
results.append((lineno, line.strip()))
|
|
142
|
+
return results
|
|
143
|
+
|
|
144
|
+
|
|
145
|
+
def find_secret_returns(ir_text: str) -> list[tuple[int, str]]:
|
|
146
|
+
"""
|
|
147
|
+
Detect returns of secret-named SSA values.
|
|
148
|
+
Returns (lineno, symbol_without_percent).
|
|
149
|
+
"""
|
|
150
|
+
results: list[tuple[int, str]] = []
|
|
151
|
+
ret_re = re.compile(
|
|
152
|
+
r"\bret\s+[^%]*%(\w*(?:key|secret|password|token|nonce|seed|priv|master|credential)\w*)",
|
|
153
|
+
re.IGNORECASE,
|
|
154
|
+
)
|
|
155
|
+
for lineno, line in enumerate(ir_text.splitlines(), start=1):
|
|
156
|
+
m = ret_re.search(line)
|
|
157
|
+
if m:
|
|
158
|
+
results.append((lineno, m.group(1)))
|
|
159
|
+
return results
|
|
160
|
+
|
|
161
|
+
|
|
162
|
+
def find_secret_aggregate_passes(ir_text: str) -> list[tuple[int, str]]:
|
|
163
|
+
"""
|
|
164
|
+
Detect call sites that appear to pass aggregate values containing secret-named
|
|
165
|
+
symbols by value. This is heuristic and intentionally conservative.
|
|
166
|
+
Returns (lineno, argument_snippet).
|
|
167
|
+
"""
|
|
168
|
+
results: list[tuple[int, str]] = []
|
|
169
|
+
call_re = re.compile(r"\bcall\s+\S+\s+@\w+\s*\(([^)]*)\)")
|
|
170
|
+
for lineno, line in enumerate(ir_text.splitlines(), start=1):
|
|
171
|
+
m = call_re.search(line)
|
|
172
|
+
if not m:
|
|
173
|
+
continue
|
|
174
|
+
args = m.group(1)
|
|
175
|
+
if re.search(
|
|
176
|
+
r"%\w*(?:key|secret|password|token|nonce|seed|priv|master|credential)\w*",
|
|
177
|
+
args,
|
|
178
|
+
re.IGNORECASE,
|
|
179
|
+
) and ("{" in args or "byval" in args):
|
|
180
|
+
results.append((lineno, args[:120]))
|
|
181
|
+
return results
|
|
182
|
+
|
|
183
|
+
|
|
184
|
+
def find_arg_load_calls(ir_text: str) -> list[tuple[int, str, str]]:
|
|
185
|
+
"""
|
|
186
|
+
Detect: %secret_val = load ... %secret_alloca followed by a call that uses %secret_val.
|
|
187
|
+
Returns (lineno, varname, callee).
|
|
188
|
+
"""
|
|
189
|
+
results: list[tuple[int, str, str]] = []
|
|
190
|
+
lines = ir_text.splitlines()
|
|
191
|
+
|
|
192
|
+
load_re = re.compile(
|
|
193
|
+
r"(%\w*(?:key|secret|password|token|nonce|seed)\w*)\s*=\s*load\b", re.IGNORECASE
|
|
194
|
+
)
|
|
195
|
+
call_re = re.compile(r"call\s+\S+\s+(@\w+)\s*\(([^)]*)\)")
|
|
196
|
+
|
|
197
|
+
loaded_vars: dict[str, int] = {} # varname → lineno
|
|
198
|
+
define_re = re.compile(r"^define\s")
|
|
199
|
+
|
|
200
|
+
for lineno, line in enumerate(lines, start=1):
|
|
201
|
+
# Reset tracked loads at each LLVM IR function boundary to avoid
|
|
202
|
+
# cross-function false positives (I17).
|
|
203
|
+
if define_re.match(line):
|
|
204
|
+
loaded_vars.clear()
|
|
205
|
+
continue
|
|
206
|
+
|
|
207
|
+
# Track loads of sensitive-named SSA values
|
|
208
|
+
m = load_re.search(line)
|
|
209
|
+
if m:
|
|
210
|
+
loaded_vars[m.group(1)] = lineno
|
|
211
|
+
continue
|
|
212
|
+
|
|
213
|
+
# Check call sites
|
|
214
|
+
mc = call_re.search(line)
|
|
215
|
+
if not mc:
|
|
216
|
+
continue
|
|
217
|
+
callee = mc.group(1)
|
|
218
|
+
if "zeroize" in callee.lower() or "memset" in callee.lower():
|
|
219
|
+
continue
|
|
220
|
+
args = mc.group(2)
|
|
221
|
+
for varname, _load_lineno in loaded_vars.items():
|
|
222
|
+
if varname in args:
|
|
223
|
+
results.append((lineno, varname.lstrip("%"), callee))
|
|
224
|
+
|
|
225
|
+
return results
|
|
226
|
+
|
|
227
|
+
|
|
228
|
+
# ---------------------------------------------------------------------------
|
|
229
|
+
# Main analysis
|
|
230
|
+
# ---------------------------------------------------------------------------
|
|
231
|
+
|
|
232
|
+
|
|
233
|
+
def analyze(level_to_ir: dict[str, tuple[str, str]]) -> list[dict]:
|
|
234
|
+
"""Analyze LLVM IR files for zeroization issues.
|
|
235
|
+
|
|
236
|
+
Precondition: ``level_to_ir`` must contain at least ``"O0"`` and ``"O2"``
|
|
237
|
+
keys — if either is absent the function returns an empty list with no
|
|
238
|
+
diagnostic. The CLI always satisfies this; library callers must ensure it.
|
|
239
|
+
"""
|
|
240
|
+
findings: list[dict] = []
|
|
241
|
+
if "O0" not in level_to_ir or "O2" not in level_to_ir:
|
|
242
|
+
return findings
|
|
243
|
+
|
|
244
|
+
o0_file, o0_text = level_to_ir["O0"]
|
|
245
|
+
o2_file, o2_text = level_to_ir["O2"]
|
|
246
|
+
|
|
247
|
+
# --- 1. Global volatile store count drop O0 → O2 ---
|
|
248
|
+
o0_vol_count = count_volatile_stores(o0_text)
|
|
249
|
+
o2_vol_count = count_volatile_stores(o2_text)
|
|
250
|
+
|
|
251
|
+
if o0_vol_count > o2_vol_count:
|
|
252
|
+
diff = o0_vol_count - o2_vol_count
|
|
253
|
+
# line=0 is used for file-level findings that cannot be attributed to a
|
|
254
|
+
# single source line (I18). Downstream consumers should treat line 0
|
|
255
|
+
# as "file-level / unknown line".
|
|
256
|
+
findings.append(
|
|
257
|
+
make_finding(
|
|
258
|
+
"OPTIMIZED_AWAY_ZEROIZE",
|
|
259
|
+
"high",
|
|
260
|
+
f"Volatile store count dropped from {o0_vol_count} (O0) to {o2_vol_count} (O2) "
|
|
261
|
+
f"— {diff} volatile wipe(s) eliminated by dead-store elimination",
|
|
262
|
+
o2_file,
|
|
263
|
+
0,
|
|
264
|
+
)
|
|
265
|
+
)
|
|
266
|
+
|
|
267
|
+
# --- 1b. Per-target volatile store drop O0 -> O2 (hard evidence by symbol) ---
|
|
268
|
+
o0_vol_by_target = extract_volatile_stores_by_target(o0_text)
|
|
269
|
+
o2_vol_by_target = extract_volatile_stores_by_target(o2_text)
|
|
270
|
+
for target, o0_count in sorted(o0_vol_by_target.items()):
|
|
271
|
+
o2_count = o2_vol_by_target.get(target, 0)
|
|
272
|
+
if o0_count > o2_count:
|
|
273
|
+
findings.append(
|
|
274
|
+
make_finding(
|
|
275
|
+
"OPTIMIZED_AWAY_ZEROIZE",
|
|
276
|
+
"high",
|
|
277
|
+
f"Volatile stores to %{target} dropped from {o0_count} (O0) to {o2_count} (O2) "
|
|
278
|
+
f"— symbol-specific wipe elimination detected",
|
|
279
|
+
o2_file,
|
|
280
|
+
0,
|
|
281
|
+
symbol=target,
|
|
282
|
+
)
|
|
283
|
+
)
|
|
284
|
+
|
|
285
|
+
# --- 2. Non-volatile llvm.memset calls in O2 IR ---
|
|
286
|
+
for lineno, line_text in find_nonvolatile_memsets(o2_text):
|
|
287
|
+
findings.append(
|
|
288
|
+
make_finding(
|
|
289
|
+
"OPTIMIZED_AWAY_ZEROIZE",
|
|
290
|
+
"high",
|
|
291
|
+
f"Non-volatile @llvm.memset in O2 IR — DSE-eligible, may be removed at higher "
|
|
292
|
+
f"optimization. Use zeroize crate or volatile memset. IR: {line_text[:80]}",
|
|
293
|
+
o2_file,
|
|
294
|
+
lineno,
|
|
295
|
+
)
|
|
296
|
+
)
|
|
297
|
+
|
|
298
|
+
# --- 3. alloca with lifetime.end but no volatile store (STACK_RETENTION) ---
|
|
299
|
+
o2_allocas = extract_allocas(o2_text)
|
|
300
|
+
o2_lifetime_ends = extract_lifetime_ends(o2_text)
|
|
301
|
+
o2_vol_targets = extract_volatile_store_targets(o2_text)
|
|
302
|
+
|
|
303
|
+
for alloca_name, size in o2_allocas.items():
|
|
304
|
+
if size not in SECRET_ALLOCA_SIZES:
|
|
305
|
+
continue
|
|
306
|
+
if alloca_name not in o2_lifetime_ends:
|
|
307
|
+
continue
|
|
308
|
+
if alloca_name in o2_vol_targets:
|
|
309
|
+
continue
|
|
310
|
+
findings.append(
|
|
311
|
+
make_finding(
|
|
312
|
+
"STACK_RETENTION",
|
|
313
|
+
"high",
|
|
314
|
+
f"alloca [{size} x i8] %{alloca_name} has @llvm.lifetime.end but no "
|
|
315
|
+
"volatile store — stack bytes not wiped before slot is freed",
|
|
316
|
+
o2_file,
|
|
317
|
+
0,
|
|
318
|
+
symbol=alloca_name,
|
|
319
|
+
)
|
|
320
|
+
)
|
|
321
|
+
|
|
322
|
+
# --- 4. SROA/mem2reg: secret alloca present at O0 but absent at O2 ---
|
|
323
|
+
o0_allocas = extract_allocas(o0_text)
|
|
324
|
+
|
|
325
|
+
o0_vol_targets = extract_volatile_store_targets(o0_text)
|
|
326
|
+
for alloca_name, size in o0_allocas.items():
|
|
327
|
+
if size not in SECRET_ALLOCA_SIZES:
|
|
328
|
+
continue
|
|
329
|
+
if alloca_name in o2_allocas:
|
|
330
|
+
continue
|
|
331
|
+
# Hard evidence gate: only emit when O0 showed a wipe target on this alloca.
|
|
332
|
+
if alloca_name not in o0_vol_targets:
|
|
333
|
+
continue
|
|
334
|
+
findings.append(
|
|
335
|
+
make_finding(
|
|
336
|
+
"OPTIMIZED_AWAY_ZEROIZE",
|
|
337
|
+
"high",
|
|
338
|
+
f"alloca [{size} x i8] %{alloca_name} present at O0 but absent at O2 — "
|
|
339
|
+
"SROA/mem2reg promoted it to registers; any volatile stores targeting this "
|
|
340
|
+
"alloca are now unreachable",
|
|
341
|
+
o2_file,
|
|
342
|
+
0,
|
|
343
|
+
symbol=alloca_name,
|
|
344
|
+
)
|
|
345
|
+
)
|
|
346
|
+
|
|
347
|
+
# --- 5. Secret value in argument registers at call site (REGISTER_SPILL) ---
|
|
348
|
+
for lineno, varname, callee in find_arg_load_calls(o2_text):
|
|
349
|
+
findings.append(
|
|
350
|
+
make_finding(
|
|
351
|
+
"REGISTER_SPILL",
|
|
352
|
+
"medium",
|
|
353
|
+
f"Secret-named SSA value '%{varname}' loaded and passed directly to "
|
|
354
|
+
f"'{callee}' — value in argument register may not be cleared after call",
|
|
355
|
+
o2_file,
|
|
356
|
+
lineno,
|
|
357
|
+
symbol=varname,
|
|
358
|
+
)
|
|
359
|
+
)
|
|
360
|
+
|
|
361
|
+
# --- 6. Secret return values can persist in return registers ---
|
|
362
|
+
for lineno, varname in find_secret_returns(o2_text):
|
|
363
|
+
findings.append(
|
|
364
|
+
make_finding(
|
|
365
|
+
"REGISTER_SPILL",
|
|
366
|
+
"medium",
|
|
367
|
+
f"Secret-named SSA value '%{varname}' is returned directly — "
|
|
368
|
+
"value may persist in return registers after function exit",
|
|
369
|
+
o2_file,
|
|
370
|
+
lineno,
|
|
371
|
+
symbol=varname,
|
|
372
|
+
)
|
|
373
|
+
)
|
|
374
|
+
|
|
375
|
+
# --- 7. Aggregate/by-value secret argument passing ---
|
|
376
|
+
for lineno, snippet in find_secret_aggregate_passes(o2_text):
|
|
377
|
+
findings.append(
|
|
378
|
+
make_finding(
|
|
379
|
+
"SECRET_COPY",
|
|
380
|
+
"medium",
|
|
381
|
+
"Potential by-value aggregate call argument contains secret-named data; "
|
|
382
|
+
f"copy may escape zeroization tracking. Args: {snippet}",
|
|
383
|
+
o2_file,
|
|
384
|
+
lineno,
|
|
385
|
+
)
|
|
386
|
+
)
|
|
387
|
+
|
|
388
|
+
# Collect targets already reported in section 1b (O0→O2 per-symbol comparison)
|
|
389
|
+
# so that the multi-level section below does not re-emit the same target.
|
|
390
|
+
reported_by_1b: set[str] = {
|
|
391
|
+
target
|
|
392
|
+
for target, o0_count in o0_vol_by_target.items()
|
|
393
|
+
if o0_count > o2_vol_by_target.get(target, 0)
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
# --- 8. Optional multi-level comparison (O0->O1->O2, O2->O3) ---
|
|
397
|
+
# Skip the (O0, O2) adjacent pair when O1 is absent — that comparison is already
|
|
398
|
+
# done by sections 1 and 1b above, and re-emitting it here causes duplicate findings.
|
|
399
|
+
level_order = ["O0", "O1", "O2", "O3"]
|
|
400
|
+
present = [lvl for lvl in level_order if lvl in level_to_ir]
|
|
401
|
+
for idx in range(len(present) - 1):
|
|
402
|
+
from_level = present[idx]
|
|
403
|
+
to_level = present[idx + 1]
|
|
404
|
+
# O0→O2 without an intermediate O1 is already covered by sections 1/1b.
|
|
405
|
+
if from_level == "O0" and to_level == "O2":
|
|
406
|
+
continue
|
|
407
|
+
_, from_ir = level_to_ir[from_level]
|
|
408
|
+
to_file, to_ir = level_to_ir[to_level]
|
|
409
|
+
from_targets = extract_volatile_stores_by_target(from_ir)
|
|
410
|
+
to_targets = extract_volatile_stores_by_target(to_ir)
|
|
411
|
+
for target, from_count in sorted(from_targets.items()):
|
|
412
|
+
# Skip targets already covered by section 1b to avoid cascading duplicates.
|
|
413
|
+
if target in reported_by_1b:
|
|
414
|
+
continue
|
|
415
|
+
to_count = to_targets.get(target, 0)
|
|
416
|
+
if from_count > to_count:
|
|
417
|
+
findings.append(
|
|
418
|
+
make_finding(
|
|
419
|
+
"OPTIMIZED_AWAY_ZEROIZE",
|
|
420
|
+
"high",
|
|
421
|
+
f"Volatile stores to %{target} dropped from {from_count} ({from_level}) "
|
|
422
|
+
f"to {to_count} ({to_level})",
|
|
423
|
+
to_file,
|
|
424
|
+
0,
|
|
425
|
+
symbol=target,
|
|
426
|
+
)
|
|
427
|
+
)
|
|
428
|
+
|
|
429
|
+
return findings
|
|
430
|
+
|
|
431
|
+
|
|
432
|
+
# ---------------------------------------------------------------------------
|
|
433
|
+
# CLI
|
|
434
|
+
# ---------------------------------------------------------------------------
|
|
435
|
+
|
|
436
|
+
|
|
437
|
+
def main() -> int:
|
|
438
|
+
parser = argparse.ArgumentParser(
|
|
439
|
+
description="LLVM IR O0 vs O2 comparison for Rust dead-store-elimination findings"
|
|
440
|
+
)
|
|
441
|
+
parser.add_argument("--o0", required=True, help="Path to O0 .ll file")
|
|
442
|
+
parser.add_argument("--o2", required=True, help="Path to O2 .ll file")
|
|
443
|
+
parser.add_argument("--o1", required=False, help="Path to O1 .ll file (optional)")
|
|
444
|
+
parser.add_argument("--o3", required=False, help="Path to O3 .ll file (optional)")
|
|
445
|
+
parser.add_argument("--out", required=True, help="Output findings JSON path")
|
|
446
|
+
args = parser.parse_args()
|
|
447
|
+
|
|
448
|
+
level_paths: dict[str, Path] = {
|
|
449
|
+
"O0": Path(args.o0),
|
|
450
|
+
"O2": Path(args.o2),
|
|
451
|
+
}
|
|
452
|
+
if args.o1:
|
|
453
|
+
level_paths["O1"] = Path(args.o1)
|
|
454
|
+
if args.o3:
|
|
455
|
+
level_paths["O3"] = Path(args.o3)
|
|
456
|
+
|
|
457
|
+
for p in level_paths.values():
|
|
458
|
+
if not p.exists():
|
|
459
|
+
print(f"check_llvm_patterns.py: IR file not found: {p}", file=sys.stderr)
|
|
460
|
+
return 1
|
|
461
|
+
|
|
462
|
+
level_to_ir: dict[str, tuple[str, str]] = {}
|
|
463
|
+
try:
|
|
464
|
+
for level, path in level_paths.items():
|
|
465
|
+
level_to_ir[level] = (str(path), path.read_text(encoding="utf-8", errors="replace"))
|
|
466
|
+
except OSError as e:
|
|
467
|
+
print(f"check_llvm_patterns.py: failed to read IR: {e}", file=sys.stderr)
|
|
468
|
+
return 1
|
|
469
|
+
|
|
470
|
+
findings = analyze(level_to_ir)
|
|
471
|
+
|
|
472
|
+
out_path = Path(args.out)
|
|
473
|
+
out_path.parent.mkdir(parents=True, exist_ok=True)
|
|
474
|
+
out_path.write_text(json.dumps(findings, indent=2), encoding="utf-8")
|
|
475
|
+
|
|
476
|
+
print(f"check_llvm_patterns.py: {len(findings)} finding(s) written to {out_path}")
|
|
477
|
+
return 0
|
|
478
|
+
|
|
479
|
+
|
|
480
|
+
if __name__ == "__main__":
|
|
481
|
+
sys.exit(main())
|