Skip to main content

wasm_sandbox/security/
resource_limits.rs

1//! Resource limits implementation for the sandbox
2
3use std::sync::atomic::{AtomicU64, Ordering};
4use std::sync::{Arc, Mutex};
5use std::time::{Duration, Instant};
6
7use crate::error::{Error, Result};
8use crate::security::{
9    MemoryLimits, CpuLimits, IoLimits, TimeLimits, ResourceLimits
10};
11
12/// Memory resource tracker
13#[derive(Debug, Clone)]
14pub struct MemoryResourceTracker {
15    /// Maximum memory pages
16    pub max_memory_pages: u32,
17    
18    /// Current memory pages
19    current_pages: Arc<AtomicU64>,
20    
21    /// Peak memory pages
22    peak_pages: Arc<AtomicU64>,
23    
24    /// Memory growth rate tracking
25    growth_tracker: Arc<Mutex<MemoryGrowthTracker>>,
26}
27
28/// Tracking for memory growth rate
29#[derive(Debug)]
30struct MemoryGrowthTracker {
31    /// Maximum growth rate
32    max_rate: Option<u32>,
33    
34    /// Last memory size
35    last_size: u64,
36    
37    /// Last check time
38    last_check: Instant,
39    
40    /// Growth events in current window
41    growth_events: Vec<(Instant, u64)>,
42    
43    /// Window duration
44    window: Duration,
45}
46
47impl MemoryResourceTracker {
48    /// Create a new memory resource tracker
49    pub fn new(limits: &MemoryLimits) -> Self {
50        let growth_tracker = MemoryGrowthTracker {
51            max_rate: limits.max_growth_rate,
52            last_size: 0,
53            last_check: Instant::now(),
54            growth_events: Vec::new(),
55            window: Duration::from_secs(1), // 1 second window for growth rate
56        };
57        
58        Self {
59            max_memory_pages: limits.max_memory_pages,
60            current_pages: Arc::new(AtomicU64::new(limits.reserved_memory_pages as u64)),
61            peak_pages: Arc::new(AtomicU64::new(limits.reserved_memory_pages as u64)),
62            growth_tracker: Arc::new(Mutex::new(growth_tracker)),
63        }
64    }
65    
66    /// Check if memory allocation is allowed
67    pub fn check_allocation(&self, pages: u32) -> Result<()> {
68        let current = self.current_pages.load(Ordering::Acquire);
69        let requested = current + pages as u64;
70        
71        if requested > self.max_memory_pages as u64 {
72            return Err(Error::ResourceLimit {
73                message: format!("Memory allocation of {} pages would exceed limit of {} pages", 
74                    pages, self.max_memory_pages)
75            });
76        }
77        
78        // Check growth rate
79        if let Some(max_rate) = self.growth_tracker.lock().unwrap().max_rate {
80            let now = Instant::now();
81            let mut tracker = self.growth_tracker.lock().unwrap();
82            
83            // Clean up old events
84            let cutoff = now - tracker.window;
85            tracker.growth_events.retain(|(time, _)| *time >= cutoff);
86            
87            // Add current growth
88            tracker.growth_events.push((now, pages as u64));
89            
90            // Calculate total growth in window
91            let total_growth: u64 = tracker.growth_events.iter().map(|(_, size)| *size).sum();
92            
93            if total_growth > max_rate as u64 {
94                return Err(Error::ResourceLimit {
95                    message: format!("Memory growth rate of {} pages/s exceeds limit of {} pages/s",
96                        total_growth, max_rate)
97                });
98            }
99            
100            tracker.last_size = requested;
101            tracker.last_check = now;
102        }
103        
104        Ok(())
105    }
106    
107    /// Update memory usage
108    pub fn update(&self, pages: u32) {
109        let current = self.current_pages.fetch_add(pages as u64, Ordering::AcqRel) + pages as u64;
110        let mut peak = self.peak_pages.load(Ordering::Acquire);
111        
112        while current > peak {
113            match self.peak_pages.compare_exchange_weak(
114                peak,
115                current,
116                Ordering::AcqRel,
117                Ordering::Acquire,
118            ) {
119                Ok(_) => break,
120                Err(actual) => peak = actual,
121            }
122        }
123    }
124    
125    /// Get current memory usage in pages
126    pub fn current_pages(&self) -> u64 {
127        self.current_pages.load(Ordering::Acquire)
128    }
129    
130    /// Get peak memory usage in pages
131    pub fn peak_pages(&self) -> u64 {
132        self.peak_pages.load(Ordering::Acquire)
133    }
134    
135    /// Reset peak memory usage
136    pub fn reset_peak(&self) {
137        self.peak_pages.store(
138            self.current_pages.load(Ordering::Acquire),
139            Ordering::Release
140        );
141    }
142}
143
144/// CPU resource tracker
145#[derive(Debug, Clone)]
146pub struct CpuResourceTracker {
147    /// Maximum execution time
148    pub max_execution_time: Duration,
149    
150    /// Target CPU usage percentage
151    pub cpu_usage_percentage: Option<u8>,
152    
153    /// Maximum number of threads
154    pub max_threads: Option<u32>,
155    
156    /// Execution start time
157    start_time: Arc<Mutex<Option<Instant>>>,
158    
159    /// Total execution time
160    total_time: Arc<AtomicU64>,
161    
162    /// Number of active threads
163    active_threads: Arc<AtomicU64>,
164}
165
166impl CpuResourceTracker {
167    /// Create a new CPU resource tracker
168    pub fn new(limits: &CpuLimits) -> Self {
169        Self {
170            max_execution_time: Duration::from_millis(limits.max_execution_time_ms),
171            cpu_usage_percentage: limits.cpu_usage_percentage,
172            max_threads: limits.max_threads,
173            start_time: Arc::new(Mutex::new(None)),
174            total_time: Arc::new(AtomicU64::new(0)),
175            active_threads: Arc::new(AtomicU64::new(0)),
176        }
177    }
178    
179    /// Start execution tracking
180    pub fn start_execution(&self) {
181        let mut start = self.start_time.lock().unwrap();
182        if start.is_none() {
183            *start = Some(Instant::now());
184        }
185    }
186    
187    /// Stop execution tracking
188    pub fn stop_execution(&self) {
189        let mut start_lock = self.start_time.lock().unwrap();
190        if let Some(start) = *start_lock {
191            let elapsed = start.elapsed();
192            self.total_time.fetch_add(elapsed.as_millis() as u64, Ordering::AcqRel);
193            *start_lock = None;
194        }
195    }
196    
197    /// Check if execution time limit has been exceeded
198    pub fn check_time_limit(&self) -> Result<()> {
199        let total = self.total_time.load(Ordering::Acquire);
200        
201        // Add current execution time if running
202        let mut current_total = total;
203        let start_lock = self.start_time.lock().unwrap();
204        if let Some(start) = *start_lock {
205            current_total += start.elapsed().as_millis() as u64;
206        }
207        
208        if current_total > self.max_execution_time.as_millis() as u64 {
209            return Err(Error::Timeout {
210                operation: "execution".to_string(),
211                duration: Duration::from_millis(current_total),
212                instance_id: None,
213            });
214        }
215        
216        Ok(())
217    }
218    
219    /// Register a new thread
220    pub fn register_thread(&self) -> Result<()> {
221        if let Some(max) = self.max_threads {
222            let current = self.active_threads.fetch_add(1, Ordering::AcqRel) + 1;
223            if current > max as u64 {
224                // Rollback the increment
225                self.active_threads.fetch_sub(1, Ordering::AcqRel);
226                return Err(Error::ResourceLimit {
227                    message: format!("Thread limit of {} exceeded", max)
228                });
229            }
230        } else {
231            self.active_threads.fetch_add(1, Ordering::AcqRel);
232        }
233        
234        Ok(())
235    }
236    
237    /// Unregister a thread
238    pub fn unregister_thread(&self) {
239        self.active_threads.fetch_sub(1, Ordering::AcqRel);
240    }
241    
242    /// Get total execution time in milliseconds
243    pub fn total_time_ms(&self) -> u64 {
244        let total = self.total_time.load(Ordering::Acquire);
245        
246        // Add current execution time if running
247        let mut current_total = total;
248        let start_lock = self.start_time.lock().unwrap();
249        if let Some(start) = *start_lock {
250            current_total += start.elapsed().as_millis() as u64;
251        }
252        
253        current_total
254    }
255    
256    /// Get number of active threads
257    pub fn active_threads(&self) -> u32 {
258        self.active_threads.load(Ordering::Acquire) as u32
259    }
260    
261    /// Apply CPU usage throttling
262    pub fn apply_throttling(&self) {
263        if let Some(percentage) = self.cpu_usage_percentage {
264            if percentage >= 100 {
265                return; // No throttling needed
266            }
267            
268            // Simple throttling: sleep proportionally to target usage
269            if percentage > 0 {
270                // Example: For 50% CPU usage, sleep for 1ms after every 1ms of execution
271                let sleep_time_ns = (100 - percentage) as u64 * 10_000; // convert to nanoseconds
272                std::thread::sleep(Duration::from_nanos(sleep_time_ns));
273            }
274        }
275    }
276}
277
278/// I/O resource tracker
279#[derive(Debug, Clone)]
280pub struct IoResourceTracker {
281    /// Maximum number of open files
282    pub max_open_files: u32,
283    
284    /// Maximum read bytes per second
285    pub max_read_bytes_per_second: Option<u64>,
286    
287    /// Maximum write bytes per second
288    pub max_write_bytes_per_second: Option<u64>,
289    
290    /// Maximum total read bytes
291    pub max_total_read_bytes: Option<u64>,
292    
293    /// Maximum total write bytes
294    pub max_total_write_bytes: Option<u64>,
295    
296    /// Current number of open files
297    open_files: Arc<AtomicU64>,
298    
299    /// Total bytes read
300    total_read: Arc<AtomicU64>,
301    
302    /// Total bytes written
303    total_write: Arc<AtomicU64>,
304    
305    /// Rate tracking
306    rate_tracker: Arc<Mutex<IoRateTracker>>,
307}
308
309/// I/O rate tracking
310#[derive(Debug)]
311struct IoRateTracker {
312    /// Read events in the current window
313    read_events: Vec<(Instant, u64)>,
314    
315    /// Write events in the current window
316    write_events: Vec<(Instant, u64)>,
317    
318    /// Window duration
319    window: Duration,
320}
321
322impl IoResourceTracker {
323    /// Create a new I/O resource tracker
324    pub fn new(limits: &IoLimits) -> Self {
325        let rate_tracker = IoRateTracker {
326            read_events: Vec::new(),
327            write_events: Vec::new(),
328            window: Duration::from_secs(1), // 1 second window
329        };
330        
331        Self {
332            max_open_files: limits.max_open_files,
333            max_read_bytes_per_second: limits.max_read_bytes_per_second,
334            max_write_bytes_per_second: limits.max_write_bytes_per_second,
335            max_total_read_bytes: limits.max_total_read_bytes,
336            max_total_write_bytes: limits.max_total_write_bytes,
337            open_files: Arc::new(AtomicU64::new(0)),
338            total_read: Arc::new(AtomicU64::new(0)),
339            total_write: Arc::new(AtomicU64::new(0)),
340            rate_tracker: Arc::new(Mutex::new(rate_tracker)),
341        }
342    }
343    
344    /// Register a file open
345    pub fn register_open(&self) -> Result<()> {
346        let current = self.open_files.fetch_add(1, Ordering::AcqRel) + 1;
347        if current > self.max_open_files as u64 {
348            // Rollback the increment
349            self.open_files.fetch_sub(1, Ordering::AcqRel);
350            return Err(Error::ResourceLimit {
351                message: format!("Open file limit of {} exceeded", self.max_open_files)
352            });
353        }
354        
355        Ok(())
356    }
357    
358    /// Register a file close
359    pub fn register_close(&self) {
360        self.open_files.fetch_sub(1, Ordering::AcqRel);
361    }
362    
363    /// Register a read operation
364    pub fn register_read(&self, bytes: u64) -> Result<()> {
365        // Update total
366        let total = self.total_read.fetch_add(bytes, Ordering::AcqRel) + bytes;
367        
368        // Check total limit
369        if let Some(limit) = self.max_total_read_bytes {
370            if total > limit {
371                return Err(Error::ResourceLimit {
372                    message: format!("Total read limit of {} bytes exceeded", limit)
373                });
374            }
375        }
376        
377        // Check rate limit
378        if let Some(rate_limit) = self.max_read_bytes_per_second {
379            let now = Instant::now();
380            let mut tracker = self.rate_tracker.lock().unwrap();
381            
382            // Clean up old events
383            let cutoff = now - tracker.window;
384            tracker.read_events.retain(|(time, _)| *time >= cutoff);
385            
386            // Add current read
387            tracker.read_events.push((now, bytes));
388            
389            // Calculate total in window
390            let window_total: u64 = tracker.read_events.iter().map(|(_, size)| *size).sum();
391            
392            if window_total > rate_limit {
393                return Err(Error::ResourceLimit {
394                    message: format!("Read rate limit of {} bytes/s exceeded", rate_limit)
395                });
396            }
397        }
398        
399        Ok(())
400    }
401    
402    /// Register a write operation
403    pub fn register_write(&self, bytes: u64) -> Result<()> {
404        // Update total
405        let total = self.total_write.fetch_add(bytes, Ordering::AcqRel) + bytes;
406        
407        // Check total limit
408        if let Some(limit) = self.max_total_write_bytes {
409            if total > limit {
410                return Err(Error::ResourceLimit {
411                    message: format!("Total write limit of {} bytes exceeded", limit)
412                });
413            }
414        }
415        
416        // Check rate limit
417        if let Some(rate_limit) = self.max_write_bytes_per_second {
418            let now = Instant::now();
419            let mut tracker = self.rate_tracker.lock().unwrap();
420            
421            // Clean up old events
422            let cutoff = now - tracker.window;
423            tracker.write_events.retain(|(time, _)| *time >= cutoff);
424            
425            // Add current write
426            tracker.write_events.push((now, bytes));
427            
428            // Calculate total in window
429            let window_total: u64 = tracker.write_events.iter().map(|(_, size)| *size).sum();
430            
431            if window_total > rate_limit {
432                return Err(Error::ResourceLimit {
433                    message: format!("Write rate limit of {} bytes/s exceeded", rate_limit)
434                });
435            }
436        }
437        
438        Ok(())
439    }
440    
441    /// Get number of open files
442    pub fn open_files(&self) -> u32 {
443        self.open_files.load(Ordering::Acquire) as u32
444    }
445    
446    /// Get total bytes read
447    pub fn total_read(&self) -> u64 {
448        self.total_read.load(Ordering::Acquire)
449    }
450    
451    /// Get total bytes written
452    pub fn total_write(&self) -> u64 {
453        self.total_write.load(Ordering::Acquire)
454    }
455    
456    /// Get current read rate in bytes per second
457    pub fn read_rate(&self) -> u64 {
458        let tracker = self.rate_tracker.lock().unwrap();
459        let now = Instant::now();
460        let cutoff = now - tracker.window;
461        
462        // Sum bytes in current window
463        tracker.read_events
464            .iter()
465            .filter(|(time, _)| *time >= cutoff)
466            .map(|(_, size)| *size)
467            .sum()
468    }
469    
470    /// Get current write rate in bytes per second
471    pub fn write_rate(&self) -> u64 {
472        let tracker = self.rate_tracker.lock().unwrap();
473        let now = Instant::now();
474        let cutoff = now - tracker.window;
475        
476        // Sum bytes in current window
477        tracker.write_events
478            .iter()
479            .filter(|(time, _)| *time >= cutoff)
480            .map(|(_, size)| *size)
481            .sum()
482    }
483}
484
485/// Time resource tracker
486#[derive(Debug, Clone)]
487pub struct TimeResourceTracker {
488    /// Maximum total time
489    pub max_total_time: Duration,
490    
491    /// Maximum idle time
492    pub max_idle_time: Option<Duration>,
493    
494    /// Start time
495    start_time: Arc<Mutex<Instant>>,
496    
497    /// Last activity time
498    last_activity: Arc<Mutex<Instant>>,
499}
500
501impl TimeResourceTracker {
502    /// Create a new time resource tracker
503    pub fn new(limits: &TimeLimits) -> Self {
504        let now = Instant::now();
505        
506        Self {
507            max_total_time: Duration::from_millis(limits.max_total_time_ms),
508            max_idle_time: limits.max_idle_time_ms.map(Duration::from_millis),
509            start_time: Arc::new(Mutex::new(now)),
510            last_activity: Arc::new(Mutex::new(now)),
511        }
512    }
513    
514    /// Register activity to reset idle timer
515    pub fn register_activity(&self) {
516        *self.last_activity.lock().unwrap() = Instant::now();
517    }
518    
519    /// Check if time limits have been exceeded
520    pub fn check_limits(&self) -> Result<()> {
521        let now = Instant::now();
522        
523        // Check total time
524        let elapsed = now.duration_since(*self.start_time.lock().unwrap());
525        if elapsed > self.max_total_time {
526            return Err(Error::Timeout {
527                operation: "total time".to_string(),
528                duration: elapsed,
529                instance_id: None,
530            });
531        }
532        
533        // Check idle time
534        if let Some(idle_limit) = self.max_idle_time {
535            let idle_time = now.duration_since(*self.last_activity.lock().unwrap());
536            if idle_time > idle_limit {
537                return Err(Error::ResourceLimit {
538                    message: format!("Idle time limit of {}ms exceeded", idle_limit.as_millis())
539                });
540            }
541        }
542        
543        Ok(())
544    }
545    
546    /// Get elapsed time in milliseconds
547    pub fn elapsed_ms(&self) -> u64 {
548        let now = Instant::now();
549        now.duration_since(*self.start_time.lock().unwrap()).as_millis() as u64
550    }
551    
552    /// Get idle time in milliseconds
553    pub fn idle_ms(&self) -> u64 {
554        let now = Instant::now();
555        now.duration_since(*self.last_activity.lock().unwrap()).as_millis() as u64
556    }
557}
558
559/// Main resource limit manager
560#[derive(Debug, Clone)]
561pub struct ResourceLimitManager {
562    /// Memory resource tracker
563    pub memory: MemoryResourceTracker,
564    
565    /// CPU resource tracker
566    pub cpu: CpuResourceTracker,
567    
568    /// I/O resource tracker
569    pub io: IoResourceTracker,
570    
571    /// Time resource tracker
572    pub time: TimeResourceTracker,
573    
574    /// Fuel limit and usage
575    pub fuel: Option<Arc<AtomicU64>>,
576}
577
578impl ResourceLimitManager {
579    /// Create a new resource limit manager
580    pub fn new(limits: &ResourceLimits) -> Self {
581        let fuel = limits.fuel.map(|f| Arc::new(AtomicU64::new(f)));
582        
583        Self {
584            memory: MemoryResourceTracker::new(&limits.memory),
585            cpu: CpuResourceTracker::new(&limits.cpu),
586            io: IoResourceTracker::new(&limits.io),
587            time: TimeResourceTracker::new(&limits.time),
588            fuel,
589        }
590    }
591    
592    /// Check all resource limits
593    pub fn check_all_limits(&self) -> Result<()> {
594        // Check time limits
595        self.time.check_limits()?;
596        
597        // Check CPU limits
598        self.cpu.check_time_limit()?;
599        
600        // Check fuel limits
601        if let Some(fuel) = &self.fuel {
602            if fuel.load(Ordering::Acquire) == 0 {
603                return Err(Error::ResourceLimit {
604                    message: "Fuel limit exceeded".to_string()
605                });
606            }
607        }
608        
609        Ok(())
610    }
611    
612    /// Consume fuel
613    pub fn consume_fuel(&self, amount: u64) -> Result<()> {
614        if let Some(fuel) = &self.fuel {
615            let current = fuel.load(Ordering::Acquire);
616            if current < amount {
617                return Err(Error::ResourceLimit {
618                    message: format!("Not enough fuel: requested {}, available {}", amount, current)
619                });
620            }
621            
622            fuel.fetch_sub(amount, Ordering::AcqRel);
623        }
624        
625        Ok(())
626    }
627    
628    /// Add fuel
629    pub fn add_fuel(&self, amount: u64) -> Result<()> {
630        if let Some(fuel) = &self.fuel {
631            fuel.fetch_add(amount, Ordering::AcqRel);
632            Ok(())
633        } else {
634            Err(Error::UnsupportedOperation {
635                message: "Fuel metering is not enabled".to_string()
636            })
637        }
638    }
639    
640    /// Reset fuel
641    pub fn reset_fuel(&self, amount: u64) -> Result<()> {
642        if let Some(fuel) = &self.fuel {
643            fuel.store(amount, Ordering::Release);
644            Ok(())
645        } else {
646            Err(Error::UnsupportedOperation {
647                message: "Fuel metering is not enabled".to_string()
648            })
649        }
650    }
651    
652    /// Get remaining fuel
653    pub fn get_remaining_fuel(&self) -> Option<u64> {
654        self.fuel.as_ref().map(|f| f.load(Ordering::Acquire))
655    }
656    
657    /// Start a background monitor thread for resource limits
658    pub fn start_monitor(&self) -> std::thread::JoinHandle<()> {
659        // Clone the trackers
660        let cpu_tracker = self.cpu.clone();
661        let time_tracker = self.time.clone();
662        
663        std::thread::spawn(move || {
664            let check_interval = Duration::from_millis(100); // Check every 100ms
665            
666            loop {
667                // Sleep
668                std::thread::sleep(check_interval);
669                
670                // Register activity for time tracker (monitor itself counts as activity)
671                time_tracker.register_activity();
672                
673                // Apply CPU throttling
674                cpu_tracker.apply_throttling();
675                
676                // Check limits
677                // NOTE: We don't handle errors here because the monitor thread
678                // doesn't have a way to signal the main thread directly.
679                // This is just a background check - the main thread should also
680                // check limits at critical points.
681                let _ = time_tracker.check_limits();
682                let _ = cpu_tracker.check_time_limit();
683            }
684        })
685    }
686}