1use std::time::Duration;
26
27#[cfg(feature = "failpoints")]
28use fail::fail_point;
29
30#[derive(Debug, Clone)]
32pub struct ExecutionLimits {
33 pub max_commands: usize,
36
37 pub max_loop_iterations: usize,
40
41 pub max_total_loop_iterations: usize,
47
48 pub max_function_depth: usize,
51
52 pub timeout: Duration,
55
56 pub parser_timeout: Duration,
61
62 pub max_input_bytes: usize,
66
67 pub max_ast_depth: usize,
71
72 pub max_parser_operations: usize,
76
77 pub max_stdout_bytes: usize,
81
82 pub max_stderr_bytes: usize,
86
87 pub capture_final_env: bool,
90}
91
92impl Default for ExecutionLimits {
93 fn default() -> Self {
94 Self {
95 max_commands: 10_000,
96 max_loop_iterations: 10_000,
97 max_total_loop_iterations: 1_000_000,
98 max_function_depth: 100,
99 timeout: Duration::from_secs(30),
100 parser_timeout: Duration::from_secs(5),
101 max_input_bytes: 10_000_000, max_ast_depth: 100,
103 max_parser_operations: 100_000,
104 max_stdout_bytes: 1_048_576, max_stderr_bytes: 1_048_576, capture_final_env: false,
107 }
108 }
109}
110
111impl ExecutionLimits {
112 pub fn new() -> Self {
114 Self::default()
115 }
116
117 pub fn max_commands(mut self, count: usize) -> Self {
119 self.max_commands = count;
120 self
121 }
122
123 pub fn max_loop_iterations(mut self, count: usize) -> Self {
125 self.max_loop_iterations = count;
126 self
127 }
128
129 pub fn max_total_loop_iterations(mut self, count: usize) -> Self {
132 self.max_total_loop_iterations = count;
133 self
134 }
135
136 pub fn max_function_depth(mut self, depth: usize) -> Self {
138 self.max_function_depth = depth;
139 self
140 }
141
142 pub fn timeout(mut self, timeout: Duration) -> Self {
144 self.timeout = timeout;
145 self
146 }
147
148 pub fn parser_timeout(mut self, timeout: Duration) -> Self {
150 self.parser_timeout = timeout;
151 self
152 }
153
154 pub fn max_input_bytes(mut self, bytes: usize) -> Self {
156 self.max_input_bytes = bytes;
157 self
158 }
159
160 pub fn max_ast_depth(mut self, depth: usize) -> Self {
162 self.max_ast_depth = depth;
163 self
164 }
165
166 pub fn max_parser_operations(mut self, ops: usize) -> Self {
168 self.max_parser_operations = ops;
169 self
170 }
171
172 pub fn max_stdout_bytes(mut self, bytes: usize) -> Self {
174 self.max_stdout_bytes = bytes;
175 self
176 }
177
178 pub fn max_stderr_bytes(mut self, bytes: usize) -> Self {
180 self.max_stderr_bytes = bytes;
181 self
182 }
183
184 pub fn capture_final_env(mut self, capture: bool) -> Self {
186 self.capture_final_env = capture;
187 self
188 }
189}
190
191pub const DEFAULT_SESSION_MAX_COMMANDS: u64 = 100_000;
198
199pub const DEFAULT_SESSION_MAX_EXEC_CALLS: u64 = 1_000;
201
202#[derive(Debug, Clone)]
207pub struct SessionLimits {
208 pub max_total_commands: u64,
211
212 pub max_exec_calls: u64,
215}
216
217impl Default for SessionLimits {
218 fn default() -> Self {
219 Self {
220 max_total_commands: DEFAULT_SESSION_MAX_COMMANDS,
221 max_exec_calls: DEFAULT_SESSION_MAX_EXEC_CALLS,
222 }
223 }
224}
225
226impl SessionLimits {
227 pub fn new() -> Self {
229 Self::default()
230 }
231
232 pub fn max_total_commands(mut self, count: u64) -> Self {
234 self.max_total_commands = count;
235 self
236 }
237
238 pub fn max_exec_calls(mut self, count: u64) -> Self {
240 self.max_exec_calls = count;
241 self
242 }
243
244 pub fn unlimited() -> Self {
246 Self {
247 max_total_commands: u64::MAX,
248 max_exec_calls: u64::MAX,
249 }
250 }
251}
252
253#[derive(Debug, Clone, Default)]
255pub struct ExecutionCounters {
256 pub commands: usize,
258
259 pub function_depth: usize,
261
262 pub loop_iterations: usize,
264
265 pub total_loop_iterations: usize,
269
270 pub session_commands: u64,
274
275 pub session_exec_calls: u64,
277}
278
279impl ExecutionCounters {
280 pub fn new() -> Self {
282 Self::default()
283 }
284
285 pub fn reset_for_execution(&mut self) {
289 self.commands = 0;
290 self.loop_iterations = 0;
291 self.total_loop_iterations = 0;
292 self.function_depth = 0;
295 }
296
297 pub fn tick_command(&mut self, limits: &ExecutionLimits) -> Result<(), LimitExceeded> {
299 #[cfg(feature = "failpoints")]
301 fail_point!("limits::tick_command", |action| {
302 match action.as_deref() {
303 Some("skip_increment") => {
304 return Ok(());
306 }
307 Some("force_overflow") => {
308 self.commands = usize::MAX;
310 return Err(LimitExceeded::MaxCommands(limits.max_commands));
311 }
312 Some("corrupt_high") => {
313 self.commands = limits.max_commands + 1;
315 }
316 _ => {}
317 }
318 Ok(())
319 });
320
321 self.commands += 1;
322 self.session_commands += 1;
323 if self.commands > limits.max_commands {
324 return Err(LimitExceeded::MaxCommands(limits.max_commands));
325 }
326 Ok(())
327 }
328
329 pub fn check_session_limits(
331 &self,
332 session_limits: &SessionLimits,
333 ) -> Result<(), LimitExceeded> {
334 if self.session_exec_calls > session_limits.max_exec_calls {
335 return Err(LimitExceeded::SessionMaxExecCalls(
336 session_limits.max_exec_calls,
337 ));
338 }
339 if self.session_commands > session_limits.max_total_commands {
340 return Err(LimitExceeded::SessionMaxCommands(
341 session_limits.max_total_commands,
342 ));
343 }
344 Ok(())
345 }
346
347 pub fn tick_exec_call(&mut self) {
349 self.session_exec_calls += 1;
350 }
351
352 pub fn tick_loop(&mut self, limits: &ExecutionLimits) -> Result<(), LimitExceeded> {
354 #[cfg(feature = "failpoints")]
356 fail_point!("limits::tick_loop", |action| {
357 match action.as_deref() {
358 Some("skip_check") => {
359 self.loop_iterations += 1;
361 return Ok(());
362 }
363 Some("reset_counter") => {
364 self.loop_iterations = 0;
366 return Ok(());
367 }
368 _ => {}
369 }
370 Ok(())
371 });
372
373 self.loop_iterations += 1;
374 self.total_loop_iterations += 1;
375 if self.loop_iterations > limits.max_loop_iterations {
376 return Err(LimitExceeded::MaxLoopIterations(limits.max_loop_iterations));
377 }
378 if self.total_loop_iterations > limits.max_total_loop_iterations {
380 return Err(LimitExceeded::MaxTotalLoopIterations(
381 limits.max_total_loop_iterations,
382 ));
383 }
384 Ok(())
385 }
386
387 pub fn reset_loop(&mut self) {
389 self.loop_iterations = 0;
390 }
391
392 pub fn push_function(&mut self, limits: &ExecutionLimits) -> Result<(), LimitExceeded> {
394 #[cfg(feature = "failpoints")]
396 fail_point!("limits::push_function", |action| {
397 match action.as_deref() {
398 Some("skip_check") => {
399 self.function_depth += 1;
401 return Ok(());
402 }
403 Some("corrupt_depth") => {
404 self.function_depth = 0;
406 return Ok(());
407 }
408 _ => {}
409 }
410 Ok(())
411 });
412
413 if self.function_depth >= limits.max_function_depth {
415 return Err(LimitExceeded::MaxFunctionDepth(limits.max_function_depth));
416 }
417 self.function_depth += 1;
418 Ok(())
419 }
420
421 pub fn pop_function(&mut self) {
423 if self.function_depth > 0 {
424 self.function_depth -= 1;
425 }
426 }
427}
428
429#[derive(Debug, Clone, thiserror::Error)]
431pub enum LimitExceeded {
432 #[error("maximum command count exceeded ({0})")]
433 MaxCommands(usize),
434
435 #[error("maximum loop iterations exceeded ({0})")]
436 MaxLoopIterations(usize),
437
438 #[error("maximum total loop iterations exceeded ({0})")]
439 MaxTotalLoopIterations(usize),
440
441 #[error("maximum function depth exceeded ({0})")]
442 MaxFunctionDepth(usize),
443
444 #[error("execution timeout ({0:?})")]
445 Timeout(Duration),
446
447 #[error("parser timeout ({0:?})")]
448 ParserTimeout(Duration),
449
450 #[error("input too large ({0} bytes, max {1} bytes)")]
451 InputTooLarge(usize, usize),
452
453 #[error("AST nesting too deep ({0} levels, max {1})")]
454 AstTooDeep(usize, usize),
455
456 #[error("parser fuel exhausted ({0} operations, max {1})")]
457 ParserExhausted(usize, usize),
458
459 #[error("session command limit exceeded ({0} total commands)")]
460 SessionMaxCommands(u64),
461
462 #[error("session exec() call limit exceeded ({0} calls)")]
463 SessionMaxExecCalls(u64),
464
465 #[error("memory limit exceeded: {0}")]
466 Memory(String),
467}
468
469pub const DEFAULT_MAX_VARIABLE_COUNT: usize = 10_000;
475pub const DEFAULT_MAX_TOTAL_VARIABLE_BYTES: usize = 10_000_000; pub const DEFAULT_MAX_ARRAY_ENTRIES: usize = 100_000;
479pub const DEFAULT_MAX_FUNCTION_COUNT: usize = 1_000;
481pub const DEFAULT_MAX_FUNCTION_BODY_BYTES: usize = 1_000_000; #[derive(Debug, Clone)]
489pub struct MemoryLimits {
490 pub max_variable_count: usize,
492 pub max_total_variable_bytes: usize,
494 pub max_array_entries: usize,
496 pub max_function_count: usize,
498 pub max_function_body_bytes: usize,
500}
501
502impl Default for MemoryLimits {
503 fn default() -> Self {
504 Self {
505 max_variable_count: DEFAULT_MAX_VARIABLE_COUNT,
506 max_total_variable_bytes: DEFAULT_MAX_TOTAL_VARIABLE_BYTES,
507 max_array_entries: DEFAULT_MAX_ARRAY_ENTRIES,
508 max_function_count: DEFAULT_MAX_FUNCTION_COUNT,
509 max_function_body_bytes: DEFAULT_MAX_FUNCTION_BODY_BYTES,
510 }
511 }
512}
513
514impl MemoryLimits {
515 pub fn new() -> Self {
517 Self::default()
518 }
519
520 pub fn max_variable_count(mut self, count: usize) -> Self {
522 self.max_variable_count = count;
523 self
524 }
525
526 pub fn max_total_variable_bytes(mut self, bytes: usize) -> Self {
528 self.max_total_variable_bytes = bytes;
529 self
530 }
531
532 pub fn max_array_entries(mut self, count: usize) -> Self {
534 self.max_array_entries = count;
535 self
536 }
537
538 pub fn max_function_count(mut self, count: usize) -> Self {
540 self.max_function_count = count;
541 self
542 }
543
544 pub fn max_function_body_bytes(mut self, bytes: usize) -> Self {
546 self.max_function_body_bytes = bytes;
547 self
548 }
549
550 pub fn unlimited() -> Self {
552 Self {
553 max_variable_count: usize::MAX,
554 max_total_variable_bytes: usize::MAX,
555 max_array_entries: usize::MAX,
556 max_function_count: usize::MAX,
557 max_function_body_bytes: usize::MAX,
558 }
559 }
560}
561
562#[derive(Debug, Clone, Default)]
564pub struct MemoryBudget {
565 pub variable_count: usize,
567 pub variable_bytes: usize,
569 pub array_entries: usize,
571 pub function_count: usize,
573 pub function_body_bytes: usize,
575}
576
577impl MemoryBudget {
578 pub fn check_variable_insert(
580 &self,
581 key_len: usize,
582 value_len: usize,
583 is_new: bool,
584 old_key_len: usize,
585 old_value_len: usize,
586 limits: &MemoryLimits,
587 ) -> Result<(), LimitExceeded> {
588 if is_new && self.variable_count >= limits.max_variable_count {
589 return Err(LimitExceeded::Memory(format!(
590 "variable count limit ({}) exceeded",
591 limits.max_variable_count
592 )));
593 }
594 let new_bytes =
595 (self.variable_bytes + key_len + value_len).saturating_sub(old_key_len + old_value_len);
596 if new_bytes > limits.max_total_variable_bytes {
597 return Err(LimitExceeded::Memory(format!(
598 "variable byte limit ({}) exceeded",
599 limits.max_total_variable_bytes
600 )));
601 }
602 Ok(())
603 }
604
605 pub fn record_variable_insert(
607 &mut self,
608 key_len: usize,
609 value_len: usize,
610 is_new: bool,
611 old_key_len: usize,
612 old_value_len: usize,
613 ) {
614 if is_new {
615 self.variable_count += 1;
616 }
617 self.variable_bytes =
618 (self.variable_bytes + key_len + value_len).saturating_sub(old_key_len + old_value_len);
619 }
620
621 pub fn record_variable_remove(&mut self, key_len: usize, value_len: usize) {
623 self.variable_count = self.variable_count.saturating_sub(1);
624 self.variable_bytes = self.variable_bytes.saturating_sub(key_len + value_len);
625 }
626
627 pub fn check_array_entries(
629 &self,
630 additional: usize,
631 limits: &MemoryLimits,
632 ) -> Result<(), LimitExceeded> {
633 if self.array_entries + additional > limits.max_array_entries {
634 return Err(LimitExceeded::Memory(format!(
635 "array entry limit ({}) exceeded",
636 limits.max_array_entries
637 )));
638 }
639 Ok(())
640 }
641
642 pub fn record_array_insert(&mut self, added: usize) {
644 self.array_entries += added;
645 }
646
647 pub fn record_array_remove(&mut self, removed: usize) {
649 self.array_entries = self.array_entries.saturating_sub(removed);
650 }
651
652 pub fn check_function_insert(
654 &self,
655 body_bytes: usize,
656 is_new: bool,
657 old_body_bytes: usize,
658 limits: &MemoryLimits,
659 ) -> Result<(), LimitExceeded> {
660 if is_new && self.function_count >= limits.max_function_count {
661 return Err(LimitExceeded::Memory(format!(
662 "function count limit ({}) exceeded",
663 limits.max_function_count
664 )));
665 }
666 let new_bytes = self.function_body_bytes + body_bytes - old_body_bytes;
667 if new_bytes > limits.max_function_body_bytes {
668 return Err(LimitExceeded::Memory(format!(
669 "function body byte limit ({}) exceeded",
670 limits.max_function_body_bytes
671 )));
672 }
673 Ok(())
674 }
675
676 pub fn record_function_insert(
678 &mut self,
679 body_bytes: usize,
680 is_new: bool,
681 old_body_bytes: usize,
682 ) {
683 if is_new {
684 self.function_count += 1;
685 }
686 self.function_body_bytes =
687 (self.function_body_bytes + body_bytes).saturating_sub(old_body_bytes);
688 }
689
690 pub fn record_function_remove(&mut self, body_bytes: usize) {
692 self.function_count = self.function_count.saturating_sub(1);
693 self.function_body_bytes = self.function_body_bytes.saturating_sub(body_bytes);
694 }
695}
696
697#[cfg(test)]
698mod tests {
699 use super::*;
700
701 #[test]
702 fn test_default_limits() {
703 let limits = ExecutionLimits::default();
704 assert_eq!(limits.max_commands, 10_000);
705 assert_eq!(limits.max_loop_iterations, 10_000);
706 assert_eq!(limits.max_total_loop_iterations, 1_000_000);
707 assert_eq!(limits.max_function_depth, 100);
708 assert_eq!(limits.timeout, Duration::from_secs(30));
709 assert_eq!(limits.parser_timeout, Duration::from_secs(5));
710 assert_eq!(limits.max_input_bytes, 10_000_000);
711 assert_eq!(limits.max_ast_depth, 100);
712 assert_eq!(limits.max_parser_operations, 100_000);
713 assert_eq!(limits.max_stdout_bytes, 1_048_576);
714 assert_eq!(limits.max_stderr_bytes, 1_048_576);
715 assert!(!limits.capture_final_env);
716 }
717
718 #[test]
719 fn test_builder_pattern() {
720 let limits = ExecutionLimits::new()
721 .max_commands(100)
722 .max_loop_iterations(50)
723 .max_function_depth(10)
724 .timeout(Duration::from_secs(5));
725
726 assert_eq!(limits.max_commands, 100);
727 assert_eq!(limits.max_loop_iterations, 50);
728 assert_eq!(limits.max_function_depth, 10);
729 assert_eq!(limits.timeout, Duration::from_secs(5));
730 }
731
732 #[test]
733 fn test_command_counter() {
734 let limits = ExecutionLimits::new().max_commands(5);
735 let mut counters = ExecutionCounters::new();
736
737 for _ in 0..5 {
738 assert!(counters.tick_command(&limits).is_ok());
739 }
740
741 assert!(matches!(
743 counters.tick_command(&limits),
744 Err(LimitExceeded::MaxCommands(5))
745 ));
746 }
747
748 #[test]
749 fn test_loop_counter() {
750 let limits = ExecutionLimits::new().max_loop_iterations(3);
751 let mut counters = ExecutionCounters::new();
752
753 for _ in 0..3 {
754 assert!(counters.tick_loop(&limits).is_ok());
755 }
756
757 assert!(matches!(
759 counters.tick_loop(&limits),
760 Err(LimitExceeded::MaxLoopIterations(3))
761 ));
762
763 counters.reset_loop();
765 assert!(counters.tick_loop(&limits).is_ok());
766 }
767
768 #[test]
769 fn test_total_loop_counter_accumulates() {
770 let limits = ExecutionLimits::new()
771 .max_loop_iterations(5)
772 .max_total_loop_iterations(8);
773 let mut counters = ExecutionCounters::new();
774
775 for _ in 0..5 {
777 assert!(counters.tick_loop(&limits).is_ok());
778 }
779 assert_eq!(counters.total_loop_iterations, 5);
780
781 counters.reset_loop();
783 assert_eq!(counters.loop_iterations, 0);
784 assert_eq!(counters.total_loop_iterations, 5);
786
787 assert!(counters.tick_loop(&limits).is_ok()); assert!(counters.tick_loop(&limits).is_ok()); assert!(counters.tick_loop(&limits).is_ok()); assert!(matches!(
794 counters.tick_loop(&limits),
795 Err(LimitExceeded::MaxTotalLoopIterations(8))
796 ));
797 }
798
799 #[test]
800 fn test_function_depth() {
801 let limits = ExecutionLimits::new().max_function_depth(2);
802 let mut counters = ExecutionCounters::new();
803
804 assert!(counters.push_function(&limits).is_ok());
805 assert!(counters.push_function(&limits).is_ok());
806
807 assert!(matches!(
809 counters.push_function(&limits),
810 Err(LimitExceeded::MaxFunctionDepth(2))
811 ));
812
813 counters.pop_function();
815 assert!(counters.push_function(&limits).is_ok());
816 }
817
818 #[test]
819 fn test_reset_for_execution() {
820 let limits = ExecutionLimits::new().max_commands(5);
821 let mut counters = ExecutionCounters::new();
822
823 for _ in 0..5 {
825 counters.tick_command(&limits).unwrap();
826 }
827 assert!(counters.tick_command(&limits).is_err());
828
829 counters.loop_iterations = 42;
831 counters.total_loop_iterations = 999;
832 counters.function_depth = 3;
833
834 counters.reset_for_execution();
836 assert_eq!(counters.commands, 0);
837 assert_eq!(counters.loop_iterations, 0);
838 assert_eq!(counters.total_loop_iterations, 0);
839 assert_eq!(counters.function_depth, 0);
840
841 assert!(counters.tick_command(&limits).is_ok());
843 }
844}