@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,381 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
# /// script
|
|
3
|
+
# requires-python = ">=3.11"
|
|
4
|
+
# dependencies = []
|
|
5
|
+
# ///
|
|
6
|
+
"""
|
|
7
|
+
Control-Flow Graph analyzer for zeroization path coverage.
|
|
8
|
+
|
|
9
|
+
This tool builds CFGs from source code or LLVM IR to verify that:
|
|
10
|
+
- Zeroization occurs on ALL execution paths
|
|
11
|
+
- Early returns don't skip cleanup
|
|
12
|
+
- Error paths include proper cleanup
|
|
13
|
+
- Wipes dominate all function exits
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
import argparse
|
|
17
|
+
import json
|
|
18
|
+
import re
|
|
19
|
+
import sys
|
|
20
|
+
from dataclasses import dataclass, field
|
|
21
|
+
from pathlib import Path
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
@dataclass
|
|
25
|
+
class CFGNode:
|
|
26
|
+
"""Node in control flow graph."""
|
|
27
|
+
|
|
28
|
+
id: str
|
|
29
|
+
type: str # 'entry', 'exit', 'statement', 'branch', 'return'
|
|
30
|
+
line_num: int | None = None
|
|
31
|
+
statement: str | None = None
|
|
32
|
+
successors: list[str] = field(default_factory=list)
|
|
33
|
+
predecessors: list[str] = field(default_factory=list)
|
|
34
|
+
has_wipe: bool = False
|
|
35
|
+
has_sensitive_var: bool = False
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
class CFGBuilder:
|
|
39
|
+
"""Build control flow graph from source or IR."""
|
|
40
|
+
|
|
41
|
+
def __init__(self, source_file: Path, sensitive_patterns: list[str], wipe_patterns: list[str]):
|
|
42
|
+
self.source_file = source_file
|
|
43
|
+
self.sensitive_patterns = sensitive_patterns
|
|
44
|
+
self.wipe_patterns = wipe_patterns
|
|
45
|
+
self.nodes: dict[str, CFGNode] = {}
|
|
46
|
+
self.entry_node: str | None = None
|
|
47
|
+
self.exit_nodes: set[str] = set()
|
|
48
|
+
self.node_counter = 0
|
|
49
|
+
|
|
50
|
+
def create_node(
|
|
51
|
+
self, node_type: str, line_num: int | None = None, statement: str | None = None
|
|
52
|
+
) -> str:
|
|
53
|
+
"""Create a new CFG node."""
|
|
54
|
+
node_id = f"node_{self.node_counter}"
|
|
55
|
+
self.node_counter += 1
|
|
56
|
+
|
|
57
|
+
node = CFGNode(id=node_id, type=node_type, line_num=line_num, statement=statement)
|
|
58
|
+
|
|
59
|
+
# Check if this node has sensitive variable
|
|
60
|
+
if statement:
|
|
61
|
+
for pattern in self.sensitive_patterns:
|
|
62
|
+
if re.search(pattern, statement, re.IGNORECASE):
|
|
63
|
+
node.has_sensitive_var = True
|
|
64
|
+
break
|
|
65
|
+
|
|
66
|
+
# Check if this node has wipe
|
|
67
|
+
for pattern in self.wipe_patterns:
|
|
68
|
+
if re.search(pattern, statement):
|
|
69
|
+
node.has_wipe = True
|
|
70
|
+
break
|
|
71
|
+
|
|
72
|
+
self.nodes[node_id] = node
|
|
73
|
+
return node_id
|
|
74
|
+
|
|
75
|
+
def add_edge(self, from_id: str, to_id: str) -> None:
|
|
76
|
+
"""Add directed edge in CFG."""
|
|
77
|
+
if from_id in self.nodes and to_id in self.nodes:
|
|
78
|
+
self.nodes[from_id].successors.append(to_id)
|
|
79
|
+
self.nodes[to_id].predecessors.append(from_id)
|
|
80
|
+
|
|
81
|
+
def build_from_source(self) -> None:
|
|
82
|
+
"""Build CFG from source code (simplified C/C++ parser)."""
|
|
83
|
+
with open(self.source_file) as f:
|
|
84
|
+
lines = f.readlines()
|
|
85
|
+
|
|
86
|
+
self.entry_node = self.create_node("entry")
|
|
87
|
+
current_node = self.entry_node
|
|
88
|
+
|
|
89
|
+
in_function = False
|
|
90
|
+
brace_depth = 0
|
|
91
|
+
branch_stack = [] # Stack of (condition_node, merge_node) pairs
|
|
92
|
+
|
|
93
|
+
for line_num, line in enumerate(lines, 1):
|
|
94
|
+
stripped = line.strip()
|
|
95
|
+
|
|
96
|
+
# Skip comments and empty lines
|
|
97
|
+
if not stripped or stripped.startswith("//") or stripped.startswith("/*"):
|
|
98
|
+
continue
|
|
99
|
+
|
|
100
|
+
# Function start
|
|
101
|
+
if "{" in line and not in_function:
|
|
102
|
+
in_function = True
|
|
103
|
+
brace_depth = line.count("{")
|
|
104
|
+
continue
|
|
105
|
+
|
|
106
|
+
if not in_function:
|
|
107
|
+
continue
|
|
108
|
+
|
|
109
|
+
# Track brace depth
|
|
110
|
+
brace_depth += line.count("{") - line.count("}")
|
|
111
|
+
|
|
112
|
+
# Function end
|
|
113
|
+
if brace_depth == 0:
|
|
114
|
+
in_function = False
|
|
115
|
+
# Connect to exit
|
|
116
|
+
exit_node = self.create_node("exit", line_num)
|
|
117
|
+
self.add_edge(current_node, exit_node)
|
|
118
|
+
self.exit_nodes.add(exit_node)
|
|
119
|
+
continue
|
|
120
|
+
|
|
121
|
+
# Return statement
|
|
122
|
+
if re.match(r"\s*return\b", stripped):
|
|
123
|
+
return_node = self.create_node("return", line_num, stripped)
|
|
124
|
+
self.add_edge(current_node, return_node)
|
|
125
|
+
exit_node = self.create_node("exit", line_num)
|
|
126
|
+
self.add_edge(return_node, exit_node)
|
|
127
|
+
self.exit_nodes.add(exit_node)
|
|
128
|
+
# Reset current for next statement (in case there's dead code)
|
|
129
|
+
current_node = return_node
|
|
130
|
+
continue
|
|
131
|
+
|
|
132
|
+
# If statement
|
|
133
|
+
if re.match(r"\s*if\s*\(", stripped):
|
|
134
|
+
branch_node = self.create_node("branch", line_num, stripped)
|
|
135
|
+
self.add_edge(current_node, branch_node)
|
|
136
|
+
|
|
137
|
+
# Create merge point for later
|
|
138
|
+
merge_node = self.create_node("statement", line_num, "// merge point")
|
|
139
|
+
branch_stack.append((branch_node, merge_node))
|
|
140
|
+
|
|
141
|
+
# True branch starts after condition
|
|
142
|
+
true_node = self.create_node("statement", line_num, "// true branch")
|
|
143
|
+
self.add_edge(branch_node, true_node)
|
|
144
|
+
current_node = true_node
|
|
145
|
+
continue
|
|
146
|
+
|
|
147
|
+
# Else statement
|
|
148
|
+
if re.match(r"\s*else\b", stripped):
|
|
149
|
+
if branch_stack:
|
|
150
|
+
branch_node, merge_node = branch_stack[-1]
|
|
151
|
+
# False branch
|
|
152
|
+
false_node = self.create_node("statement", line_num, "// false branch")
|
|
153
|
+
self.add_edge(branch_node, false_node)
|
|
154
|
+
# Connect previous path to merge
|
|
155
|
+
self.add_edge(current_node, merge_node)
|
|
156
|
+
current_node = false_node
|
|
157
|
+
continue
|
|
158
|
+
|
|
159
|
+
# End of branch (closing brace)
|
|
160
|
+
if stripped == "}" and branch_stack:
|
|
161
|
+
branch_node, merge_node = branch_stack.pop()
|
|
162
|
+
self.add_edge(current_node, merge_node)
|
|
163
|
+
current_node = merge_node
|
|
164
|
+
continue
|
|
165
|
+
|
|
166
|
+
# Regular statement
|
|
167
|
+
stmt_node = self.create_node("statement", line_num, stripped)
|
|
168
|
+
self.add_edge(current_node, stmt_node)
|
|
169
|
+
current_node = stmt_node
|
|
170
|
+
|
|
171
|
+
# Ensure we have at least one exit node
|
|
172
|
+
if not self.exit_nodes:
|
|
173
|
+
exit_node = self.create_node("exit")
|
|
174
|
+
self.add_edge(current_node, exit_node)
|
|
175
|
+
self.exit_nodes.add(exit_node)
|
|
176
|
+
|
|
177
|
+
def find_all_paths_to_exit(self) -> list[list[str]]:
|
|
178
|
+
"""Find all paths from entry to any exit node."""
|
|
179
|
+
if not self.entry_node:
|
|
180
|
+
return []
|
|
181
|
+
|
|
182
|
+
all_paths = []
|
|
183
|
+
|
|
184
|
+
def dfs(node_id: str, path: list[str], visited: set[str]) -> None:
|
|
185
|
+
if node_id in visited:
|
|
186
|
+
return # Avoid cycles
|
|
187
|
+
|
|
188
|
+
visited.add(node_id)
|
|
189
|
+
path.append(node_id)
|
|
190
|
+
|
|
191
|
+
node = self.nodes[node_id]
|
|
192
|
+
|
|
193
|
+
# If this is an exit node, save the path
|
|
194
|
+
if node_id in self.exit_nodes:
|
|
195
|
+
all_paths.append(path.copy())
|
|
196
|
+
else:
|
|
197
|
+
# Continue to successors
|
|
198
|
+
for succ_id in node.successors:
|
|
199
|
+
dfs(succ_id, path, visited.copy())
|
|
200
|
+
|
|
201
|
+
path.pop()
|
|
202
|
+
|
|
203
|
+
dfs(self.entry_node, [], set())
|
|
204
|
+
return all_paths
|
|
205
|
+
|
|
206
|
+
def check_path_has_wipe(self, path: list[str]) -> tuple[bool, str | None]:
|
|
207
|
+
"""Check if a path contains a wipe operation."""
|
|
208
|
+
for node_id in path:
|
|
209
|
+
if self.nodes[node_id].has_wipe:
|
|
210
|
+
return True, node_id
|
|
211
|
+
return False, None
|
|
212
|
+
|
|
213
|
+
def check_path_has_sensitive_var(self, path: list[str]) -> bool:
|
|
214
|
+
"""Check if a path uses sensitive variables."""
|
|
215
|
+
return any(self.nodes[node_id].has_sensitive_var for node_id in path)
|
|
216
|
+
|
|
217
|
+
def compute_dominators(self) -> dict[str, set[str]]:
|
|
218
|
+
"""Compute dominator sets for all nodes."""
|
|
219
|
+
if not self.entry_node:
|
|
220
|
+
return {}
|
|
221
|
+
|
|
222
|
+
# Initialize
|
|
223
|
+
dominators = {}
|
|
224
|
+
all_nodes = set(self.nodes.keys())
|
|
225
|
+
|
|
226
|
+
dominators[self.entry_node] = {self.entry_node}
|
|
227
|
+
|
|
228
|
+
for node_id in all_nodes:
|
|
229
|
+
if node_id != self.entry_node:
|
|
230
|
+
dominators[node_id] = all_nodes.copy()
|
|
231
|
+
|
|
232
|
+
# Iterate until fixpoint
|
|
233
|
+
changed = True
|
|
234
|
+
while changed:
|
|
235
|
+
changed = False
|
|
236
|
+
for node_id in all_nodes:
|
|
237
|
+
if node_id == self.entry_node:
|
|
238
|
+
continue
|
|
239
|
+
|
|
240
|
+
# Dom(n) = {n} ∪ (∩ Dom(p) for all predecessors p)
|
|
241
|
+
new_dom = {node_id}
|
|
242
|
+
if self.nodes[node_id].predecessors:
|
|
243
|
+
pred_doms = [dominators[pred] for pred in self.nodes[node_id].predecessors]
|
|
244
|
+
if pred_doms:
|
|
245
|
+
new_dom = new_dom.union(set.intersection(*pred_doms))
|
|
246
|
+
|
|
247
|
+
if new_dom != dominators[node_id]:
|
|
248
|
+
dominators[node_id] = new_dom
|
|
249
|
+
changed = True
|
|
250
|
+
|
|
251
|
+
return dominators
|
|
252
|
+
|
|
253
|
+
def verify_wipe_dominates_exits(self) -> dict:
|
|
254
|
+
"""Verify that wipe operations dominate all exit nodes."""
|
|
255
|
+
dominators = self.compute_dominators()
|
|
256
|
+
|
|
257
|
+
# Find all wipe nodes
|
|
258
|
+
wipe_nodes = [node_id for node_id, node in self.nodes.items() if node.has_wipe]
|
|
259
|
+
|
|
260
|
+
results = {
|
|
261
|
+
"wipe_dominates_all_exits": True,
|
|
262
|
+
"wipe_nodes": wipe_nodes,
|
|
263
|
+
"problematic_exits": [],
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
for exit_id in self.exit_nodes:
|
|
267
|
+
exit_doms = dominators.get(exit_id, set())
|
|
268
|
+
|
|
269
|
+
# Check if any wipe node dominates this exit
|
|
270
|
+
has_dominating_wipe = any(wipe_id in exit_doms for wipe_id in wipe_nodes)
|
|
271
|
+
|
|
272
|
+
if not has_dominating_wipe:
|
|
273
|
+
results["wipe_dominates_all_exits"] = False
|
|
274
|
+
results["problematic_exits"].append(
|
|
275
|
+
{
|
|
276
|
+
"exit_node": exit_id,
|
|
277
|
+
"line": self.nodes[exit_id].line_num,
|
|
278
|
+
"dominators": list(exit_doms),
|
|
279
|
+
}
|
|
280
|
+
)
|
|
281
|
+
|
|
282
|
+
return results
|
|
283
|
+
|
|
284
|
+
def analyze(self) -> dict:
|
|
285
|
+
"""Perform comprehensive CFG analysis."""
|
|
286
|
+
# Find all paths
|
|
287
|
+
all_paths = self.find_all_paths_to_exit()
|
|
288
|
+
|
|
289
|
+
# Check each path
|
|
290
|
+
paths_with_wipe = 0
|
|
291
|
+
paths_without_wipe = []
|
|
292
|
+
paths_with_sensitive_vars = 0
|
|
293
|
+
|
|
294
|
+
for i, path in enumerate(all_paths):
|
|
295
|
+
has_wipe, wipe_node = self.check_path_has_wipe(path)
|
|
296
|
+
has_sensitive = self.check_path_has_sensitive_var(path)
|
|
297
|
+
|
|
298
|
+
if has_wipe:
|
|
299
|
+
paths_with_wipe += 1
|
|
300
|
+
elif has_sensitive:
|
|
301
|
+
# Sensitive path without wipe
|
|
302
|
+
paths_without_wipe.append(
|
|
303
|
+
{
|
|
304
|
+
"path_id": i,
|
|
305
|
+
"length": len(path),
|
|
306
|
+
"nodes": [
|
|
307
|
+
{
|
|
308
|
+
"id": node_id,
|
|
309
|
+
"line": self.nodes[node_id].line_num,
|
|
310
|
+
"statement": self.nodes[node_id].statement,
|
|
311
|
+
}
|
|
312
|
+
for node_id in path
|
|
313
|
+
],
|
|
314
|
+
}
|
|
315
|
+
)
|
|
316
|
+
|
|
317
|
+
if has_sensitive:
|
|
318
|
+
paths_with_sensitive_vars += 1
|
|
319
|
+
|
|
320
|
+
# Dominator analysis
|
|
321
|
+
dominator_results = self.verify_wipe_dominates_exits()
|
|
322
|
+
|
|
323
|
+
return {
|
|
324
|
+
"cfg_stats": {
|
|
325
|
+
"total_nodes": len(self.nodes),
|
|
326
|
+
"total_paths": len(all_paths),
|
|
327
|
+
"exit_nodes": len(self.exit_nodes),
|
|
328
|
+
},
|
|
329
|
+
"wipe_coverage": {
|
|
330
|
+
"paths_with_wipe": paths_with_wipe,
|
|
331
|
+
"paths_without_wipe": len(paths_without_wipe),
|
|
332
|
+
"paths_with_sensitive_vars": paths_with_sensitive_vars,
|
|
333
|
+
"coverage_percentage": (paths_with_wipe / len(all_paths) * 100) if all_paths else 0,
|
|
334
|
+
},
|
|
335
|
+
"problematic_paths": paths_without_wipe,
|
|
336
|
+
"dominator_analysis": dominator_results,
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
|
|
340
|
+
def main():
|
|
341
|
+
parser = argparse.ArgumentParser(description="Control-flow graph analyzer")
|
|
342
|
+
parser.add_argument("--src", required=True, help="Source file to analyze")
|
|
343
|
+
parser.add_argument("--out", required=True, help="Output JSON file")
|
|
344
|
+
|
|
345
|
+
args = parser.parse_args()
|
|
346
|
+
|
|
347
|
+
# Default patterns
|
|
348
|
+
sensitive_patterns = [
|
|
349
|
+
r"\b(secret|key|seed|priv|private|sk|shared_secret|nonce|token|pwd|pass)\b"
|
|
350
|
+
]
|
|
351
|
+
wipe_patterns = [
|
|
352
|
+
r"\bexplicit_bzero\s*\(",
|
|
353
|
+
r"\bmemset_s\s*\(",
|
|
354
|
+
r"\bOPENSSL_cleanse\s*\(",
|
|
355
|
+
r"\bsodium_memzero\s*\(",
|
|
356
|
+
r"\bzeroize\s*\(",
|
|
357
|
+
]
|
|
358
|
+
|
|
359
|
+
# Build CFG
|
|
360
|
+
builder = CFGBuilder(Path(args.src), sensitive_patterns, wipe_patterns)
|
|
361
|
+
try:
|
|
362
|
+
builder.build_from_source()
|
|
363
|
+
except OSError as e:
|
|
364
|
+
print(f"Error: cannot read source file {args.src}: {e}", file=sys.stderr)
|
|
365
|
+
sys.exit(1)
|
|
366
|
+
|
|
367
|
+
# Analyze
|
|
368
|
+
results = {"source_file": args.src, "analysis": builder.analyze()}
|
|
369
|
+
|
|
370
|
+
# Write output
|
|
371
|
+
output_path = Path(args.out)
|
|
372
|
+
output_path.parent.mkdir(parents=True, exist_ok=True)
|
|
373
|
+
|
|
374
|
+
with open(output_path, "w") as f:
|
|
375
|
+
json.dump(results, f, indent=2)
|
|
376
|
+
|
|
377
|
+
print(f"OK: CFG analysis written to {args.out}")
|
|
378
|
+
|
|
379
|
+
|
|
380
|
+
if __name__ == "__main__":
|
|
381
|
+
main()
|
|
@@ -0,0 +1,211 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
set -euo pipefail
|
|
3
|
+
|
|
4
|
+
# Analyze heap allocations for security issues with sensitive data.
|
|
5
|
+
#
|
|
6
|
+
# Usage:
|
|
7
|
+
# analyze_heap.sh --src path/to/file.c --config config.yaml --out /tmp/heap_analysis.json
|
|
8
|
+
#
|
|
9
|
+
# Detects:
|
|
10
|
+
# - malloc/calloc/realloc for sensitive variables (should use secure allocators)
|
|
11
|
+
# - Missing mlock/madvise for sensitive heaps
|
|
12
|
+
# - Secure allocator usage (approved patterns)
|
|
13
|
+
|
|
14
|
+
usage() {
|
|
15
|
+
echo "Usage: $0 --src <file> --out <analysis.json> [--config <config.yaml>]" >&2
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
json_escape() {
|
|
19
|
+
local s="$1"
|
|
20
|
+
s="${s//\\/\\\\}"
|
|
21
|
+
s="${s//\"/\\\"}"
|
|
22
|
+
s="${s//$'\n'/\\n}"
|
|
23
|
+
s="${s//$'\t'/\\t}"
|
|
24
|
+
printf '%s' "$s"
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
SRC=""
|
|
28
|
+
CONFIG=""
|
|
29
|
+
OUT=""
|
|
30
|
+
|
|
31
|
+
while [[ $# -gt 0 ]]; do
|
|
32
|
+
case "$1" in
|
|
33
|
+
--src)
|
|
34
|
+
SRC="$2"
|
|
35
|
+
shift 2
|
|
36
|
+
;;
|
|
37
|
+
--config)
|
|
38
|
+
CONFIG="$2"
|
|
39
|
+
shift 2
|
|
40
|
+
;;
|
|
41
|
+
--out)
|
|
42
|
+
OUT="$2"
|
|
43
|
+
shift 2
|
|
44
|
+
;;
|
|
45
|
+
*)
|
|
46
|
+
echo "Unknown arg: $1" >&2
|
|
47
|
+
usage
|
|
48
|
+
exit 2
|
|
49
|
+
;;
|
|
50
|
+
esac
|
|
51
|
+
done
|
|
52
|
+
|
|
53
|
+
if [[ -z "$SRC" || -z "$OUT" ]]; then
|
|
54
|
+
usage
|
|
55
|
+
exit 2
|
|
56
|
+
fi
|
|
57
|
+
|
|
58
|
+
if [[ ! -f "$SRC" ]]; then
|
|
59
|
+
echo "Source file not found: $SRC" >&2
|
|
60
|
+
exit 2
|
|
61
|
+
fi
|
|
62
|
+
|
|
63
|
+
# Load patterns from config
|
|
64
|
+
SENSITIVE_PATTERN="(secret|key|seed|priv|private|sk|shared_secret|nonce|token|pwd|pass)"
|
|
65
|
+
SECURE_ALLOC_FUNCS="(OPENSSL_secure_malloc|OPENSSL_secure_zalloc|sodium_malloc|sodium_allocarray|SecureAlloc)"
|
|
66
|
+
|
|
67
|
+
if [[ -n "$CONFIG" ]] && [[ -f "$CONFIG" ]]; then
|
|
68
|
+
# Extract patterns from YAML (POSIX-compatible, no grep -P)
|
|
69
|
+
SENS_PAT=$(grep -A 20 "^sensitive_name_regex:" "$CONFIG" | sed -n 's/.*"\([^"]*\)".*/\1/p' | head -1 || echo "")
|
|
70
|
+
if [[ -n "$SENS_PAT" ]]; then
|
|
71
|
+
SENSITIVE_PATTERN="$SENS_PAT"
|
|
72
|
+
fi
|
|
73
|
+
|
|
74
|
+
SEC_FUNCS=$(grep -A 20 "^secure_heap_alloc_funcs:" "$CONFIG" | sed -n 's/.*- "\([^"]*\)".*/\1/p' | tr '\n' '|' | sed 's/|$//')
|
|
75
|
+
if [[ -n "$SEC_FUNCS" ]]; then
|
|
76
|
+
SECURE_ALLOC_FUNCS="($SEC_FUNCS)"
|
|
77
|
+
elif [[ -z "$SENS_PAT" ]]; then
|
|
78
|
+
echo "WARNING: config file provided but no patterns extracted from $CONFIG" >&2
|
|
79
|
+
fi
|
|
80
|
+
fi
|
|
81
|
+
|
|
82
|
+
# Arrays to collect findings
|
|
83
|
+
INSECURE_ALLOCS=()
|
|
84
|
+
SECURE_ALLOCS=()
|
|
85
|
+
MISSING_MLOCK=()
|
|
86
|
+
MISSING_MADVISE=()
|
|
87
|
+
MADVISE_RE='madvise[[:space:]]*\(([a-zA-Z_][a-zA-Z0-9_]*)[^)]*MADV_(DONTDUMP|DONTFORK|WIPEONFORK)'
|
|
88
|
+
|
|
89
|
+
# Track allocated pointers to check for mlock/madvise
|
|
90
|
+
declare -A ALLOCATED_PTRS
|
|
91
|
+
|
|
92
|
+
LINE_NUM=0
|
|
93
|
+
|
|
94
|
+
while IFS= read -r line; do
|
|
95
|
+
((LINE_NUM++))
|
|
96
|
+
|
|
97
|
+
# Skip comments
|
|
98
|
+
[[ "$line" =~ ^[[:space:]]*// ]] && continue
|
|
99
|
+
[[ "$line" =~ ^[[:space:]]*\* ]] && continue
|
|
100
|
+
|
|
101
|
+
# Detect insecure allocations
|
|
102
|
+
if [[ "$line" =~ ([a-zA-Z_][a-zA-Z0-9_]*)[[:space:]]*=[[:space:]]*(malloc|calloc|realloc)[[:space:]]*\( ]]; then
|
|
103
|
+
PTR="${BASH_REMATCH[1]}"
|
|
104
|
+
ALLOC_FUNC="${BASH_REMATCH[2]}"
|
|
105
|
+
|
|
106
|
+
if [[ "$PTR" =~ $SENSITIVE_PATTERN ]]; then
|
|
107
|
+
INSECURE_ALLOCS+=("{\"line\": $LINE_NUM, \"pointer\": \"$PTR\", \"allocator\": \"$ALLOC_FUNC\", \"severity\": \"high\", \"context\": \"$(json_escape "$line")\"}")
|
|
108
|
+
ALLOCATED_PTRS["$PTR"]="insecure:$LINE_NUM"
|
|
109
|
+
fi
|
|
110
|
+
fi
|
|
111
|
+
|
|
112
|
+
# Detect secure allocations
|
|
113
|
+
if [[ "$line" =~ ([a-zA-Z_][a-zA-Z0-9_]*)[[:space:]]*=[[:space:]]*($SECURE_ALLOC_FUNCS)[[:space:]]*\( ]]; then
|
|
114
|
+
PTR="${BASH_REMATCH[1]}"
|
|
115
|
+
ALLOC_FUNC="${BASH_REMATCH[2]}"
|
|
116
|
+
|
|
117
|
+
SECURE_ALLOCS+=("{\"line\": $LINE_NUM, \"pointer\": \"$PTR\", \"allocator\": \"$ALLOC_FUNC\", \"context\": \"$(json_escape "$line")\"}")
|
|
118
|
+
ALLOCATED_PTRS["$PTR"]="secure:$LINE_NUM"
|
|
119
|
+
fi
|
|
120
|
+
|
|
121
|
+
# Detect mlock usage
|
|
122
|
+
if [[ "$line" =~ mlock[2]?[[:space:]]*\(([a-zA-Z_][a-zA-Z0-9_]*) ]]; then
|
|
123
|
+
PTR="${BASH_REMATCH[1]}"
|
|
124
|
+
if [[ -n "${ALLOCATED_PTRS[$PTR]:-}" ]]; then
|
|
125
|
+
ALLOCATED_PTRS["$PTR"]="${ALLOCATED_PTRS[$PTR]}:mlocked"
|
|
126
|
+
fi
|
|
127
|
+
fi
|
|
128
|
+
|
|
129
|
+
# Detect madvise usage
|
|
130
|
+
if [[ "$line" =~ $MADVISE_RE ]]; then
|
|
131
|
+
PTR="${BASH_REMATCH[1]}"
|
|
132
|
+
if [[ -n "${ALLOCATED_PTRS[$PTR]:-}" ]]; then
|
|
133
|
+
ALLOCATED_PTRS["$PTR"]="${ALLOCATED_PTRS[$PTR]}:madvised"
|
|
134
|
+
fi
|
|
135
|
+
fi
|
|
136
|
+
|
|
137
|
+
done <"$SRC"
|
|
138
|
+
|
|
139
|
+
# Check for missing protections
|
|
140
|
+
for PTR in "${!ALLOCATED_PTRS[@]}"; do
|
|
141
|
+
INFO="${ALLOCATED_PTRS[$PTR]}"
|
|
142
|
+
|
|
143
|
+
if [[ "$INFO" =~ ^insecure: ]]; then
|
|
144
|
+
LINE="${INFO#insecure:}"
|
|
145
|
+
LINE="${LINE%%:*}"
|
|
146
|
+
|
|
147
|
+
if [[ ! "$INFO" =~ mlocked ]]; then
|
|
148
|
+
MISSING_MLOCK+=("{\"line\": $LINE, \"pointer\": \"$PTR\", \"recommendation\": \"Add mlock() to prevent swapping to disk\"}")
|
|
149
|
+
fi
|
|
150
|
+
|
|
151
|
+
if [[ ! "$INFO" =~ madvised ]]; then
|
|
152
|
+
MISSING_MADVISE+=("{\"line\": $LINE, \"pointer\": \"$PTR\", \"recommendation\": \"Add madvise(MADV_DONTDUMP) to exclude from core dumps\"}")
|
|
153
|
+
fi
|
|
154
|
+
fi
|
|
155
|
+
done
|
|
156
|
+
|
|
157
|
+
# Generate JSON report
|
|
158
|
+
mkdir -p "$(dirname "$OUT")"
|
|
159
|
+
|
|
160
|
+
cat >"$OUT" <<EOF
|
|
161
|
+
{
|
|
162
|
+
"source_file": "$SRC",
|
|
163
|
+
"findings": {
|
|
164
|
+
"insecure_allocations": [
|
|
165
|
+
$(
|
|
166
|
+
IFS=,
|
|
167
|
+
echo "${INSECURE_ALLOCS[*]}"
|
|
168
|
+
)
|
|
169
|
+
],
|
|
170
|
+
"secure_allocations": [
|
|
171
|
+
$(
|
|
172
|
+
IFS=,
|
|
173
|
+
echo "${SECURE_ALLOCS[*]}"
|
|
174
|
+
)
|
|
175
|
+
],
|
|
176
|
+
"missing_mlock": [
|
|
177
|
+
$(
|
|
178
|
+
IFS=,
|
|
179
|
+
echo "${MISSING_MLOCK[*]}"
|
|
180
|
+
)
|
|
181
|
+
],
|
|
182
|
+
"missing_madvise": [
|
|
183
|
+
$(
|
|
184
|
+
IFS=,
|
|
185
|
+
echo "${MISSING_MADVISE[*]}"
|
|
186
|
+
)
|
|
187
|
+
]
|
|
188
|
+
},
|
|
189
|
+
"summary": {
|
|
190
|
+
"insecure_alloc_count": ${#INSECURE_ALLOCS[@]},
|
|
191
|
+
"secure_alloc_count": ${#SECURE_ALLOCS[@]},
|
|
192
|
+
"missing_protection_count": $((${#MISSING_MLOCK[@]} + ${#MISSING_MADVISE[@]}))
|
|
193
|
+
},
|
|
194
|
+
"recommendations": [
|
|
195
|
+
"Replace malloc/calloc/realloc with OPENSSL_secure_malloc/sodium_malloc for sensitive data",
|
|
196
|
+
"Use mlock() to prevent sensitive memory from being swapped to disk",
|
|
197
|
+
"Use madvise(MADV_DONTDUMP) to exclude sensitive memory from core dumps",
|
|
198
|
+
"Use madvise(MADV_WIPEONFORK) to zero memory in child processes after fork"
|
|
199
|
+
]
|
|
200
|
+
}
|
|
201
|
+
EOF
|
|
202
|
+
|
|
203
|
+
# Validate JSON output
|
|
204
|
+
if command -v jq &>/dev/null; then
|
|
205
|
+
if ! jq empty "$OUT" 2>/dev/null; then
|
|
206
|
+
echo "ERROR: generated JSON is malformed: $OUT" >&2
|
|
207
|
+
exit 1
|
|
208
|
+
fi
|
|
209
|
+
fi
|
|
210
|
+
|
|
211
|
+
echo "OK: heap analysis written to $OUT"
|