nyx/src/cfg_analysis/resources.rs

164 lines
5.3 KiB
Rust
Raw Normal View History

Feat/full cfg (#30) * feat: Enhance control flow analysis with function summaries and taint analysis * feat: Update taint analysis to utilize function summaries for enhanced tracking * Refactor `walk.rs` batch processing and override handling: - Renamed `Batcher` to `BatchSender` for clarity. - Added `BatchSender::new` constructor for cleaner initialization. - Simplified batch size management in `BatchSender`. - Extracted `build_overrides` function for reusable override construction. - Improved error handling and validation in override building. - Enhanced performance with directory and file type filtering in `walk`. * Improve logging and streamline directory walk process: - Added detailed `tracing` logs for debugging batch flushes, override construction, and walk initialization/completion. - Optimized and simplified `filter_entry` logic for directory and file type filters. - Improved metadata checks and max file size enforcement during the scan. * Refactor and optimize taint tracking, label rules, and directory walk process: - Replaced `DefaultHasher` with `blake3::Hasher` for improved taint hashing. - Enhanced sorting and hashing logic in `taint.rs` for consistency and efficiency. - Removed unused `set_hash` function and redundant imports across files. - Improved batch sender logic in `walk.rs`, renaming key components for clarity. - Unified `spawn_senders` and `spawn_file_walker` with thread handling and channel tuple return. - Expanded label rules with additional matchers for sources, sanitizers, and sinks. - Deprecated `dump_cfg` and specific logging utilities in `cfg.rs` for code cleanup. * fix: fixed let chains error in walk.rs * fix: updated dependencies * fix: updated dependencies * chore: Remove standard error in scan.rs * feat: Introduce function summaries for enhanced taint and control flow analysis * feat: Enhance taint analysis with interop support and function summaries * feat: Add configuration analysis module and enhance matcher rules * feat: Add arity column to function_summaries and handle schema migration * fix: fixed clippy &PathBuf warnings * chore: Update dependencies and versioning in Cargo files * docs: Update README to enhance clarity and detail on features and analysis modes * chore: Update CHANGELOG for version 0.2.0 with new features, changes, and fixes * docs: Update SECURITY.md to clarify version support status --------- Co-authored-by: elipeter <eli.peter@es.fcm.travel>
2026-02-24 23:44:07 -05:00
use super::dominators;
use super::rules;
use super::{AnalysisContext, CfgAnalysis, CfgFinding, Confidence};
use crate::cfg::StmtKind;
use crate::patterns::Severity;
use petgraph::graph::NodeIndex;
use std::collections::HashSet;
pub struct ResourceMisuse;
/// Find nodes matching acquire patterns for a given resource pair.
fn find_acquire_nodes(ctx: &AnalysisContext, acquire_patterns: &[&str]) -> Vec<NodeIndex> {
ctx.cfg
.node_indices()
.filter(|&idx| {
let info = &ctx.cfg[idx];
if info.kind != StmtKind::Call {
return false;
}
if let Some(callee) = &info.callee {
let callee_lower = callee.to_ascii_lowercase();
acquire_patterns.iter().any(|p| {
let pl = p.to_ascii_lowercase();
callee_lower.ends_with(&pl) || callee_lower == pl
})
} else {
false
}
})
.collect()
}
/// Find nodes matching release patterns for a given resource pair.
fn find_release_nodes(ctx: &AnalysisContext, release_patterns: &[&str]) -> Vec<NodeIndex> {
ctx.cfg
.node_indices()
.filter(|&idx| {
let info = &ctx.cfg[idx];
if info.kind != StmtKind::Call {
return false;
}
if let Some(callee) = &info.callee {
let callee_lower = callee.to_ascii_lowercase();
release_patterns.iter().any(|p| {
let pl = p.to_ascii_lowercase();
callee_lower.ends_with(&pl) || callee_lower == pl
})
} else {
false
}
})
.collect()
}
/// Check if a release node is on all paths from acquire to every exit.
fn release_on_all_exit_paths(
ctx: &AnalysisContext,
acquire: NodeIndex,
release_nodes: &[NodeIndex],
exit: NodeIndex,
) -> bool {
// Use post-dominators as optimization: if any release post-dominates acquire, it's fine
if let Some(post_doms) = dominators::compute_post_dominators(ctx.cfg) {
for &release in release_nodes {
if dominators::dominates(&post_doms, release, acquire) {
return true;
}
}
}
// Fall back to path enumeration via DFS
// Check if all paths from acquire to exit pass through a release
let release_set: HashSet<_> = release_nodes.iter().copied().collect();
all_paths_pass_through(ctx, acquire, exit, &release_set)
}
/// Check if all paths from `from` to `to` pass through at least one node in `through`.
fn all_paths_pass_through(
ctx: &AnalysisContext,
from: NodeIndex,
to: NodeIndex,
through: &HashSet<NodeIndex>,
) -> bool {
use std::collections::VecDeque;
if through.contains(&from) {
return true;
}
// BFS, tracking whether we've passed through a required node
let mut visited = HashSet::new();
let mut queue = VecDeque::new();
queue.push_back((from, false));
visited.insert((from, false));
while let Some((node, passed)) = queue.pop_front() {
if node == to {
if !passed {
return false; // Found a path to exit without passing through release
}
continue;
}
for succ in ctx.cfg.neighbors(node) {
let new_passed = passed || through.contains(&succ);
let state = (succ, new_passed);
if visited.insert(state) {
queue.push_back(state);
}
}
}
true
}
impl CfgAnalysis for ResourceMisuse {
fn name(&self) -> &'static str {
"resource-misuse"
}
fn run(&self, ctx: &AnalysisContext) -> Vec<CfgFinding> {
let pairs = rules::resource_pairs(ctx.lang);
let exit = match dominators::find_exit_node(ctx.cfg) {
Some(e) => e,
None => return Vec::new(),
};
let mut findings = Vec::new();
for pair in pairs {
let acquire_nodes = find_acquire_nodes(ctx, pair.acquire);
let release_nodes = find_release_nodes(ctx, pair.release);
for &acquire in &acquire_nodes {
if !release_on_all_exit_paths(ctx, acquire, &release_nodes, exit) {
let info = &ctx.cfg[acquire];
let callee_desc = info.callee.as_deref().unwrap_or("(acquire)");
findings.push(CfgFinding {
rule_id: if pair.resource_name == "mutex" {
"cfg-lock-not-released".to_string()
} else {
"cfg-resource-leak".to_string()
},
title: format!("{} may leak", pair.resource_name),
severity: Severity::Medium,
confidence: Confidence::Medium,
span: info.span,
message: format!(
"`{callee_desc}` acquires {} but not all exit paths \
release it",
pair.resource_name
),
evidence: vec![acquire],
score: None,
});
}
}
}
findings
}
}