Skip to main content

memscope_rs/analysis/security/
analyzer.rs

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    /// Objective: Verify SecurityViolationAnalyzer creation with default config
535    /// Invariants: Default config should have correlation analysis enabled
536    #[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    /// Objective: Verify SecurityViolationAnalyzer creation with custom config
544    /// Invariants: Custom config values should be respected
545    #[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    /// Objective: Verify generate_security_summary with no violations
563    /// Invariants: Should produce valid JSON summary with zero violations
564    #[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    /// Objective: Verify clear_reports functionality
578    /// Invariants: Should work on empty analyzer
579    #[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    /// Objective: Verify get_reports_by_severity with no reports
590    /// Invariants: Should return empty vector
591    #[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    /// Objective: Verify update_allocations functionality
599    /// Invariants: Should accept empty vector
600    #[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    /// Objective: Verify ViolationSeverity score values
609    /// Invariants: Critical should be highest, Info should be lowest
610    #[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    /// Objective: Verify AnalysisConfig default values
636    /// Invariants: Default should have sensible values
637    #[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    /// Objective: Verify analyze_violation with DoubleFree
663    /// Invariants: Should create report with Critical severity
664    #[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    /// Objective: Verify analyze_violation with InvalidFree
700    /// Invariants: Should create report with High severity
701    #[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    /// Objective: Verify analyze_violation with PotentialLeak
733    /// Invariants: Should create report with Medium severity
734    #[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    /// Objective: Verify analyze_violation with CrossBoundaryRisk
766    /// Invariants: Should create report with Medium severity
767    #[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    /// Objective: Verify analyze_violation with correlation analysis disabled
802    /// Invariants: Should not find correlated violations when disabled
803    #[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    /// Objective: Verify analyze_violation with integrity hash disabled
832    /// Invariants: Should not generate hash when disabled
833    #[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    /// Objective: Verify get_reports_by_severity filters correctly
862    /// Invariants: Should return only reports with severity >= min_severity
863    #[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    /// Objective: Verify verify_report_integrity with valid hash
912    /// Invariants: Should return true for valid integrity hash
913    #[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    /// Objective: Verify verify_report_integrity when disabled
938    /// Invariants: Should return true when integrity hashes disabled
939    #[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    /// Objective: Verify generate_security_summary with violations
984    /// Invariants: Should include violation counts and risk assessment
985    #[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    /// Objective: Verify clear_reports removes all data
1019    /// Invariants: Should clear both reports and correlation matrix
1020    #[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    /// Objective: Verify update_allocations affects memory snapshot
1047    /// Invariants: Memory snapshot should reflect active allocations
1048    #[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    /// Objective: Verify memory pressure assessment - Critical
1079    /// Invariants: > 2GB should be Critical
1080    #[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    /// Objective: Verify memory pressure assessment - High
1108    /// Invariants: > 1GB should be High
1109    #[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    /// Objective: Verify memory pressure assessment - Medium
1137    /// Invariants: > 512MB should be Medium
1138    #[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    /// Objective: Verify impact assessment for DoubleFree
1166    /// Invariants: Should have code execution risk
1167    #[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    /// Objective: Verify impact assessment for InvalidFree
1199    /// Invariants: Should not have code execution risk
1200    #[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    /// Objective: Verify impact assessment for CrossBoundaryRisk
1228    /// Invariants: Should have information disclosure risk
1229    #[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    /// Objective: Verify remediation suggestions for DoubleFree
1253    /// Invariants: Should include RAII and ownership tracking suggestions
1254    #[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    /// Objective: Verify remediation suggestions for high risk
1288    /// Invariants: Should include URGENT prefix for high risk
1289    #[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    /// Objective: Verify related allocations detection
1322    /// Invariants: Should find allocations in same region
1323    #[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    /// Objective: Verify correlation analysis between violations
1350    /// Invariants: DoubleFree and InvalidFree should be correlated
1351    #[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    /// Objective: Verify technical details generation
1385    /// Invariants: Should include timestamp in technical details
1386    #[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}