Skip to main content

memscope_rs/analysis/security/
types.rs

1use serde::{Deserialize, Serialize};
2
3#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
4pub enum ViolationSeverity {
5    Critical,
6    High,
7    Medium,
8    Low,
9    Info,
10}
11
12impl ViolationSeverity {
13    pub fn score(&self) -> u32 {
14        match self {
15            ViolationSeverity::Critical => 100,
16            ViolationSeverity::High => 75,
17            ViolationSeverity::Medium => 50,
18            ViolationSeverity::Low => 25,
19            ViolationSeverity::Info => 10,
20        }
21    }
22}
23
24#[derive(Debug, Clone, Serialize, Deserialize)]
25pub struct MemoryStateSnapshot {
26    pub timestamp_ns: u64,
27    pub total_allocated_bytes: usize,
28    pub active_allocation_count: usize,
29    pub involved_addresses: Vec<String>,
30    pub stack_trace: Vec<StackFrame>,
31    pub related_allocations: Vec<RelatedAllocation>,
32    pub memory_pressure: MemoryPressureLevel,
33}
34
35#[derive(Debug, Clone, Serialize, Deserialize)]
36pub struct StackFrame {
37    pub function_name: String,
38    pub file_path: Option<String>,
39    pub line_number: Option<u32>,
40    pub frame_address: String,
41    pub is_unsafe: bool,
42    pub is_ffi: bool,
43}
44
45#[derive(Debug, Clone, Serialize, Deserialize)]
46pub struct RelatedAllocation {
47    pub address: String,
48    pub size: usize,
49    pub type_name: Option<String>,
50    pub variable_name: Option<String>,
51    pub allocated_at_ns: u64,
52    pub is_active: bool,
53    pub relationship: AllocationRelationship,
54}
55
56#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
57pub enum AllocationRelationship {
58    SameRegion,
59    Adjacent,
60    SameType,
61    SameScope,
62    DoubleFreeCandidate,
63    LeakRelated,
64    UseAfterFreeRelated,
65    None,
66}
67
68#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
69pub enum MemoryPressureLevel {
70    Low,
71    Medium,
72    High,
73    Critical,
74}
75
76#[derive(Debug, Clone, Serialize, Deserialize)]
77pub struct SecurityViolationReport {
78    pub violation_id: String,
79    pub violation_type: String,
80    pub severity: ViolationSeverity,
81    pub description: String,
82    pub technical_details: String,
83    pub memory_snapshot: MemoryStateSnapshot,
84    pub impact_assessment: ImpactAssessment,
85    pub remediation_suggestions: Vec<String>,
86    pub correlated_violations: Vec<String>,
87    pub integrity_hash: String,
88    pub generated_at_ns: u64,
89}
90
91#[derive(Debug, Clone, Serialize, Deserialize)]
92pub struct ImpactAssessment {
93    pub exploitability_score: f64,
94    pub data_corruption_risk: bool,
95    pub information_disclosure_risk: bool,
96    pub denial_of_service_risk: bool,
97    pub code_execution_risk: bool,
98    pub overall_risk_score: f64,
99}
100
101#[derive(Debug, Clone)]
102pub struct AnalysisConfig {
103    pub max_related_allocations: usize,
104    pub max_stack_depth: usize,
105    pub enable_correlation_analysis: bool,
106    pub include_low_severity: bool,
107    pub generate_integrity_hashes: bool,
108}
109
110impl Default for AnalysisConfig {
111    fn default() -> Self {
112        Self {
113            max_related_allocations: 10,
114            max_stack_depth: 20,
115            enable_correlation_analysis: true,
116            include_low_severity: true,
117            generate_integrity_hashes: true,
118        }
119    }
120}
121
122#[cfg(test)]
123mod tests {
124    use super::*;
125
126    /// Objective: Verify ViolationSeverity score values
127    /// Invariants: Critical should be highest, Info should be lowest
128    #[test]
129    fn test_violation_severity_scores() {
130        assert_eq!(
131            ViolationSeverity::Critical.score(),
132            100,
133            "Critical score should be 100"
134        );
135        assert_eq!(
136            ViolationSeverity::High.score(),
137            75,
138            "High score should be 75"
139        );
140        assert_eq!(
141            ViolationSeverity::Medium.score(),
142            50,
143            "Medium score should be 50"
144        );
145        assert_eq!(ViolationSeverity::Low.score(), 25, "Low score should be 25");
146        assert_eq!(
147            ViolationSeverity::Info.score(),
148            10,
149            "Info score should be 10"
150        );
151    }
152
153    /// Objective: Verify ViolationSeverity ordering
154    /// Invariants: Scores should reflect severity ordering
155    #[test]
156    fn test_violation_severity_ordering() {
157        assert!(ViolationSeverity::Critical.score() > ViolationSeverity::High.score());
158        assert!(ViolationSeverity::High.score() > ViolationSeverity::Medium.score());
159        assert!(ViolationSeverity::Medium.score() > ViolationSeverity::Low.score());
160        assert!(ViolationSeverity::Low.score() > ViolationSeverity::Info.score());
161    }
162
163    /// Objective: Verify ViolationSeverity equality
164    /// Invariants: Same severities should be equal
165    #[test]
166    fn test_violation_severity_equality() {
167        assert_eq!(ViolationSeverity::Critical, ViolationSeverity::Critical);
168        assert_eq!(ViolationSeverity::High, ViolationSeverity::High);
169        assert_ne!(ViolationSeverity::Critical, ViolationSeverity::High);
170    }
171
172    /// Objective: Verify MemoryStateSnapshot creation
173    /// Invariants: All fields should be properly initialized
174    #[test]
175    fn test_memory_state_snapshot_creation() {
176        let snapshot = MemoryStateSnapshot {
177            timestamp_ns: 1000,
178            total_allocated_bytes: 1024 * 1024,
179            active_allocation_count: 10,
180            involved_addresses: vec!["0x1000".to_string()],
181            stack_trace: vec![],
182            related_allocations: vec![],
183            memory_pressure: MemoryPressureLevel::Medium,
184        };
185
186        assert_eq!(snapshot.timestamp_ns, 1000, "Timestamp should match");
187        assert_eq!(
188            snapshot.total_allocated_bytes,
189            1024 * 1024,
190            "Total bytes should match"
191        );
192        assert_eq!(
193            snapshot.active_allocation_count, 10,
194            "Allocation count should match"
195        );
196    }
197
198    /// Objective: Verify StackFrame creation
199    /// Invariants: All fields should be accessible
200    #[test]
201    fn test_stack_frame_creation() {
202        let frame = StackFrame {
203            function_name: "test_function".to_string(),
204            file_path: Some("test.rs".to_string()),
205            line_number: Some(42),
206            frame_address: "0x7FFF12345678".to_string(),
207            is_unsafe: true,
208            is_ffi: false,
209        };
210
211        assert_eq!(
212            frame.function_name, "test_function",
213            "Function name should match"
214        );
215        assert_eq!(frame.line_number, Some(42), "Line number should match");
216        assert!(frame.is_unsafe, "Should be marked unsafe");
217        assert!(!frame.is_ffi, "Should not be FFI");
218    }
219
220    /// Objective: Verify RelatedAllocation creation
221    /// Invariants: All fields should be properly initialized
222    #[test]
223    fn test_related_allocation_creation() {
224        let related = RelatedAllocation {
225            address: "0x1000".to_string(),
226            size: 1024,
227            type_name: Some("Vec<u8>".to_string()),
228            variable_name: Some("data".to_string()),
229            allocated_at_ns: 1000,
230            is_active: true,
231            relationship: AllocationRelationship::SameRegion,
232        };
233
234        assert_eq!(related.size, 1024, "Size should match");
235        assert!(related.is_active, "Should be active");
236        assert_eq!(
237            related.relationship,
238            AllocationRelationship::SameRegion,
239            "Relationship should match"
240        );
241    }
242
243    /// Objective: Verify AllocationRelationship variants
244    /// Invariants: All variants should be distinct
245    #[test]
246    fn test_allocation_relationship_variants() {
247        let relationships = vec![
248            AllocationRelationship::SameRegion,
249            AllocationRelationship::Adjacent,
250            AllocationRelationship::SameType,
251            AllocationRelationship::SameScope,
252            AllocationRelationship::DoubleFreeCandidate,
253            AllocationRelationship::LeakRelated,
254            AllocationRelationship::UseAfterFreeRelated,
255            AllocationRelationship::None,
256        ];
257
258        for rel in &relationships {
259            let debug_str = format!("{rel:?}");
260            assert!(
261                !debug_str.is_empty(),
262                "Relationship should have debug representation"
263            );
264        }
265    }
266
267    /// Objective: Verify MemoryPressureLevel variants
268    /// Invariants: All levels should be distinct
269    #[test]
270    fn test_memory_pressure_level_variants() {
271        assert_eq!(MemoryPressureLevel::Low, MemoryPressureLevel::Low);
272        assert_eq!(MemoryPressureLevel::Medium, MemoryPressureLevel::Medium);
273        assert_eq!(MemoryPressureLevel::High, MemoryPressureLevel::High);
274        assert_eq!(MemoryPressureLevel::Critical, MemoryPressureLevel::Critical);
275
276        assert_ne!(MemoryPressureLevel::Low, MemoryPressureLevel::Critical);
277    }
278
279    /// Objective: Verify SecurityViolationReport creation
280    /// Invariants: All fields should be properly initialized
281    #[test]
282    fn test_security_violation_report_creation() {
283        let report = SecurityViolationReport {
284            violation_id: "SEC-DF-123".to_string(),
285            violation_type: "DoubleFree".to_string(),
286            severity: ViolationSeverity::Critical,
287            description: "Double free detected".to_string(),
288            technical_details: "Technical info".to_string(),
289            memory_snapshot: MemoryStateSnapshot {
290                timestamp_ns: 0,
291                total_allocated_bytes: 0,
292                active_allocation_count: 0,
293                involved_addresses: vec![],
294                stack_trace: vec![],
295                related_allocations: vec![],
296                memory_pressure: MemoryPressureLevel::Low,
297            },
298            impact_assessment: ImpactAssessment {
299                exploitability_score: 0.9,
300                data_corruption_risk: true,
301                information_disclosure_risk: false,
302                denial_of_service_risk: true,
303                code_execution_risk: true,
304                overall_risk_score: 0.9,
305            },
306            remediation_suggestions: vec!["Fix the bug".to_string()],
307            correlated_violations: vec![],
308            integrity_hash: "abc123".to_string(),
309            generated_at_ns: 1000,
310        };
311
312        assert_eq!(
313            report.violation_id, "SEC-DF-123",
314            "Violation ID should match"
315        );
316        assert_eq!(
317            report.severity,
318            ViolationSeverity::Critical,
319            "Severity should be Critical"
320        );
321        assert!(
322            report.impact_assessment.code_execution_risk,
323            "Should have code execution risk"
324        );
325    }
326
327    /// Objective: Verify ImpactAssessment creation
328    /// Invariants: All fields should be accessible
329    #[test]
330    fn test_impact_assessment_creation() {
331        let impact = ImpactAssessment {
332            exploitability_score: 0.75,
333            data_corruption_risk: true,
334            information_disclosure_risk: true,
335            denial_of_service_risk: false,
336            code_execution_risk: false,
337            overall_risk_score: 0.6,
338        };
339
340        assert_eq!(
341            impact.exploitability_score, 0.75,
342            "Exploitability should match"
343        );
344        assert!(
345            impact.data_corruption_risk,
346            "Should have data corruption risk"
347        );
348        assert!(!impact.denial_of_service_risk, "Should not have DoS risk");
349    }
350
351    /// Objective: Verify ImpactAssessment edge values
352    /// Invariants: Should handle zero and max values
353    #[test]
354    fn test_impact_assessment_edge_values() {
355        let zero_impact = ImpactAssessment {
356            exploitability_score: 0.0,
357            data_corruption_risk: false,
358            information_disclosure_risk: false,
359            denial_of_service_risk: false,
360            code_execution_risk: false,
361            overall_risk_score: 0.0,
362        };
363
364        let max_impact = ImpactAssessment {
365            exploitability_score: 1.0,
366            data_corruption_risk: true,
367            information_disclosure_risk: true,
368            denial_of_service_risk: true,
369            code_execution_risk: true,
370            overall_risk_score: 1.0,
371        };
372
373        assert_eq!(
374            zero_impact.exploitability_score, 0.0,
375            "Zero exploitability should be valid"
376        );
377        assert_eq!(
378            max_impact.exploitability_score, 1.0,
379            "Max exploitability should be valid"
380        );
381    }
382
383    /// Objective: Verify AnalysisConfig default values
384    /// Invariants: Default should have sensible values
385    #[test]
386    fn test_analysis_config_default() {
387        let config = AnalysisConfig::default();
388
389        assert_eq!(
390            config.max_related_allocations, 10,
391            "Default max related should be 10"
392        );
393        assert_eq!(
394            config.max_stack_depth, 20,
395            "Default max stack depth should be 20"
396        );
397        assert!(
398            config.enable_correlation_analysis,
399            "Correlation should be enabled by default"
400        );
401        assert!(
402            config.include_low_severity,
403            "Low severity should be included by default"
404        );
405        assert!(
406            config.generate_integrity_hashes,
407            "Integrity hashes should be enabled by default"
408        );
409    }
410
411    /// Objective: Verify AnalysisConfig custom values
412    /// Invariants: Custom values should be respected
413    #[test]
414    fn test_analysis_config_custom() {
415        let config = AnalysisConfig {
416            max_related_allocations: 5,
417            max_stack_depth: 10,
418            enable_correlation_analysis: false,
419            include_low_severity: false,
420            generate_integrity_hashes: false,
421        };
422
423        assert_eq!(
424            config.max_related_allocations, 5,
425            "Custom max related should be 5"
426        );
427        assert_eq!(
428            config.max_stack_depth, 10,
429            "Custom max stack depth should be 10"
430        );
431        assert!(
432            !config.enable_correlation_analysis,
433            "Correlation should be disabled"
434        );
435    }
436
437    /// Objective: Verify serialization of types
438    /// Invariants: Types should serialize and deserialize correctly
439    #[test]
440    fn test_serialization() {
441        let severity = ViolationSeverity::Critical;
442        let json = serde_json::to_string(&severity);
443        assert!(json.is_ok(), "Should serialize to JSON");
444
445        let deserialized: Result<ViolationSeverity, _> = serde_json::from_str(&json.unwrap());
446        assert!(deserialized.is_ok(), "Should deserialize from JSON");
447        assert_eq!(
448            deserialized.unwrap(),
449            ViolationSeverity::Critical,
450            "Should preserve value"
451        );
452    }
453
454    /// Objective: Verify SecurityViolationReport serialization
455    /// Invariants: Report should serialize correctly
456    #[test]
457    fn test_report_serialization() {
458        let report = SecurityViolationReport {
459            violation_id: "SEC-TEST-123".to_string(),
460            violation_type: "Test".to_string(),
461            severity: ViolationSeverity::High,
462            description: "Test description".to_string(),
463            technical_details: "Test details".to_string(),
464            memory_snapshot: MemoryStateSnapshot {
465                timestamp_ns: 1000,
466                total_allocated_bytes: 1024,
467                active_allocation_count: 1,
468                involved_addresses: vec!["0x1000".to_string()],
469                stack_trace: vec![],
470                related_allocations: vec![],
471                memory_pressure: MemoryPressureLevel::Low,
472            },
473            impact_assessment: ImpactAssessment {
474                exploitability_score: 0.5,
475                data_corruption_risk: false,
476                information_disclosure_risk: false,
477                denial_of_service_risk: true,
478                code_execution_risk: false,
479                overall_risk_score: 0.5,
480            },
481            remediation_suggestions: vec!["Fix it".to_string()],
482            correlated_violations: vec![],
483            integrity_hash: "".to_string(),
484            generated_at_ns: 2000,
485        };
486
487        let json = serde_json::to_string(&report);
488        assert!(json.is_ok(), "Report should serialize to JSON");
489
490        let deserialized: Result<SecurityViolationReport, _> = serde_json::from_str(&json.unwrap());
491        assert!(deserialized.is_ok(), "Report should deserialize from JSON");
492    }
493
494    /// Objective: Verify StackFrame with None values
495    /// Invariants: Should handle missing file path and line number
496    #[test]
497    fn test_stack_frame_with_none_values() {
498        let frame = StackFrame {
499            function_name: "unknown".to_string(),
500            file_path: None,
501            line_number: None,
502            frame_address: "0x0".to_string(),
503            is_unsafe: false,
504            is_ffi: false,
505        };
506
507        assert!(frame.file_path.is_none(), "File path should be None");
508        assert!(frame.line_number.is_none(), "Line number should be None");
509    }
510
511    /// Objective: Verify RelatedAllocation with None type name
512    /// Invariants: Should handle missing type and variable names
513    #[test]
514    fn test_related_allocation_with_none_values() {
515        let related = RelatedAllocation {
516            address: "0x1000".to_string(),
517            size: 0,
518            type_name: None,
519            variable_name: None,
520            allocated_at_ns: 0,
521            is_active: false,
522            relationship: AllocationRelationship::None,
523        };
524
525        assert!(related.type_name.is_none(), "Type name should be None");
526        assert!(
527            related.variable_name.is_none(),
528            "Variable name should be None"
529        );
530        assert!(!related.is_active, "Should be inactive");
531    }
532}