mirror of
https://github.com/elicpeter/nyx.git
synced 2026-07-18 21:21:03 +02:00
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>
This commit is contained in:
parent
8cbbec7d90
commit
f96a89e7c1
87 changed files with 11505 additions and 1099 deletions
161
src/cfg_analysis/error_handling.rs
Normal file
161
src/cfg_analysis/error_handling.rs
Normal file
|
|
@ -0,0 +1,161 @@
|
|||
use super::{AnalysisContext, CfgAnalysis, CfgFinding, Confidence, is_sink};
|
||||
use crate::cfg::{EdgeKind, StmtKind};
|
||||
use crate::patterns::Severity;
|
||||
use petgraph::graph::NodeIndex;
|
||||
use petgraph::visit::EdgeRef;
|
||||
|
||||
pub struct IncompleteErrorHandling;
|
||||
|
||||
/// Check if the true branch of an If node terminates (has Return/Break/Continue).
|
||||
fn branch_terminates(cfg: &crate::cfg::Cfg, if_node: NodeIndex) -> bool {
|
||||
// Follow the True edge from the If node
|
||||
let true_successors: Vec<NodeIndex> = cfg
|
||||
.edges(if_node)
|
||||
.filter(|e| matches!(e.weight(), EdgeKind::True))
|
||||
.map(|e| e.target())
|
||||
.collect();
|
||||
|
||||
if true_successors.is_empty() {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check if any path through the true branch terminates
|
||||
for &start in &true_successors {
|
||||
if terminates_on_all_paths(cfg, start, if_node) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
false
|
||||
}
|
||||
|
||||
/// Check if all paths from `node` reach a Return/Break/Continue before exiting scope.
|
||||
fn terminates_on_all_paths(
|
||||
cfg: &crate::cfg::Cfg,
|
||||
node: NodeIndex,
|
||||
_scope_entry: NodeIndex,
|
||||
) -> bool {
|
||||
use std::collections::HashSet;
|
||||
|
||||
let mut visited = HashSet::new();
|
||||
let mut stack = vec![node];
|
||||
|
||||
while let Some(current) = stack.pop() {
|
||||
if !visited.insert(current) {
|
||||
continue;
|
||||
}
|
||||
|
||||
let info = &cfg[current];
|
||||
match info.kind {
|
||||
StmtKind::Return | StmtKind::Break | StmtKind::Continue => {
|
||||
// This path terminates
|
||||
continue;
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
|
||||
let successors: Vec<_> = cfg.neighbors(current).collect();
|
||||
if successors.is_empty() {
|
||||
// Reached a dead end without terminating — path does not terminate
|
||||
return false;
|
||||
}
|
||||
|
||||
for succ in successors {
|
||||
// Don't follow back edges (loops)
|
||||
let is_back_edge = cfg
|
||||
.edges(current)
|
||||
.any(|e| e.target() == succ && matches!(e.weight(), EdgeKind::Back));
|
||||
if !is_back_edge {
|
||||
stack.push(succ);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
true
|
||||
}
|
||||
|
||||
/// Find successor nodes after an If node merges (nodes reachable from both branches).
|
||||
fn find_post_if_sinks(cfg: &crate::cfg::Cfg, if_node: NodeIndex) -> Vec<NodeIndex> {
|
||||
let mut sinks_after = Vec::new();
|
||||
|
||||
// Get all successors of the if node's merge point
|
||||
// Walk through successors looking for sinks
|
||||
let mut visited = std::collections::HashSet::new();
|
||||
let mut stack: Vec<NodeIndex> = cfg.neighbors(if_node).collect();
|
||||
|
||||
while let Some(current) = stack.pop() {
|
||||
if !visited.insert(current) {
|
||||
continue;
|
||||
}
|
||||
|
||||
let info = &cfg[current];
|
||||
if is_sink(info) || (info.kind == StmtKind::Call && info.callee.is_some()) {
|
||||
sinks_after.push(current);
|
||||
}
|
||||
|
||||
for succ in cfg.neighbors(current) {
|
||||
let is_back_edge = cfg
|
||||
.edges(current)
|
||||
.any(|e| e.target() == succ && matches!(e.weight(), EdgeKind::Back));
|
||||
if !is_back_edge {
|
||||
stack.push(succ);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
sinks_after
|
||||
}
|
||||
|
||||
impl CfgAnalysis for IncompleteErrorHandling {
|
||||
fn name(&self) -> &'static str {
|
||||
"incomplete-error-handling"
|
||||
}
|
||||
|
||||
fn run(&self, ctx: &AnalysisContext) -> Vec<CfgFinding> {
|
||||
let mut findings = Vec::new();
|
||||
|
||||
for idx in ctx.cfg.node_indices() {
|
||||
let info = &ctx.cfg[idx];
|
||||
|
||||
// Look for If nodes whose condition involves "err" or "error"
|
||||
if info.kind != StmtKind::If {
|
||||
continue;
|
||||
}
|
||||
|
||||
let mentions_err = info.uses.iter().any(|u| {
|
||||
let lower = u.to_ascii_lowercase();
|
||||
lower == "err" || lower == "error" || lower.contains("err")
|
||||
});
|
||||
|
||||
if !mentions_err {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Check: does the true branch terminate?
|
||||
if branch_terminates(ctx.cfg, idx) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Check: are there dangerous calls/sinks after this error check?
|
||||
let post_sinks = find_post_if_sinks(ctx.cfg, idx);
|
||||
let has_dangerous_successor = post_sinks.iter().any(|&s| is_sink(&ctx.cfg[s]));
|
||||
|
||||
if has_dangerous_successor {
|
||||
findings.push(CfgFinding {
|
||||
rule_id: "cfg-error-fallthrough".to_string(),
|
||||
title: "Error check without return".to_string(),
|
||||
severity: Severity::Medium,
|
||||
confidence: Confidence::Medium,
|
||||
span: info.span,
|
||||
message: "Error check does not terminate on error; \
|
||||
execution falls through to dangerous operations"
|
||||
.to_string(),
|
||||
evidence: vec![idx],
|
||||
score: None,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
findings
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue