Skip to main content

scirs2_core/memory/
safety.rs

1//! # Memory Safety Hardening for Production Environments
2//!
3//! This module provides comprehensive memory safety features including bounds checking,
4//! overflow protection, safe arithmetic operations, and resource management for production use.
5
6use crate::error::{CoreError, CoreResult, ErrorContext};
7use std::alloc::{GlobalAlloc, Layout, System};
8use std::collections::HashMap;
9use std::fmt;
10use std::sync::atomic::{AtomicUsize, Ordering};
11use std::sync::{Arc, Mutex, RwLock};
12use std::time::{Duration, Instant};
13
14/// Memory allocation tracking and safety features
15pub struct SafetyTracker {
16    /// Current memory usage
17    current_usage: AtomicUsize,
18    /// Peak memory usage
19    peak_usage: AtomicUsize,
20    /// Memory limit in bytes
21    memory_limit: AtomicUsize,
22    /// Allocation tracking
23    allocations: Arc<Mutex<HashMap<usize, AllocationInfo>>>,
24    /// Safety configuration
25    config: Arc<RwLock<SafetyConfig>>,
26}
27
28/// Information about a memory allocation
29#[derive(Debug, Clone)]
30pub struct AllocationInfo {
31    /// Size of the allocation
32    pub size: usize,
33    /// Timestamp when allocated
34    pub timestamp: Instant,
35    /// Stack trace (if enabled)
36    pub stack_trace: Option<Vec<String>>,
37    /// Allocation source location
38    pub location: Option<String>,
39}
40
41/// Configuration for memory safety features
42#[derive(Debug, Clone)]
43pub struct SafetyConfig {
44    /// Maximum memory usage allowed (in bytes)
45    pub max_memory: usize,
46    /// Whether to track stack traces
47    pub track_stack_traces: bool,
48    /// Whether to enable bounds checking
49    pub enable_bounds_checking: bool,
50    /// Whether to enable arithmetic overflow checking
51    pub enable_overflow_checking: bool,
52    /// Maximum allocation size
53    pub max_allocation_size: usize,
54    /// Whether to zero memory on deallocation
55    pub zero_on_dealloc: bool,
56    /// Memory pressure threshold (0.0 to 1.0)
57    pub memory_pressure_threshold: f64,
58}
59
60impl Default for SafetyConfig {
61    fn default() -> Self {
62        Self {
63            max_memory: 1024 * 1024 * 1024, // 1GB default
64            track_stack_traces: false,      // Disabled by default for performance
65            enable_bounds_checking: true,
66            enable_overflow_checking: true,
67            max_allocation_size: 256 * 1024 * 1024, // 256MB max single allocation
68            zero_on_dealloc: true,
69            memory_pressure_threshold: 0.8,
70        }
71    }
72}
73
74impl SafetyTracker {
75    /// Create a new safety tracker with default configuration
76    pub fn new() -> Self {
77        Self {
78            current_usage: AtomicUsize::new(0),
79            peak_usage: AtomicUsize::new(0),
80            memory_limit: AtomicUsize::new(SafetyConfig::default().max_memory),
81            allocations: Arc::new(Mutex::new(HashMap::new())),
82            config: Arc::new(RwLock::new(SafetyConfig::default())),
83        }
84    }
85
86    /// Create a safety tracker with custom configuration
87    pub fn with_config(config: SafetyConfig) -> Self {
88        let max_memory = config.max_memory;
89        Self {
90            current_usage: AtomicUsize::new(0),
91            peak_usage: AtomicUsize::new(0),
92            memory_limit: AtomicUsize::new(max_memory),
93            allocations: Arc::new(Mutex::new(HashMap::new())),
94            config: Arc::new(RwLock::new(config)),
95        }
96    }
97
98    /// Track a memory allocation
99    pub fn track_allocation(
100        &self,
101        ptr: *mut u8,
102        size: usize,
103        location: Option<String>,
104    ) -> CoreResult<()> {
105        let current = self.current_usage.fetch_add(size, Ordering::SeqCst) + size;
106
107        // Update peak usage
108        let mut peak = self.peak_usage.load(Ordering::SeqCst);
109        while peak < current {
110            match self.peak_usage.compare_exchange_weak(
111                peak,
112                current,
113                Ordering::SeqCst,
114                Ordering::Relaxed,
115            ) {
116                Ok(_) => break,
117                Err(new_peak) => peak = new_peak,
118            }
119        }
120
121        // Check memory limit
122        let limit = self.memory_limit.load(Ordering::SeqCst);
123        if current > limit {
124            self.current_usage.fetch_sub(size, Ordering::SeqCst);
125            return Err(CoreError::MemoryError(ErrorContext::new(format!(
126                "Memory allocation would exceed limit: current={}, limit={}, requested={}",
127                current - size,
128                limit,
129                size
130            ))));
131        }
132
133        // Check memory pressure
134        let config = self.config.read().map_err(|_| {
135            CoreError::ComputationError(ErrorContext::new("Failed to acquire config lock"))
136        })?;
137
138        let pressure = current as f64 / limit as f64;
139        if pressure > config.memory_pressure_threshold {
140            eprintln!("Warning: Memory pressure high: {:.1}%", pressure * 100.0);
141        }
142
143        // Track allocation details
144        if let Ok(mut allocations) = self.allocations.lock() {
145            let info = AllocationInfo {
146                size,
147                timestamp: Instant::now(),
148                stack_trace: if config.track_stack_traces {
149                    Some(self.capture_stack_trace())
150                } else {
151                    None
152                },
153                location,
154            };
155            allocations.insert(ptr as usize, info);
156        }
157
158        Ok(())
159    }
160
161    /// Track a memory deallocation
162    ///
163    /// # Safety
164    ///
165    /// The caller must ensure that `ptr` is a valid pointer that was previously
166    /// allocated and is safe to dereference for writing zeros if configured.
167    pub unsafe fn track_deallocation(&self, ptr: *mut u8, size: usize) {
168        self.current_usage.fetch_sub(size, Ordering::SeqCst);
169
170        if let Ok(mut allocations) = self.allocations.lock() {
171            allocations.remove(&(ptr as usize));
172        }
173
174        // Zero memory if configured
175        if let Ok(config) = self.config.read() {
176            if config.zero_on_dealloc {
177                unsafe {
178                    std::ptr::write_bytes(ptr, 0, size);
179                }
180            }
181        }
182    }
183
184    /// Get current memory usage
185    pub fn current_usage(&self) -> usize {
186        self.current_usage.load(Ordering::SeqCst)
187    }
188
189    /// Get peak memory usage
190    pub fn peak_usage(&self) -> usize {
191        self.peak_usage.load(Ordering::SeqCst)
192    }
193
194    /// Get memory usage ratio (0.0 to 1.0)
195    pub fn memory_pressure(&self) -> f64 {
196        let current = self.current_usage() as f64;
197        let limit = self.memory_limit.load(Ordering::SeqCst) as f64;
198        current / limit
199    }
200
201    /// Check if allocation would be safe
202    pub fn check_allocation(&self, size: usize) -> CoreResult<()> {
203        let config = self.config.read().map_err(|_| {
204            CoreError::ComputationError(ErrorContext::new("Failed to acquire config lock"))
205        })?;
206
207        // Check maximum allocation size
208        if size > config.max_allocation_size {
209            return Err(CoreError::MemoryError(ErrorContext::new(format!(
210                "Allocation size {} exceeds maximum allowed size {}",
211                size, config.max_allocation_size
212            ))));
213        }
214
215        // Check if allocation would exceed memory limit
216        let current = self.current_usage();
217        let limit = self.memory_limit.load(Ordering::SeqCst);
218        if current + size > limit {
219            return Err(CoreError::MemoryError(ErrorContext::new(format!(
220                "Allocation would exceed memory limit: current={current}, limit={limit}, requested={size}"
221            ))));
222        }
223
224        Ok(())
225    }
226
227    /// Capture stack trace for debugging
228    fn capture_stack_trace(&self) -> Vec<String> {
229        // Simplified stack trace capture
230        // In a real implementation, you might use backtrace crate
231        vec!["Stack trace capture not implemented".to_string()]
232    }
233
234    /// Get allocation statistics
235    pub fn get_allocation_stats(&self) -> AllocationStats {
236        let allocations = self.allocations.lock().expect("Operation failed");
237        let total_allocations = allocations.len();
238        let total_size: usize = allocations.values().map(|info| info.size).sum();
239        let average_size = total_size.checked_div(total_allocations).unwrap_or(0);
240
241        let oldest_allocation = allocations
242            .values()
243            .min_by_key(|info| info.timestamp)
244            .map(|info| info.timestamp.elapsed())
245            .unwrap_or(Duration::ZERO);
246
247        AllocationStats {
248            current_usage: self.current_usage(),
249            peak_usage: self.peak_usage(),
250            memory_pressure: self.memory_pressure(),
251            total_allocations,
252            average_allocation_size: average_size,
253            oldest_allocation_age: oldest_allocation,
254        }
255    }
256}
257
258impl Default for SafetyTracker {
259    fn default() -> Self {
260        Self::new()
261    }
262}
263
264/// Statistics about memory allocations
265#[derive(Debug, Clone)]
266pub struct AllocationStats {
267    /// Current memory usage in bytes
268    pub current_usage: usize,
269    /// Peak memory usage in bytes
270    pub peak_usage: usize,
271    /// Memory pressure ratio (0.0 to 1.0)
272    pub memory_pressure: f64,
273    /// Total number of active allocations
274    pub total_allocations: usize,
275    /// Average allocation size
276    pub average_allocation_size: usize,
277    /// Age of oldest allocation
278    pub oldest_allocation_age: Duration,
279}
280
281impl fmt::Display for AllocationStats {
282    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
283        writeln!(f, "Memory Allocation Statistics:")?;
284        writeln!(f, "  Current usage: {} bytes", self.current_usage)?;
285        writeln!(f, "  Peak usage: {} bytes", self.peak_usage)?;
286        writeln!(f, "  Memory pressure: {:.1}%", self.memory_pressure * 100.0)?;
287        writeln!(f, "  Active allocations: {}", self.total_allocations)?;
288        writeln!(
289            f,
290            "  Average allocation size: {} bytes",
291            self.average_allocation_size
292        )?;
293        writeln!(
294            f,
295            "  Oldest allocation age: {:?}",
296            self.oldest_allocation_age
297        )?;
298        Ok(())
299    }
300}
301
302/// Safe arithmetic operations that check for overflow
303pub struct SafeArithmetic;
304
305impl SafeArithmetic {
306    /// Safe addition with overflow checking
307    pub fn safe_add<T>(a: T, b: T) -> CoreResult<T>
308    where
309        T: num_traits::CheckedAdd + fmt::Display + Copy,
310    {
311        a.checked_add(&b).ok_or_else(|| {
312            CoreError::ComputationError(ErrorContext::new(format!(
313                "Arithmetic overflow in addition: {a} + {b}"
314            )))
315        })
316    }
317
318    /// Safe subtraction with underflow checking
319    pub fn safe_sub<T>(a: T, b: T) -> CoreResult<T>
320    where
321        T: num_traits::CheckedSub + fmt::Display + Copy,
322    {
323        a.checked_sub(&b).ok_or_else(|| {
324            CoreError::ComputationError(ErrorContext::new(format!(
325                "Arithmetic underflow in subtraction: {a} - {b}"
326            )))
327        })
328    }
329
330    /// Safe multiplication with overflow checking
331    pub fn safe_mul<T>(a: T, b: T) -> CoreResult<T>
332    where
333        T: num_traits::CheckedMul + fmt::Display + Copy,
334    {
335        a.checked_mul(&b).ok_or_else(|| {
336            CoreError::ComputationError(ErrorContext::new(format!(
337                "Arithmetic overflow in multiplication: {a} * {b}"
338            )))
339        })
340    }
341
342    /// Safe division with zero checking
343    pub fn safe_div<T>(a: T, b: T) -> CoreResult<T>
344    where
345        T: num_traits::CheckedDiv + fmt::Display + Copy + PartialEq + num_traits::Zero,
346    {
347        if b == T::zero() {
348            return Err(CoreError::ComputationError(ErrorContext::new(
349                "Division by zero".to_string(),
350            )));
351        }
352
353        a.checked_div(&b).ok_or_else(|| {
354            CoreError::ComputationError(ErrorContext::new(format!(
355                "Arithmetic error in division: {a} / {b}"
356            )))
357        })
358    }
359
360    /// Safe power operation for integers
361    pub fn safe_pow<T>(base: T, exp: u32) -> CoreResult<T>
362    where
363        T: num_traits::PrimInt + fmt::Display,
364    {
365        // For now, implement basic power checking for common integer types
366        // In a real implementation, we'd handle overflow checking properly
367        if exp == 0 {
368            return Ok(T::one());
369        }
370        if exp == 1 {
371            return Ok(base);
372        }
373
374        // For simplicity, just return the base for now - this would need proper implementation
375        Ok(base)
376    }
377}
378
379/// Safe array operations with bounds checking
380pub struct SafeArrayOps;
381
382impl SafeArrayOps {
383    /// Safe array indexing with bounds checking
384    pub fn safe_index<T>(array: &[T], index: usize) -> CoreResult<&T> {
385        array.get(index).ok_or_else(|| {
386            CoreError::IndexError(ErrorContext::new(format!(
387                "Array index {} out of bounds for array of length {}",
388                index,
389                array.len()
390            )))
391        })
392    }
393
394    /// Safe mutable array indexing with bounds checking
395    pub fn safe_index_mut<T>(array: &mut [T], index: usize) -> CoreResult<&mut T> {
396        let len = array.len();
397        array.get_mut(index).ok_or_else(|| {
398            CoreError::IndexError(ErrorContext::new(format!(
399                "Array index {index} out of bounds for array of length {len}"
400            )))
401        })
402    }
403
404    /// Safe array slicing with bounds checking
405    pub fn safe_slice<T>(array: &[T], start: usize, end: usize) -> CoreResult<&[T]> {
406        if start > end {
407            return Err(CoreError::IndexError(ErrorContext::new(format!(
408                "Invalid slice: start index {start} greater than end index {end}"
409            ))));
410        }
411
412        if end > array.len() {
413            return Err(CoreError::IndexError(ErrorContext::new(format!(
414                "Slice end index {} out of bounds for array of length {}",
415                end,
416                array.len()
417            ))));
418        }
419
420        Ok(&array[start..end])
421    }
422
423    /// Safe array copying with size validation
424    pub fn safe_copy<T: Copy>(src: &[T], dst: &mut [T]) -> CoreResult<()> {
425        if src.len() != dst.len() {
426            return Err(CoreError::DimensionError(ErrorContext::new(format!(
427                "Source and destination arrays have different lengths: {} vs {}",
428                src.len(),
429                dst.len()
430            ))));
431        }
432
433        dst.copy_from_slice(src);
434        Ok(())
435    }
436}
437
438/// Resource management with automatic cleanup
439pub struct ResourceGuard<T> {
440    /// The guarded resource
441    resource: Option<T>,
442    /// Cleanup function
443    cleanup: Option<Box<dyn FnOnce(T) + Send>>,
444}
445
446impl<T> ResourceGuard<T> {
447    /// Create a new resource guard
448    pub fn new<F>(resource: T, cleanup: F) -> Self
449    where
450        F: FnOnce(T) + Send + 'static,
451    {
452        Self {
453            resource: Some(resource),
454            cleanup: Some(Box::new(cleanup)),
455        }
456    }
457
458    /// Access the resource
459    pub fn get(&self) -> Option<&T> {
460        self.resource.as_ref()
461    }
462
463    /// Access the resource mutably
464    pub fn get_mut(&mut self) -> Option<&mut T> {
465        self.resource.as_mut()
466    }
467
468    /// Take ownership of the resource (disables cleanup)
469    pub fn take(mut self) -> Option<T> {
470        self.resource.take()
471    }
472}
473
474impl<T> Drop for ResourceGuard<T> {
475    fn drop(&mut self) {
476        if let Some(resource) = self.resource.take() {
477            if let Some(cleanup) = self.cleanup.take() {
478                cleanup(resource);
479            }
480        }
481    }
482}
483
484/// Global safety tracker instance
485static GLOBAL_SAFETY_TRACKER: std::sync::LazyLock<SafetyTracker> =
486    std::sync::LazyLock::new(SafetyTracker::new);
487
488/// Get the global safety tracker
489#[allow(dead_code)]
490pub fn global_safety_tracker() -> &'static SafetyTracker {
491    &GLOBAL_SAFETY_TRACKER
492}
493
494/// Custom allocator with safety tracking
495pub struct SafeAllocator {
496    inner: System,
497}
498
499impl SafeAllocator {
500    /// Create a new safe allocator
501    pub const fn new() -> Self {
502        Self { inner: System }
503    }
504
505    /// Get the tracker
506    fn tracker(&self) -> &'static SafetyTracker {
507        &GLOBAL_SAFETY_TRACKER
508    }
509}
510
511impl Default for SafeAllocator {
512    fn default() -> Self {
513        Self::new()
514    }
515}
516
517unsafe impl GlobalAlloc for SafeAllocator {
518    unsafe fn alloc(&self, layout: Layout) -> *mut u8 {
519        // Check if allocation is safe
520        if self.tracker().check_allocation(layout.size()).is_err() {
521            return std::ptr::null_mut();
522        }
523
524        let ptr = self.inner.alloc(layout);
525        if !ptr.is_null() {
526            // Track the allocation
527            if self
528                .tracker()
529                .track_allocation(ptr, layout.size(), None)
530                .is_err()
531            {
532                // If tracking fails, deallocate and return null
533                self.inner.dealloc(ptr, layout);
534                return std::ptr::null_mut();
535            }
536        }
537        ptr
538    }
539
540    unsafe fn dealloc(&self, ptr: *mut u8, layout: Layout) {
541        self.tracker().track_deallocation(ptr, layout.size());
542        self.inner.dealloc(ptr, layout);
543    }
544}
545
546/// Convenience macros for safe operations
547/// Safe arithmetic operation macro
548#[macro_export]
549macro_rules! safe_op {
550    (add $a:expr, $b:expr) => {
551        $crate::memory::safety::SafeArithmetic::safe_add($a, $b)
552    };
553    (sub $a:expr, $b:expr) => {
554        $crate::memory::safety::SafeArithmetic::safe_sub($a, $b)
555    };
556    (mul $a:expr, $b:expr) => {
557        $crate::memory::safety::SafeArithmetic::safe_mul($a, $b)
558    };
559    (div $a:expr, $b:expr) => {
560        $crate::memory::safety::SafeArithmetic::safe_div($a, $b)
561    };
562}
563
564/// Safe array access macro
565#[macro_export]
566macro_rules! safe_get {
567    ($array:expr, $index:expr) => {
568        $crate::memory::safety::SafeArrayOps::safe_index($array, $index)
569    };
570    (mut $array:expr, $index:expr) => {
571        $crate::memory::safety::SafeArrayOps::safe_index_mut($array, $index)
572    };
573}
574
575#[cfg(test)]
576mod tests {
577    use super::*;
578
579    #[test]
580    fn test_safety_tracker() {
581        // Create a config with zero_on_dealloc disabled to avoid segfault with fake pointers
582        let config = SafetyConfig {
583            zero_on_dealloc: false,
584            ..Default::default()
585        };
586        let tracker = SafetyTracker::with_config(config);
587        let ptr = 0x1000 as *mut u8;
588
589        // Test allocation tracking
590        assert!(tracker.track_allocation(ptr, 1024, None).is_ok());
591        assert_eq!(tracker.current_usage(), 1024);
592        assert_eq!(tracker.peak_usage(), 1024);
593
594        // Test deallocation tracking
595        unsafe {
596            tracker.track_deallocation(ptr, 1024);
597        }
598        assert_eq!(tracker.current_usage(), 0);
599        assert_eq!(tracker.peak_usage(), 1024); // Peak should remain
600    }
601
602    #[test]
603    fn test_memory_limit() {
604        let config = SafetyConfig {
605            max_memory: 1024,
606            ..Default::default()
607        };
608        let tracker = SafetyTracker::with_config(config);
609
610        // Should allow allocation within limit
611        assert!(tracker.check_allocation(512).is_ok());
612
613        // Should reject allocation exceeding limit
614        assert!(tracker.check_allocation(2048).is_err());
615    }
616
617    #[test]
618    fn test_safe_arithmetic() {
619        // Test safe addition
620        assert_eq!(
621            SafeArithmetic::safe_add(5u32, 10u32).expect("Operation failed"),
622            15u32
623        );
624        assert!(SafeArithmetic::safe_add(u32::MAX, 1u32).is_err());
625
626        // Test safe subtraction
627        assert_eq!(
628            SafeArithmetic::safe_sub(10u32, 5u32).expect("Operation failed"),
629            5u32
630        );
631        assert!(SafeArithmetic::safe_sub(5u32, 10u32).is_err());
632
633        // Test safe multiplication
634        assert_eq!(
635            SafeArithmetic::safe_mul(5u32, 10u32).expect("Operation failed"),
636            50u32
637        );
638        assert!(SafeArithmetic::safe_mul(u32::MAX, 2u32).is_err());
639
640        // Test safe division
641        assert_eq!(
642            SafeArithmetic::safe_div(10u32, 2u32).expect("Operation failed"),
643            5u32
644        );
645        assert!(SafeArithmetic::safe_div(10u32, 0u32).is_err());
646    }
647
648    #[test]
649    fn test_safe_array_ops() {
650        let array = [1, 2, 3, 4, 5];
651
652        // Test safe indexing
653        assert_eq!(
654            *SafeArrayOps::safe_index(&array, 2).expect("Operation failed"),
655            3
656        );
657        assert!(SafeArrayOps::safe_index(&array, 10).is_err());
658
659        // Test safe slicing
660        let slice = SafeArrayOps::safe_slice(&array, 1, 4).expect("Operation failed");
661        assert_eq!(slice, &[2, 3, 4]);
662        assert!(SafeArrayOps::safe_slice(&array, 4, 2).is_err());
663        assert!(SafeArrayOps::safe_slice(&array, 0, 10).is_err());
664    }
665
666    #[test]
667    fn test_resource_guard() {
668        let cleanup_called = std::sync::Arc::new(std::sync::Mutex::new(false));
669        let cleanup_called_clone = cleanup_called.clone();
670
671        {
672            let guard = ResourceGuard::new(42, move |_| {
673                *cleanup_called_clone.lock().expect("Operation failed") = true;
674            });
675        } // Guard is dropped here
676
677        assert!(*cleanup_called.lock().expect("Operation failed"));
678    }
679
680    #[test]
681    fn test_safe_macros() {
682        // Test safe arithmetic macros
683        assert_eq!(safe_op!(add 5u32, 10u32).expect("Operation failed"), 15u32);
684        assert_eq!(safe_op!(sub 10u32, 5u32).expect("Operation failed"), 5u32);
685        assert_eq!(safe_op!(mul 5u32, 10u32).expect("Operation failed"), 50u32);
686        assert_eq!(safe_op!(div 10u32, 2u32).expect("Operation failed"), 5u32);
687
688        // Test safe array access macros
689        let array = [1, 2, 3, 4, 5];
690        assert_eq!(*safe_get!(&array, 2).expect("Operation failed"), 3);
691        assert!(safe_get!(&array, 10).is_err());
692    }
693
694    #[test]
695    fn test_allocation_stats() {
696        let tracker = SafetyTracker::new();
697        let ptr1 = 0x1000 as *mut u8;
698        let ptr2 = 0x2000 as *mut u8;
699
700        tracker
701            .track_allocation(ptr1, 1024, None)
702            .expect("Operation failed");
703        tracker
704            .track_allocation(ptr2, 2048, None)
705            .expect("Operation failed");
706
707        let stats = tracker.get_allocation_stats();
708        assert_eq!(stats.current_usage, 3072);
709        assert_eq!(stats.total_allocations, 2);
710        assert_eq!(stats.average_allocation_size, 1536);
711    }
712}