nyx/src/summary/tests.rs

259 lines
7 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::*;
fn make(name: &str, src: u8, san: u8, sink: u8) -> FuncSummary {
FuncSummary {
name: name.into(),
file_path: "test.rs".into(),
lang: "rust".into(),
param_count: 0,
param_names: vec![],
source_caps: src,
sanitizer_caps: san,
sink_caps: sink,
propagates_taint: false,
tainted_sink_params: vec![],
callees: vec![],
}
}
#[test]
fn primary_label_priority() {
// sink beats everything
let s = make("f", 0xFF, 0xFF, 0x01);
assert!(matches!(s.primary_label(), Some(DataLabel::Sink(_))));
// source beats sanitizer
let s = make("f", 0x01, 0x02, 0x00);
assert!(matches!(s.primary_label(), Some(DataLabel::Source(_))));
// sanitizer alone
let s = make("f", 0x00, 0x04, 0x00);
assert!(matches!(s.primary_label(), Some(DataLabel::Sanitizer(_))));
// nothing
let s = make("f", 0, 0, 0);
assert!(s.primary_label().is_none());
}
#[test]
fn merge_unions_conservatively() {
let a = make("foo", 0x01, 0x00, 0x00);
let b = FuncSummary {
sink_caps: 0x04,
propagates_taint: true,
tainted_sink_params: vec![0],
callees: vec!["bar".into()],
..make("foo", 0x00, 0x02, 0x00)
};
let merged = merge_summaries(vec![a, b], None);
let key = FuncKey {
lang: Lang::Rust,
namespace: "test.rs".into(),
name: "foo".into(),
arity: Some(0),
};
let foo = merged.get(&key).unwrap();
assert_eq!(foo.source_caps, 0x01);
assert_eq!(foo.sanitizer_caps, 0x02);
assert_eq!(foo.sink_caps, 0x04);
assert!(foo.propagates_taint);
assert_eq!(foo.tainted_sink_params, vec![0]);
assert_eq!(foo.callees, vec!["bar".to_string()]);
}
#[test]
fn is_interesting_detects_all_cases() {
assert!(!make("f", 0, 0, 0).is_interesting());
assert!(make("f", 1, 0, 0).is_interesting());
assert!(make("f", 0, 1, 0).is_interesting());
assert!(make("f", 0, 0, 1).is_interesting());
let mut p = make("f", 0, 0, 0);
p.propagates_taint = true;
assert!(p.is_interesting());
}
#[test]
fn same_lang_different_namespace_no_merge() {
let a = FuncSummary {
name: "helper".into(),
file_path: "file_a.rs".into(),
lang: "rust".into(),
param_count: 0,
param_names: vec![],
source_caps: Cap::all().bits(),
sanitizer_caps: 0,
sink_caps: 0,
propagates_taint: false,
tainted_sink_params: vec![],
callees: vec![],
};
let b = FuncSummary {
name: "helper".into(),
file_path: "file_b.rs".into(),
lang: "rust".into(),
param_count: 0,
param_names: vec![],
source_caps: 0,
sanitizer_caps: 0,
sink_caps: Cap::SHELL_ESCAPE.bits(),
propagates_taint: false,
tainted_sink_params: vec![],
callees: vec![],
};
let global = merge_summaries(vec![a, b], None);
// They should be stored under different FuncKeys
let key_a = FuncKey {
lang: Lang::Rust,
namespace: "file_a.rs".into(),
name: "helper".into(),
arity: Some(0),
};
let key_b = FuncKey {
lang: Lang::Rust,
namespace: "file_b.rs".into(),
name: "helper".into(),
arity: Some(0),
};
assert!(global.get(&key_a).is_some());
assert!(global.get(&key_b).is_some());
// source_caps NOT merged
assert_eq!(global.get(&key_a).unwrap().source_caps, Cap::all().bits());
assert_eq!(global.get(&key_b).unwrap().source_caps, 0);
}
#[test]
fn same_lang_same_namespace_merges() {
let a = FuncSummary {
name: "helper".into(),
file_path: "lib.rs".into(),
lang: "rust".into(),
param_count: 0,
param_names: vec![],
source_caps: 0x01,
sanitizer_caps: 0,
sink_caps: 0,
propagates_taint: false,
tainted_sink_params: vec![],
callees: vec![],
};
let b = FuncSummary {
name: "helper".into(),
file_path: "lib.rs".into(),
lang: "rust".into(),
param_count: 0,
param_names: vec![],
source_caps: 0,
sanitizer_caps: 0x02,
sink_caps: 0,
propagates_taint: true,
tainted_sink_params: vec![],
callees: vec![],
};
let global = merge_summaries(vec![a, b], None);
let key = FuncKey {
lang: Lang::Rust,
namespace: "lib.rs".into(),
name: "helper".into(),
arity: Some(0),
};
let merged = global.get(&key).unwrap();
assert_eq!(merged.source_caps, 0x01);
assert_eq!(merged.sanitizer_caps, 0x02);
assert!(merged.propagates_taint);
}
#[test]
fn cross_lang_name_collision_stays_separate() {
let py = FuncSummary {
name: "process_data".into(),
file_path: "handler.py".into(),
lang: "python".into(),
param_count: 0,
param_names: vec![],
source_caps: Cap::all().bits(),
sanitizer_caps: 0,
sink_caps: 0,
propagates_taint: false,
tainted_sink_params: vec![],
callees: vec![],
};
let c = FuncSummary {
name: "process_data".into(),
file_path: "handler.c".into(),
lang: "c".into(),
param_count: 1,
param_names: vec!["s".into()],
source_caps: 0,
sanitizer_caps: 0,
sink_caps: 0,
propagates_taint: true,
tainted_sink_params: vec![],
callees: vec![],
};
let global = merge_summaries(vec![py, c], None);
let py_key = FuncKey {
lang: Lang::Python,
namespace: "handler.py".into(),
name: "process_data".into(),
arity: Some(0),
};
let c_key = FuncKey {
lang: Lang::C,
namespace: "handler.c".into(),
name: "process_data".into(),
arity: Some(1),
};
assert!(global.get(&py_key).is_some());
assert!(global.get(&c_key).is_some());
// Python's source_caps NOT merged into C
assert_eq!(global.get(&c_key).unwrap().source_caps, 0);
assert_eq!(global.get(&py_key).unwrap().source_caps, Cap::all().bits());
}
#[test]
fn lookup_same_lang_returns_all_matches() {
let a = FuncSummary {
name: "helper".into(),
file_path: "a.rs".into(),
lang: "rust".into(),
param_count: 0,
param_names: vec![],
source_caps: 1,
sanitizer_caps: 0,
sink_caps: 0,
propagates_taint: false,
tainted_sink_params: vec![],
callees: vec![],
};
let b = FuncSummary {
name: "helper".into(),
file_path: "b.rs".into(),
lang: "rust".into(),
param_count: 0,
param_names: vec![],
source_caps: 2,
sanitizer_caps: 0,
sink_caps: 0,
propagates_taint: false,
tainted_sink_params: vec![],
callees: vec![],
};
let global = merge_summaries(vec![a, b], None);
let matches = global.lookup_same_lang(Lang::Rust, "helper");
assert_eq!(matches.len(), 2);
// No cross-language matches
let py_matches = global.lookup_same_lang(Lang::Python, "helper");
assert!(py_matches.is_empty());
}