mirror of
https://github.com/elicpeter/nyx.git
synced 2026-07-12 21:02:11 +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
170
src/cfg_analysis/mod.rs
Normal file
170
src/cfg_analysis/mod.rs
Normal file
|
|
@ -0,0 +1,170 @@
|
|||
pub mod auth;
|
||||
pub mod dominators;
|
||||
pub mod error_handling;
|
||||
pub mod guards;
|
||||
pub mod resources;
|
||||
pub mod rules;
|
||||
pub mod scoring;
|
||||
#[cfg(test)]
|
||||
mod tests;
|
||||
pub mod unreachable;
|
||||
|
||||
use crate::cfg::{FuncSummaries, NodeInfo, StmtKind};
|
||||
use crate::labels::DataLabel;
|
||||
use crate::patterns::Severity;
|
||||
use crate::summary::GlobalSummaries;
|
||||
use crate::symbol::Lang;
|
||||
use crate::taint;
|
||||
use petgraph::graph::NodeIndex;
|
||||
use std::collections::HashSet;
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
|
||||
pub enum Confidence {
|
||||
Low,
|
||||
Medium,
|
||||
High,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct CfgFinding {
|
||||
pub rule_id: String,
|
||||
#[allow(dead_code)]
|
||||
pub title: String,
|
||||
pub severity: Severity,
|
||||
pub confidence: Confidence,
|
||||
pub span: (usize, usize),
|
||||
#[allow(dead_code)]
|
||||
pub message: String,
|
||||
pub evidence: Vec<NodeIndex>,
|
||||
pub score: Option<f64>,
|
||||
}
|
||||
|
||||
pub struct AnalysisContext<'a> {
|
||||
pub cfg: &'a crate::cfg::Cfg,
|
||||
pub entry: NodeIndex,
|
||||
pub lang: Lang,
|
||||
#[allow(dead_code)]
|
||||
pub file_path: &'a str,
|
||||
#[allow(dead_code)]
|
||||
pub source_bytes: &'a [u8],
|
||||
pub func_summaries: &'a FuncSummaries,
|
||||
#[allow(dead_code)]
|
||||
pub global_summaries: Option<&'a GlobalSummaries>,
|
||||
pub taint_findings: &'a [taint::Finding],
|
||||
}
|
||||
|
||||
pub trait CfgAnalysis {
|
||||
#[allow(dead_code)]
|
||||
fn name(&self) -> &'static str;
|
||||
fn run(&self, ctx: &AnalysisContext) -> Vec<CfgFinding>;
|
||||
}
|
||||
|
||||
/// Run all registered analyses and return merged findings.
|
||||
pub fn run_all(ctx: &AnalysisContext) -> Vec<CfgFinding> {
|
||||
let analyses: Vec<Box<dyn CfgAnalysis>> = vec![
|
||||
Box::new(unreachable::UnreachableCode),
|
||||
Box::new(guards::UnguardedSink),
|
||||
Box::new(auth::AuthGap),
|
||||
Box::new(error_handling::IncompleteErrorHandling),
|
||||
Box::new(resources::ResourceMisuse),
|
||||
];
|
||||
let mut findings: Vec<CfgFinding> = analyses.iter().flat_map(|a| a.run(ctx)).collect();
|
||||
|
||||
// ── Dedup: suppress cfg-unguarded-sink when taint already covers the span ──
|
||||
// Collect spans where taint findings exist (sink byte offset).
|
||||
let taint_spans: HashSet<(usize, usize)> = ctx
|
||||
.taint_findings
|
||||
.iter()
|
||||
.map(|f| ctx.cfg[f.sink].span)
|
||||
.collect();
|
||||
|
||||
findings.retain(|f| {
|
||||
// If both taint and cfg-unguarded-sink fire on the same span,
|
||||
// suppress the structural CFG finding (taint is the primary signal).
|
||||
if f.rule_id == "cfg-unguarded-sink" && taint_spans.contains(&f.span) {
|
||||
return false;
|
||||
}
|
||||
true
|
||||
});
|
||||
|
||||
scoring::score_findings(&mut findings, ctx);
|
||||
findings.sort_by(|a, b| {
|
||||
b.score
|
||||
.partial_cmp(&a.score)
|
||||
.unwrap_or(std::cmp::Ordering::Equal)
|
||||
});
|
||||
findings
|
||||
}
|
||||
|
||||
/// Helper: check whether a node is a guard call (validate, sanitize, check, etc.).
|
||||
pub(crate) fn is_guard_call(info: &NodeInfo, lang: Lang) -> bool {
|
||||
if info.kind != StmtKind::Call {
|
||||
return false;
|
||||
}
|
||||
if let Some(callee) = &info.callee {
|
||||
let guard_rules = rules::guard_rules(lang);
|
||||
let callee_lower = callee.to_ascii_lowercase();
|
||||
for rule in guard_rules {
|
||||
for &m in rule.matchers {
|
||||
let ml = m.to_ascii_lowercase();
|
||||
if ml.ends_with('_') {
|
||||
if callee_lower.starts_with(&ml) {
|
||||
return true;
|
||||
}
|
||||
} else if callee_lower.ends_with(&ml) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
false
|
||||
}
|
||||
|
||||
/// Helper: check whether a node is an auth check call.
|
||||
pub(crate) fn is_auth_call(info: &NodeInfo, lang: Lang) -> bool {
|
||||
if info.kind != StmtKind::Call {
|
||||
return false;
|
||||
}
|
||||
if let Some(callee) = &info.callee {
|
||||
let auth_rules = rules::auth_rules(lang);
|
||||
let callee_lower = callee.to_ascii_lowercase();
|
||||
for rule in auth_rules {
|
||||
for &m in rule.matchers {
|
||||
let ml = m.to_ascii_lowercase();
|
||||
if ml.ends_with('_') {
|
||||
if callee_lower.starts_with(&ml) {
|
||||
return true;
|
||||
}
|
||||
} else if callee_lower.ends_with(&ml) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
false
|
||||
}
|
||||
|
||||
/// Helper: check if a function name looks like an entry point (HTTP handler, main, etc.).
|
||||
pub(crate) fn is_entry_point_func(func_name: &str, lang: Lang) -> bool {
|
||||
let ep_rules = rules::entry_point_rules(lang);
|
||||
let name_lower = func_name.to_ascii_lowercase();
|
||||
for rule in ep_rules {
|
||||
for &m in rule.matchers {
|
||||
let ml = m.to_ascii_lowercase();
|
||||
if ml.ends_with('*') {
|
||||
let prefix = &ml[..ml.len() - 1];
|
||||
if name_lower.starts_with(prefix) {
|
||||
return true;
|
||||
}
|
||||
} else if name_lower == ml {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
false
|
||||
}
|
||||
|
||||
/// Helper: check if a node is a sink.
|
||||
pub(crate) fn is_sink(info: &NodeInfo) -> bool {
|
||||
matches!(info.label, Some(DataLabel::Sink(_)))
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue