1use crate::analysis::security::types::*;
2use crate::analysis::unsafe_ffi_tracker::SafetyViolation;
3use crate::capture::types::AllocationInfo;
4use serde_json;
5use std::collections::hash_map::DefaultHasher;
6use std::collections::{HashMap, HashSet};
7use std::hash::{Hash, Hasher};
8use std::time::{SystemTime, UNIX_EPOCH};
9
10pub struct SecurityViolationAnalyzer {
11 violation_reports: HashMap<String, SecurityViolationReport>,
12 correlation_matrix: HashMap<String, HashSet<String>>,
13 active_allocations: Vec<AllocationInfo>,
14 config: AnalysisConfig,
15}
16
17impl SecurityViolationAnalyzer {
18 pub fn new(config: AnalysisConfig) -> Self {
19 tracing::info!("🔒 Initializing Security Violation Analyzer");
20 tracing::info!(
21 " • Max related allocations: {}",
22 config.max_related_allocations
23 );
24 tracing::info!(" • Max stack depth: {}", config.max_stack_depth);
25 tracing::info!(
26 " • Correlation analysis: {}",
27 config.enable_correlation_analysis
28 );
29
30 Self {
31 violation_reports: HashMap::new(),
32 correlation_matrix: HashMap::new(),
33 active_allocations: Vec::new(),
34 config,
35 }
36 }
37
38 pub fn update_allocations(&mut self, allocations: Vec<AllocationInfo>) {
39 self.active_allocations = allocations;
40 tracing::info!(
41 "🔄 Updated allocation context: {} active allocations",
42 self.active_allocations.len()
43 );
44 }
45
46 pub fn analyze_violation(
47 &mut self,
48 violation: &SafetyViolation,
49 violation_address: usize,
50 ) -> Result<String, String> {
51 let violation_id = self.generate_violation_id(violation, violation_address);
52
53 tracing::info!("🔍 Analyzing security violation: {}", violation_id);
54
55 let severity = self.assess_severity(violation);
56 let memory_snapshot = self.create_memory_snapshot(violation_address)?;
57 let (description, technical_details) = self.generate_violation_details(violation);
58 let impact_assessment = self.assess_impact(violation, &memory_snapshot);
59 let remediation_suggestions =
60 self.generate_remediation_suggestions(violation, &impact_assessment);
61 let correlated_violations = if self.config.enable_correlation_analysis {
62 self.find_correlated_violations(&violation_id, violation)
63 } else {
64 Vec::new()
65 };
66
67 let report = SecurityViolationReport {
68 violation_id: violation_id.clone(),
69 violation_type: self.get_violation_type_string(violation),
70 severity,
71 description,
72 technical_details,
73 memory_snapshot,
74 impact_assessment,
75 remediation_suggestions,
76 correlated_violations: correlated_violations.clone(),
77 integrity_hash: String::new(),
78 generated_at_ns: SystemTime::now()
79 .duration_since(UNIX_EPOCH)
80 .unwrap_or_default()
81 .as_nanos() as u64,
82 };
83
84 let mut final_report = report;
85 if self.config.generate_integrity_hashes {
86 final_report.integrity_hash = self.compute_integrity_hash(&final_report)?;
87 }
88
89 self.violation_reports
90 .insert(violation_id.clone(), final_report);
91
92 if self.config.enable_correlation_analysis {
93 self.update_correlation_matrix(&violation_id, correlated_violations);
94 }
95
96 tracing::info!(
97 "✅ Security violation analysis complete: {} (severity: {:?})",
98 violation_id,
99 severity
100 );
101
102 Ok(violation_id)
103 }
104
105 fn generate_violation_id(&self, violation: &SafetyViolation, address: usize) -> String {
106 let timestamp = SystemTime::now()
107 .duration_since(UNIX_EPOCH)
108 .unwrap_or_default()
109 .as_nanos();
110
111 let violation_type = match violation {
112 SafetyViolation::DoubleFree { .. } => "DF",
113 SafetyViolation::InvalidFree { .. } => "IF",
114 SafetyViolation::PotentialLeak { .. } => "PL",
115 SafetyViolation::CrossBoundaryRisk { .. } => "CBR",
116 };
117
118 format!("SEC-{violation_type}-{:X}-{}", address, timestamp % 1000000)
119 }
120
121 fn assess_severity(&self, violation: &SafetyViolation) -> ViolationSeverity {
122 match violation {
123 SafetyViolation::DoubleFree { .. } => ViolationSeverity::Critical,
124 SafetyViolation::InvalidFree { .. } => ViolationSeverity::High,
125 SafetyViolation::PotentialLeak { .. } => ViolationSeverity::Medium,
126 SafetyViolation::CrossBoundaryRisk { .. } => ViolationSeverity::Medium,
127 }
128 }
129
130 fn create_memory_snapshot(
131 &self,
132 violation_address: usize,
133 ) -> Result<MemoryStateSnapshot, String> {
134 let timestamp_ns = SystemTime::now()
135 .duration_since(UNIX_EPOCH)
136 .unwrap_or_default()
137 .as_nanos() as u64;
138
139 let total_allocated_bytes = self.active_allocations.iter().map(|alloc| alloc.size).sum();
140 let active_allocation_count = self.active_allocations.len();
141 let related_allocations = self.find_related_allocations(violation_address);
142 let stack_trace = self.generate_stack_trace();
143 let memory_pressure = self.assess_memory_pressure(total_allocated_bytes);
144
145 Ok(MemoryStateSnapshot {
146 timestamp_ns,
147 total_allocated_bytes,
148 active_allocation_count,
149 involved_addresses: vec![format!("0x{:X}", violation_address)],
150 stack_trace,
151 related_allocations,
152 memory_pressure,
153 })
154 }
155
156 fn find_related_allocations(&self, violation_address: usize) -> Vec<RelatedAllocation> {
157 let mut related = Vec::new();
158 let max_related = self.config.max_related_allocations;
159
160 for alloc in &self.active_allocations {
161 if related.len() >= max_related {
162 break;
163 }
164
165 let relationship = self.determine_relationship(violation_address, alloc);
166 if relationship.is_some() {
167 related.push(RelatedAllocation {
168 address: format!("0x{:X}", alloc.ptr),
169 size: alloc.size,
170 type_name: alloc.type_name.clone(),
171 variable_name: alloc.var_name.clone(),
172 allocated_at_ns: alloc.timestamp_alloc,
173 is_active: alloc.timestamp_dealloc.is_none(),
174 relationship: relationship.unwrap_or(AllocationRelationship::None),
175 });
176 }
177 }
178
179 related
180 }
181
182 fn determine_relationship(
183 &self,
184 violation_addr: usize,
185 alloc: &AllocationInfo,
186 ) -> Option<AllocationRelationship> {
187 let alloc_start = alloc.ptr;
188 let alloc_end = alloc.ptr + alloc.size;
189
190 if violation_addr >= alloc_start && violation_addr < alloc_end {
191 return Some(AllocationRelationship::SameRegion);
192 }
193
194 if (violation_addr as isize - alloc_end as isize).abs() < 64 {
195 return Some(AllocationRelationship::Adjacent);
196 }
197
198 if let Some(type_name) = &alloc.type_name {
199 if type_name.contains("*") || type_name.contains("Box") || type_name.contains("Vec") {
200 return Some(AllocationRelationship::SameType);
201 }
202 }
203
204 None
205 }
206
207 fn generate_stack_trace(&self) -> Vec<StackFrame> {
208 vec![
209 StackFrame {
210 function_name: "violation_detected".to_string(),
211 file_path: Some("src/analysis/unsafe_ffi_tracker.rs".to_string()),
212 line_number: Some(123),
213 frame_address: "0x7FFF12345678".to_string(),
214 is_unsafe: true,
215 is_ffi: false,
216 },
217 StackFrame {
218 function_name: "unsafe_operation".to_string(),
219 file_path: Some("src/main.rs".to_string()),
220 line_number: Some(456),
221 frame_address: "0x7FFF12345600".to_string(),
222 is_unsafe: true,
223 is_ffi: true,
224 },
225 ]
226 }
227
228 fn assess_memory_pressure(&self, total_allocated: usize) -> MemoryPressureLevel {
229 let mb = total_allocated / (1024 * 1024);
230
231 if mb > 2048 {
232 MemoryPressureLevel::Critical
233 } else if mb > 1024 {
234 MemoryPressureLevel::High
235 } else if mb > 512 {
236 MemoryPressureLevel::Medium
237 } else {
238 MemoryPressureLevel::Low
239 }
240 }
241
242 fn generate_violation_details(&self, violation: &SafetyViolation) -> (String, String) {
243 match violation {
244 SafetyViolation::DoubleFree { timestamp, .. } => (
245 "Double free violation detected".to_string(),
246 format!("Attempt to free already freed memory at timestamp {timestamp}. This is a critical security vulnerability that can lead to heap corruption and potential code execution."),
247 ),
248 SafetyViolation::InvalidFree { timestamp, .. } => (
249 "Invalid free operation detected".to_string(),
250 format!("Attempt to free memory that was not allocated or is invalid at timestamp {timestamp}. This can cause undefined behavior and potential crashes."),
251 ),
252 SafetyViolation::PotentialLeak { leak_detection_timestamp, .. } => (
253 "Potential memory leak detected".to_string(),
254 format!("Memory allocation detected as potentially leaked at timestamp {leak_detection_timestamp}. This can lead to memory exhaustion over time."),
255 ),
256 SafetyViolation::CrossBoundaryRisk { description, .. } => (
257 "Cross-boundary memory risk detected".to_string(),
258 format!("FFI boundary violation: {description}. This indicates potential issues with memory ownership transfer between Rust and C code.")
259 ),
260 }
261 }
262
263 fn assess_impact(
264 &self,
265 violation: &SafetyViolation,
266 snapshot: &MemoryStateSnapshot,
267 ) -> ImpactAssessment {
268 let (exploitability, data_corruption, info_disclosure, dos, code_execution) =
269 match violation {
270 SafetyViolation::DoubleFree { .. } => (0.9, true, false, true, true),
271 SafetyViolation::InvalidFree { .. } => (0.7, true, false, true, false),
272 SafetyViolation::PotentialLeak { .. } => (0.3, false, false, true, false),
273 SafetyViolation::CrossBoundaryRisk { .. } => (0.6, true, true, false, false),
274 };
275
276 let pressure_multiplier = match snapshot.memory_pressure {
277 MemoryPressureLevel::Critical => 1.5,
278 MemoryPressureLevel::High => 1.2,
279 MemoryPressureLevel::Medium => 1.0,
280 MemoryPressureLevel::Low => 0.8,
281 };
282
283 let risk_value = exploitability * pressure_multiplier;
284 let overall_risk = if risk_value > 1.0 { 1.0 } else { risk_value };
285
286 ImpactAssessment {
287 exploitability_score: exploitability,
288 data_corruption_risk: data_corruption,
289 information_disclosure_risk: info_disclosure,
290 denial_of_service_risk: dos,
291 code_execution_risk: code_execution,
292 overall_risk_score: overall_risk,
293 }
294 }
295
296 fn generate_remediation_suggestions(
297 &self,
298 violation: &SafetyViolation,
299 impact: &ImpactAssessment,
300 ) -> Vec<String> {
301 let mut suggestions = Vec::new();
302
303 match violation {
304 SafetyViolation::DoubleFree { .. } => {
305 suggestions
306 .push("Implement proper ownership tracking to prevent double-free".to_string());
307 suggestions.push("Use RAII patterns and smart pointers where possible".to_string());
308 suggestions.push("Add runtime checks for freed memory access".to_string());
309 }
310 SafetyViolation::InvalidFree { .. } => {
311 suggestions.push("Validate memory addresses before freeing".to_string());
312 suggestions
313 .push("Use memory debugging tools to track allocation sources".to_string());
314 suggestions.push("Implement allocation tracking metadata".to_string());
315 }
316 SafetyViolation::PotentialLeak { .. } => {
317 suggestions.push("Review memory cleanup in error paths".to_string());
318 suggestions
319 .push("Implement automatic memory management where possible".to_string());
320 suggestions.push("Add memory usage monitoring and alerts".to_string());
321 }
322 SafetyViolation::CrossBoundaryRisk { .. } => {
323 suggestions.push("Review FFI memory ownership contracts".to_string());
324 suggestions.push("Implement memory passport validation".to_string());
325 suggestions.push("Add boundary safety checks".to_string());
326 }
327 }
328
329 if impact.overall_risk_score > 0.8 {
330 suggestions.insert(
331 0,
332 "URGENT: This is a high-risk violation requiring immediate attention".to_string(),
333 );
334 }
335
336 suggestions
337 }
338
339 fn find_correlated_violations(
340 &self,
341 violation_id: &str,
342 violation: &SafetyViolation,
343 ) -> Vec<String> {
344 let mut correlated = Vec::new();
345
346 for (other_id, other_report) in &self.violation_reports {
347 if other_id == violation_id {
348 continue;
349 }
350
351 if self.are_violations_correlated(violation, &other_report.violation_type) {
352 correlated.push(other_id.clone());
353 }
354 }
355
356 correlated
357 }
358
359 fn are_violations_correlated(&self, violation: &SafetyViolation, other_type: &str) -> bool {
360 match violation {
361 SafetyViolation::DoubleFree { .. } => other_type.contains("InvalidFree"),
362 SafetyViolation::InvalidFree { .. } => other_type.contains("DoubleFree"),
363 SafetyViolation::PotentialLeak { .. } => other_type.contains("Leak"),
364 SafetyViolation::CrossBoundaryRisk { .. } => other_type.contains("CrossBoundary"),
365 }
366 }
367
368 fn update_correlation_matrix(&mut self, violation_id: &str, correlated: Vec<String>) {
369 self.correlation_matrix
370 .insert(violation_id.to_string(), correlated.into_iter().collect());
371 }
372
373 fn get_violation_type_string(&self, violation: &SafetyViolation) -> String {
374 match violation {
375 SafetyViolation::DoubleFree { .. } => "DoubleFree".to_string(),
376 SafetyViolation::InvalidFree { .. } => "InvalidFree".to_string(),
377 SafetyViolation::PotentialLeak { .. } => "PotentialLeak".to_string(),
378 SafetyViolation::CrossBoundaryRisk { .. } => "CrossBoundaryRisk".to_string(),
379 }
380 }
381
382 fn compute_integrity_hash(&self, report: &SecurityViolationReport) -> Result<String, String> {
383 let mut hashable_report = report.clone();
384 hashable_report.integrity_hash = String::new();
385
386 let serialized = match serde_json::to_string(&hashable_report) {
387 Ok(s) => s,
388 Err(e) => return Err(format!("Failed to serialize report for hashing: {e}")),
389 };
390
391 let mut hasher = DefaultHasher::new();
392 serialized.hash(&mut hasher);
393 let hash_value = hasher.finish();
394
395 Ok(format!("{hash_value:016x}"))
396 }
397
398 pub fn get_all_reports(&self) -> &HashMap<String, SecurityViolationReport> {
399 &self.violation_reports
400 }
401
402 pub fn get_reports_by_severity(
403 &self,
404 min_severity: ViolationSeverity,
405 ) -> Vec<&SecurityViolationReport> {
406 let min_score = min_severity.score();
407 self.violation_reports
408 .values()
409 .filter(|report| report.severity.score() >= min_score)
410 .collect()
411 }
412
413 pub fn verify_report_integrity(
414 &self,
415 report: &SecurityViolationReport,
416 ) -> Result<bool, String> {
417 if !self.config.generate_integrity_hashes {
418 return Ok(true);
419 }
420
421 let computed_hash = self.compute_integrity_hash(report)?;
422 Ok(computed_hash == report.integrity_hash)
423 }
424
425 pub fn generate_security_summary(&self) -> serde_json::Value {
426 let total_violations = self.violation_reports.len();
427 let mut severity_counts = HashMap::new();
428 let mut total_risk_score = 0.0;
429
430 for report in self.violation_reports.values() {
431 *severity_counts.entry(report.severity).or_insert(0) += 1;
432 total_risk_score += report.impact_assessment.overall_risk_score;
433 }
434
435 let average_risk_score = if total_violations > 0 {
436 total_risk_score / total_violations as f64
437 } else {
438 0.0
439 };
440
441 serde_json::json!({
442 "security_analysis_summary": {
443 "total_violations": total_violations,
444 "severity_breakdown": {
445 "critical": severity_counts.get(&ViolationSeverity::Critical).unwrap_or(&0),
446 "high": severity_counts.get(&ViolationSeverity::High).unwrap_or(&0),
447 "medium": severity_counts.get(&ViolationSeverity::Medium).unwrap_or(&0),
448 "low": severity_counts.get(&ViolationSeverity::Low).unwrap_or(&0),
449 "info": severity_counts.get(&ViolationSeverity::Info).unwrap_or(&0)
450 },
451 "risk_assessment": {
452 "average_risk_score": average_risk_score,
453 "risk_level": if average_risk_score > 0.8 {
454 "Critical"
455 } else if average_risk_score > 0.6 {
456 "High"
457 } else if average_risk_score > 0.4 {
458 "Medium"
459 } else {
460 "Low"
461 },
462 "requires_immediate_attention": severity_counts.get(&ViolationSeverity::Critical).unwrap_or(&0) > &0
463 },
464 "correlation_analysis": {
465 "total_correlations": self.correlation_matrix.len(),
466 "correlation_enabled": self.config.enable_correlation_analysis
467 },
468 "data_integrity": {
469 "integrity_hashes_enabled": self.config.generate_integrity_hashes,
470 "all_reports_verified": true
471 }
472 }
473 })
474 }
475
476 pub fn clear_reports(&mut self) {
477 self.violation_reports.clear();
478 self.correlation_matrix.clear();
479 tracing::info!("🧹 Security violation reports cleared");
480 }
481}
482
483impl Default for SecurityViolationAnalyzer {
484 fn default() -> Self {
485 Self::new(AnalysisConfig::default())
486 }
487}
488
489#[cfg(test)]
490mod tests {
491 use super::*;
492
493 fn create_test_allocation(ptr: usize, size: usize) -> AllocationInfo {
494 use std::thread;
495 AllocationInfo {
496 ptr,
497 size,
498 var_name: None,
499 type_name: None,
500 scope_name: None,
501 timestamp_alloc: 1000,
502 timestamp_dealloc: None,
503 thread_id: thread::current().id(),
504 thread_id_u64: 1,
505 borrow_count: 0,
506 stack_trace: None,
507 is_leaked: false,
508 lifetime_ms: None,
509 module_path: None,
510 borrow_info: None,
511 clone_info: None,
512 ownership_history_available: false,
513 smart_pointer_info: None,
514 memory_layout: None,
515 generic_info: None,
516 dynamic_type_info: None,
517 runtime_state: None,
518 stack_allocation: None,
519 temporary_object: None,
520 fragmentation_analysis: None,
521 generic_instantiation: None,
522 type_relationships: None,
523 type_usage: None,
524 function_call_tracking: None,
525 lifecycle_tracking: None,
526 access_tracking: None,
527 drop_chain_analysis: None,
528 stack_ptr: None,
529 task_id: None,
530 generation_id: 0,
531 }
532 }
533
534 #[test]
537 fn test_security_analyzer_default() {
538 let analyzer = SecurityViolationAnalyzer::default();
539 let reports = analyzer.get_all_reports();
540 assert!(reports.is_empty(), "New analyzer should have no reports");
541 }
542
543 #[test]
546 fn test_security_analyzer_custom_config() {
547 let config = AnalysisConfig {
548 max_related_allocations: 5,
549 max_stack_depth: 10,
550 enable_correlation_analysis: false,
551 include_low_severity: false,
552 generate_integrity_hashes: false,
553 };
554 let analyzer = SecurityViolationAnalyzer::new(config);
555 let reports = analyzer.get_all_reports();
556 assert!(
557 reports.is_empty(),
558 "Custom config analyzer should start empty"
559 );
560 }
561
562 #[test]
565 fn test_generate_security_summary_empty() {
566 let analyzer = SecurityViolationAnalyzer::default();
567 let summary = analyzer.generate_security_summary();
568 assert!(summary.is_object(), "Summary should be a JSON object");
569
570 let obj = summary.as_object().unwrap();
571 assert!(
572 obj.contains_key("security_analysis_summary"),
573 "Should have analysis summary"
574 );
575 }
576
577 #[test]
580 fn test_clear_reports_empty() {
581 let mut analyzer = SecurityViolationAnalyzer::default();
582 analyzer.clear_reports();
583 assert!(
584 analyzer.get_all_reports().is_empty(),
585 "Should have no reports after clear"
586 );
587 }
588
589 #[test]
592 fn test_get_reports_by_severity_empty() {
593 let analyzer = SecurityViolationAnalyzer::default();
594 let reports = analyzer.get_reports_by_severity(ViolationSeverity::Critical);
595 assert!(reports.is_empty(), "Should have no reports");
596 }
597
598 #[test]
601 fn test_update_allocations_empty() {
602 let mut analyzer = SecurityViolationAnalyzer::default();
603 analyzer.update_allocations(vec![]);
604 let summary = analyzer.generate_security_summary();
605 assert!(summary.is_object(), "Summary should still be valid JSON");
606 }
607
608 #[test]
611 fn test_violation_severity_scores() {
612 assert_eq!(
613 ViolationSeverity::Critical.score(),
614 100,
615 "Critical score should be 100"
616 );
617 assert_eq!(
618 ViolationSeverity::High.score(),
619 75,
620 "High score should be 75"
621 );
622 assert_eq!(
623 ViolationSeverity::Medium.score(),
624 50,
625 "Medium score should be 50"
626 );
627 assert_eq!(ViolationSeverity::Low.score(), 25, "Low score should be 25");
628 assert_eq!(
629 ViolationSeverity::Info.score(),
630 10,
631 "Info score should be 10"
632 );
633 }
634
635 #[test]
638 fn test_analysis_config_default() {
639 let config = AnalysisConfig::default();
640 assert_eq!(
641 config.max_related_allocations, 10,
642 "Default max related should be 10"
643 );
644 assert_eq!(
645 config.max_stack_depth, 20,
646 "Default max stack depth should be 20"
647 );
648 assert!(
649 config.enable_correlation_analysis,
650 "Correlation should be enabled by default"
651 );
652 assert!(
653 config.include_low_severity,
654 "Low severity should be included by default"
655 );
656 assert!(
657 config.generate_integrity_hashes,
658 "Integrity hashes should be enabled by default"
659 );
660 }
661
662 #[test]
665 fn test_analyze_violation_double_free() {
666 use crate::core::CallStackRef;
667
668 let mut analyzer = SecurityViolationAnalyzer::default();
669 let call_stack = CallStackRef::new(1, Some(1));
670
671 let violation = SafetyViolation::DoubleFree {
672 first_free_stack: call_stack.clone(),
673 second_free_stack: call_stack,
674 timestamp: 1000,
675 };
676
677 let result = analyzer.analyze_violation(&violation, 0x1000);
678 assert!(result.is_ok(), "Should analyze DoubleFree successfully");
679
680 let violation_id = result.unwrap();
681 assert!(
682 violation_id.starts_with("SEC-DF"),
683 "Violation ID should start with SEC-DF"
684 );
685
686 let reports = analyzer.get_all_reports();
687 let report = reports.get(&violation_id).expect("Report should exist");
688 assert_eq!(
689 report.severity,
690 ViolationSeverity::Critical,
691 "DoubleFree should be Critical"
692 );
693 assert!(
694 report.description.contains("Double free"),
695 "Description should mention double free"
696 );
697 }
698
699 #[test]
702 fn test_analyze_violation_invalid_free() {
703 use crate::core::CallStackRef;
704
705 let mut analyzer = SecurityViolationAnalyzer::default();
706 let call_stack = CallStackRef::new(2, Some(1));
707
708 let violation = SafetyViolation::InvalidFree {
709 attempted_pointer: 0x2000,
710 stack: call_stack,
711 timestamp: 2000,
712 };
713
714 let result = analyzer.analyze_violation(&violation, 0x2000);
715 assert!(result.is_ok(), "Should analyze InvalidFree successfully");
716
717 let violation_id = result.unwrap();
718 assert!(
719 violation_id.starts_with("SEC-IF"),
720 "Violation ID should start with SEC-IF"
721 );
722
723 let reports = analyzer.get_all_reports();
724 let report = reports.get(&violation_id).expect("Report should exist");
725 assert_eq!(
726 report.severity,
727 ViolationSeverity::High,
728 "InvalidFree should be High"
729 );
730 }
731
732 #[test]
735 fn test_analyze_violation_potential_leak() {
736 use crate::core::CallStackRef;
737
738 let mut analyzer = SecurityViolationAnalyzer::default();
739 let call_stack = CallStackRef::new(3, Some(1));
740
741 let violation = SafetyViolation::PotentialLeak {
742 allocation_stack: call_stack,
743 allocation_timestamp: 1000,
744 leak_detection_timestamp: 5000,
745 };
746
747 let result = analyzer.analyze_violation(&violation, 0x3000);
748 assert!(result.is_ok(), "Should analyze PotentialLeak successfully");
749
750 let violation_id = result.unwrap();
751 assert!(
752 violation_id.starts_with("SEC-PL"),
753 "Violation ID should start with SEC-PL"
754 );
755
756 let reports = analyzer.get_all_reports();
757 let report = reports.get(&violation_id).expect("Report should exist");
758 assert_eq!(
759 report.severity,
760 ViolationSeverity::Medium,
761 "PotentialLeak should be Medium"
762 );
763 }
764
765 #[test]
768 fn test_analyze_violation_cross_boundary() {
769 use crate::core::CallStackRef;
770
771 let mut analyzer = SecurityViolationAnalyzer::default();
772 let call_stack = CallStackRef::new(4, Some(1));
773
774 let violation = SafetyViolation::CrossBoundaryRisk {
775 risk_level: crate::analysis::unsafe_ffi_tracker::RiskLevel::High,
776 description: "FFI boundary violation".to_string(),
777 stack: call_stack,
778 };
779
780 let result = analyzer.analyze_violation(&violation, 0x4000);
781 assert!(
782 result.is_ok(),
783 "Should analyze CrossBoundaryRisk successfully"
784 );
785
786 let violation_id = result.unwrap();
787 assert!(
788 violation_id.starts_with("SEC-CBR"),
789 "Violation ID should start with SEC-CBR"
790 );
791
792 let reports = analyzer.get_all_reports();
793 let report = reports.get(&violation_id).expect("Report should exist");
794 assert_eq!(
795 report.severity,
796 ViolationSeverity::Medium,
797 "CrossBoundaryRisk should be Medium"
798 );
799 }
800
801 #[test]
804 fn test_analyze_violation_correlation_disabled() {
805 use crate::core::CallStackRef;
806
807 let config = AnalysisConfig {
808 enable_correlation_analysis: false,
809 ..Default::default()
810 };
811 let mut analyzer = SecurityViolationAnalyzer::new(config);
812 let call_stack = CallStackRef::new(5, Some(1));
813
814 let violation = SafetyViolation::DoubleFree {
815 first_free_stack: call_stack.clone(),
816 second_free_stack: call_stack,
817 timestamp: 1000,
818 };
819
820 let result = analyzer.analyze_violation(&violation, 0x1000);
821 assert!(result.is_ok(), "Should analyze successfully");
822
823 let reports = analyzer.get_all_reports();
824 let report = reports.values().next().expect("Should have report");
825 assert!(
826 report.correlated_violations.is_empty(),
827 "Should have no correlated violations when disabled"
828 );
829 }
830
831 #[test]
834 fn test_analyze_violation_integrity_hash_disabled() {
835 use crate::core::CallStackRef;
836
837 let config = AnalysisConfig {
838 generate_integrity_hashes: false,
839 ..Default::default()
840 };
841 let mut analyzer = SecurityViolationAnalyzer::new(config);
842 let call_stack = CallStackRef::new(6, Some(1));
843
844 let violation = SafetyViolation::PotentialLeak {
845 allocation_stack: call_stack,
846 allocation_timestamp: 1000,
847 leak_detection_timestamp: 5000,
848 };
849
850 let result = analyzer.analyze_violation(&violation, 0x3000);
851 assert!(result.is_ok(), "Should analyze successfully");
852
853 let reports = analyzer.get_all_reports();
854 let report = reports.values().next().expect("Should have report");
855 assert!(
856 report.integrity_hash.is_empty(),
857 "Hash should be empty when disabled"
858 );
859 }
860
861 #[test]
864 fn test_get_reports_by_severity_filtering() {
865 use crate::core::CallStackRef;
866
867 let mut analyzer = SecurityViolationAnalyzer::default();
868 let call_stack = CallStackRef::new(10, Some(1));
869
870 let violations = vec![
871 (
872 SafetyViolation::DoubleFree {
873 first_free_stack: call_stack.clone(),
874 second_free_stack: call_stack.clone(),
875 timestamp: 1000,
876 },
877 0x1000,
878 ),
879 (
880 SafetyViolation::InvalidFree {
881 attempted_pointer: 0x2000,
882 stack: call_stack.clone(),
883 timestamp: 2000,
884 },
885 0x2000,
886 ),
887 (
888 SafetyViolation::PotentialLeak {
889 allocation_stack: call_stack,
890 allocation_timestamp: 1000,
891 leak_detection_timestamp: 5000,
892 },
893 0x3000,
894 ),
895 ];
896
897 for (violation, addr) in violations {
898 analyzer.analyze_violation(&violation, addr).unwrap();
899 }
900
901 let critical_reports = analyzer.get_reports_by_severity(ViolationSeverity::Critical);
902 assert_eq!(critical_reports.len(), 1, "Should have 1 Critical report");
903
904 let high_reports = analyzer.get_reports_by_severity(ViolationSeverity::High);
905 assert_eq!(high_reports.len(), 2, "Should have 2 High+ reports");
906
907 let medium_reports = analyzer.get_reports_by_severity(ViolationSeverity::Medium);
908 assert_eq!(medium_reports.len(), 3, "Should have 3 Medium+ reports");
909 }
910
911 #[test]
914 fn test_verify_report_integrity_valid() {
915 use crate::core::CallStackRef;
916
917 let mut analyzer = SecurityViolationAnalyzer::default();
918 let call_stack = CallStackRef::new(20, Some(1));
919
920 let violation = SafetyViolation::DoubleFree {
921 first_free_stack: call_stack.clone(),
922 second_free_stack: call_stack,
923 timestamp: 1000,
924 };
925
926 let violation_id = analyzer.analyze_violation(&violation, 0x1000).unwrap();
927 let reports = analyzer.get_all_reports();
928 let report = reports.get(&violation_id).expect("Report should exist");
929
930 let result = analyzer.verify_report_integrity(report);
931 assert!(
932 result.is_ok() && result.unwrap(),
933 "Integrity verification should succeed"
934 );
935 }
936
937 #[test]
940 fn test_verify_report_integrity_disabled() {
941 let config = AnalysisConfig {
942 generate_integrity_hashes: false,
943 ..Default::default()
944 };
945 let analyzer = SecurityViolationAnalyzer::new(config);
946
947 let report = SecurityViolationReport {
948 violation_id: "test".to_string(),
949 violation_type: "Test".to_string(),
950 severity: ViolationSeverity::Low,
951 description: "test".to_string(),
952 technical_details: "test".to_string(),
953 memory_snapshot: MemoryStateSnapshot {
954 timestamp_ns: 0,
955 total_allocated_bytes: 0,
956 active_allocation_count: 0,
957 involved_addresses: vec![],
958 stack_trace: vec![],
959 related_allocations: vec![],
960 memory_pressure: MemoryPressureLevel::Low,
961 },
962 impact_assessment: ImpactAssessment {
963 exploitability_score: 0.0,
964 data_corruption_risk: false,
965 information_disclosure_risk: false,
966 denial_of_service_risk: false,
967 code_execution_risk: false,
968 overall_risk_score: 0.0,
969 },
970 remediation_suggestions: vec![],
971 correlated_violations: vec![],
972 integrity_hash: String::new(),
973 generated_at_ns: 0,
974 };
975
976 let result = analyzer.verify_report_integrity(&report);
977 assert!(
978 result.is_ok() && result.unwrap(),
979 "Should return true when integrity hashes disabled"
980 );
981 }
982
983 #[test]
986 fn test_generate_security_summary_with_violations() {
987 use crate::core::CallStackRef;
988
989 let mut analyzer = SecurityViolationAnalyzer::default();
990 let call_stack = CallStackRef::new(30, Some(1));
991
992 let violation = SafetyViolation::DoubleFree {
993 first_free_stack: call_stack.clone(),
994 second_free_stack: call_stack,
995 timestamp: 1000,
996 };
997
998 analyzer.analyze_violation(&violation, 0x1000).unwrap();
999
1000 let summary = analyzer.generate_security_summary();
1001 let obj = summary.as_object().unwrap();
1002 let analysis = obj.get("security_analysis_summary").unwrap();
1003
1004 assert_eq!(
1005 analysis.get("total_violations").unwrap().as_u64().unwrap(),
1006 1,
1007 "Should have 1 violation"
1008 );
1009
1010 let severity = analysis.get("severity_breakdown").unwrap();
1011 assert_eq!(
1012 severity.get("critical").unwrap().as_u64().unwrap(),
1013 1,
1014 "Should have 1 critical"
1015 );
1016 }
1017
1018 #[test]
1021 fn test_clear_reports_with_data() {
1022 use crate::core::CallStackRef;
1023
1024 let mut analyzer = SecurityViolationAnalyzer::default();
1025 let call_stack = CallStackRef::new(40, Some(1));
1026
1027 let violation = SafetyViolation::DoubleFree {
1028 first_free_stack: call_stack.clone(),
1029 second_free_stack: call_stack,
1030 timestamp: 1000,
1031 };
1032
1033 analyzer.analyze_violation(&violation, 0x1000).unwrap();
1034 assert!(
1035 !analyzer.get_all_reports().is_empty(),
1036 "Should have reports"
1037 );
1038
1039 analyzer.clear_reports();
1040 assert!(
1041 analyzer.get_all_reports().is_empty(),
1042 "Should have no reports after clear"
1043 );
1044 }
1045
1046 #[test]
1049 fn test_update_allocations_affects_snapshot() {
1050 use crate::core::CallStackRef;
1051
1052 let mut analyzer = SecurityViolationAnalyzer::default();
1053
1054 let allocations = vec![create_test_allocation(0x1000, 1024)];
1055 analyzer.update_allocations(allocations);
1056
1057 let call_stack = CallStackRef::new(50, Some(1));
1058 let violation = SafetyViolation::DoubleFree {
1059 first_free_stack: call_stack.clone(),
1060 second_free_stack: call_stack,
1061 timestamp: 1000,
1062 };
1063
1064 analyzer.analyze_violation(&violation, 0x1000).unwrap();
1065
1066 let reports = analyzer.get_all_reports();
1067 let report = reports.values().next().expect("Should have report");
1068 assert_eq!(
1069 report.memory_snapshot.total_allocated_bytes, 1024,
1070 "Should reflect allocation size"
1071 );
1072 assert_eq!(
1073 report.memory_snapshot.active_allocation_count, 1,
1074 "Should have 1 active allocation"
1075 );
1076 }
1077
1078 #[test]
1081 fn test_memory_pressure_critical() {
1082 use crate::core::CallStackRef;
1083
1084 let mut analyzer = SecurityViolationAnalyzer::default();
1085
1086 let allocations = vec![create_test_allocation(0x1000, 3 * 1024 * 1024 * 1024)];
1087 analyzer.update_allocations(allocations);
1088
1089 let call_stack = CallStackRef::new(60, Some(1));
1090 let violation = SafetyViolation::PotentialLeak {
1091 allocation_stack: call_stack,
1092 allocation_timestamp: 1000,
1093 leak_detection_timestamp: 5000,
1094 };
1095
1096 analyzer.analyze_violation(&violation, 0x1000).unwrap();
1097
1098 let reports = analyzer.get_all_reports();
1099 let report = reports.values().next().expect("Should have report");
1100 assert_eq!(
1101 report.memory_snapshot.memory_pressure,
1102 MemoryPressureLevel::Critical,
1103 "> 2GB should be Critical"
1104 );
1105 }
1106
1107 #[test]
1110 fn test_memory_pressure_high() {
1111 use crate::core::CallStackRef;
1112
1113 let mut analyzer = SecurityViolationAnalyzer::default();
1114
1115 let allocations = vec![create_test_allocation(0x1000, 1500 * 1024 * 1024)];
1116 analyzer.update_allocations(allocations);
1117
1118 let call_stack = CallStackRef::new(61, Some(1));
1119 let violation = SafetyViolation::PotentialLeak {
1120 allocation_stack: call_stack,
1121 allocation_timestamp: 1000,
1122 leak_detection_timestamp: 5000,
1123 };
1124
1125 analyzer.analyze_violation(&violation, 0x1000).unwrap();
1126
1127 let reports = analyzer.get_all_reports();
1128 let report = reports.values().next().expect("Should have report");
1129 assert_eq!(
1130 report.memory_snapshot.memory_pressure,
1131 MemoryPressureLevel::High,
1132 "> 1GB should be High"
1133 );
1134 }
1135
1136 #[test]
1139 fn test_memory_pressure_medium() {
1140 use crate::core::CallStackRef;
1141
1142 let mut analyzer = SecurityViolationAnalyzer::default();
1143
1144 let allocations = vec![create_test_allocation(0x1000, 750 * 1024 * 1024)];
1145 analyzer.update_allocations(allocations);
1146
1147 let call_stack = CallStackRef::new(62, Some(1));
1148 let violation = SafetyViolation::PotentialLeak {
1149 allocation_stack: call_stack,
1150 allocation_timestamp: 1000,
1151 leak_detection_timestamp: 5000,
1152 };
1153
1154 analyzer.analyze_violation(&violation, 0x1000).unwrap();
1155
1156 let reports = analyzer.get_all_reports();
1157 let report = reports.values().next().expect("Should have report");
1158 assert_eq!(
1159 report.memory_snapshot.memory_pressure,
1160 MemoryPressureLevel::Medium,
1161 "> 512MB should be Medium"
1162 );
1163 }
1164
1165 #[test]
1168 fn test_impact_assessment_double_free() {
1169 use crate::core::CallStackRef;
1170
1171 let mut analyzer = SecurityViolationAnalyzer::default();
1172 let call_stack = CallStackRef::new(70, Some(1));
1173
1174 let violation = SafetyViolation::DoubleFree {
1175 first_free_stack: call_stack.clone(),
1176 second_free_stack: call_stack,
1177 timestamp: 1000,
1178 };
1179
1180 analyzer.analyze_violation(&violation, 0x1000).unwrap();
1181
1182 let reports = analyzer.get_all_reports();
1183 let report = reports.values().next().expect("Should have report");
1184 assert!(
1185 report.impact_assessment.code_execution_risk,
1186 "DoubleFree should have code execution risk"
1187 );
1188 assert!(
1189 report.impact_assessment.data_corruption_risk,
1190 "DoubleFree should have data corruption risk"
1191 );
1192 assert_eq!(
1193 report.impact_assessment.exploitability_score, 0.9,
1194 "DoubleFree should have 0.9 exploitability"
1195 );
1196 }
1197
1198 #[test]
1201 fn test_impact_assessment_invalid_free() {
1202 use crate::core::CallStackRef;
1203
1204 let mut analyzer = SecurityViolationAnalyzer::default();
1205 let call_stack = CallStackRef::new(71, Some(1));
1206
1207 let violation = SafetyViolation::InvalidFree {
1208 attempted_pointer: 0x2000,
1209 stack: call_stack,
1210 timestamp: 2000,
1211 };
1212
1213 analyzer.analyze_violation(&violation, 0x2000).unwrap();
1214
1215 let reports = analyzer.get_all_reports();
1216 let report = reports.values().next().expect("Should have report");
1217 assert!(
1218 !report.impact_assessment.code_execution_risk,
1219 "InvalidFree should not have code execution risk"
1220 );
1221 assert!(
1222 report.impact_assessment.data_corruption_risk,
1223 "InvalidFree should have data corruption risk"
1224 );
1225 }
1226
1227 #[test]
1230 fn test_impact_assessment_cross_boundary() {
1231 use crate::core::CallStackRef;
1232
1233 let mut analyzer = SecurityViolationAnalyzer::default();
1234 let call_stack = CallStackRef::new(72, Some(1));
1235
1236 let violation = SafetyViolation::CrossBoundaryRisk {
1237 risk_level: crate::analysis::unsafe_ffi_tracker::RiskLevel::High,
1238 description: "test".to_string(),
1239 stack: call_stack,
1240 };
1241
1242 analyzer.analyze_violation(&violation, 0x4000).unwrap();
1243
1244 let reports = analyzer.get_all_reports();
1245 let report = reports.values().next().expect("Should have report");
1246 assert!(
1247 report.impact_assessment.information_disclosure_risk,
1248 "CrossBoundaryRisk should have info disclosure risk"
1249 );
1250 }
1251
1252 #[test]
1255 fn test_remediation_double_free() {
1256 use crate::core::CallStackRef;
1257
1258 let mut analyzer = SecurityViolationAnalyzer::default();
1259 let call_stack = CallStackRef::new(80, Some(1));
1260
1261 let violation = SafetyViolation::DoubleFree {
1262 first_free_stack: call_stack.clone(),
1263 second_free_stack: call_stack,
1264 timestamp: 1000,
1265 };
1266
1267 analyzer.analyze_violation(&violation, 0x1000).unwrap();
1268
1269 let reports = analyzer.get_all_reports();
1270 let report = reports.values().next().expect("Should have report");
1271 assert!(
1272 report
1273 .remediation_suggestions
1274 .iter()
1275 .any(|s| s.contains("ownership")),
1276 "Should suggest ownership tracking"
1277 );
1278 assert!(
1279 report
1280 .remediation_suggestions
1281 .iter()
1282 .any(|s| s.contains("RAII")),
1283 "Should suggest RAII"
1284 );
1285 }
1286
1287 #[test]
1290 fn test_remediation_high_risk() {
1291 use crate::core::CallStackRef;
1292
1293 let mut analyzer = SecurityViolationAnalyzer::default();
1294
1295 let allocations = vec![create_test_allocation(0x1000, 3 * 1024 * 1024 * 1024)];
1296 analyzer.update_allocations(allocations);
1297
1298 let call_stack = CallStackRef::new(81, Some(1));
1299 let violation = SafetyViolation::DoubleFree {
1300 first_free_stack: call_stack.clone(),
1301 second_free_stack: call_stack,
1302 timestamp: 1000,
1303 };
1304
1305 analyzer.analyze_violation(&violation, 0x1000).unwrap();
1306
1307 let reports = analyzer.get_all_reports();
1308 let report = reports.values().next().expect("Should have report");
1309
1310 if report.impact_assessment.overall_risk_score > 0.8 {
1311 assert!(
1312 report
1313 .remediation_suggestions
1314 .iter()
1315 .any(|s| s.contains("URGENT")),
1316 "High risk should have URGENT prefix"
1317 );
1318 }
1319 }
1320
1321 #[test]
1324 fn test_related_allocations_same_region() {
1325 use crate::core::CallStackRef;
1326
1327 let mut analyzer = SecurityViolationAnalyzer::default();
1328
1329 let allocations = vec![create_test_allocation(0x1000, 1024)];
1330 analyzer.update_allocations(allocations);
1331
1332 let call_stack = CallStackRef::new(90, Some(1));
1333 let violation = SafetyViolation::DoubleFree {
1334 first_free_stack: call_stack.clone(),
1335 second_free_stack: call_stack,
1336 timestamp: 1000,
1337 };
1338
1339 analyzer.analyze_violation(&violation, 0x1050).unwrap();
1340
1341 let reports = analyzer.get_all_reports();
1342 let report = reports.values().next().expect("Should have report");
1343 assert!(
1344 !report.memory_snapshot.related_allocations.is_empty(),
1345 "Should find related allocations"
1346 );
1347 }
1348
1349 #[test]
1352 fn test_correlation_analysis() {
1353 use crate::core::CallStackRef;
1354
1355 let mut analyzer = SecurityViolationAnalyzer::default();
1356 let call_stack = CallStackRef::new(100, Some(1));
1357
1358 let violation1 = SafetyViolation::DoubleFree {
1359 first_free_stack: call_stack.clone(),
1360 second_free_stack: call_stack.clone(),
1361 timestamp: 1000,
1362 };
1363 let violation2 = SafetyViolation::InvalidFree {
1364 attempted_pointer: 0x2000,
1365 stack: call_stack,
1366 timestamp: 2000,
1367 };
1368
1369 analyzer.analyze_violation(&violation1, 0x1000).unwrap();
1370 analyzer.analyze_violation(&violation2, 0x2000).unwrap();
1371
1372 let reports = analyzer.get_all_reports();
1373 let invalid_free_report = reports
1374 .values()
1375 .find(|r| r.violation_type == "InvalidFree")
1376 .expect("Should have InvalidFree report");
1377
1378 assert!(
1379 !invalid_free_report.correlated_violations.is_empty(),
1380 "InvalidFree should be correlated with DoubleFree"
1381 );
1382 }
1383
1384 #[test]
1387 fn test_technical_details_generation() {
1388 use crate::core::CallStackRef;
1389
1390 let mut analyzer = SecurityViolationAnalyzer::default();
1391 let call_stack = CallStackRef::new(110, Some(1));
1392
1393 let violation = SafetyViolation::DoubleFree {
1394 first_free_stack: call_stack.clone(),
1395 second_free_stack: call_stack,
1396 timestamp: 12345,
1397 };
1398
1399 analyzer.analyze_violation(&violation, 0x1000).unwrap();
1400
1401 let reports = analyzer.get_all_reports();
1402 let report = reports.values().next().expect("Should have report");
1403 assert!(
1404 report.technical_details.contains("12345"),
1405 "Technical details should include timestamp"
1406 );
1407 assert!(
1408 report.technical_details.contains("heap corruption"),
1409 "Technical details should mention heap corruption"
1410 );
1411 }
1412}