From d1344636b8d8bb34ff88fc1a283a689cdcf87068 Mon Sep 17 00:00:00 2001 From: Matthias Seitz Date: Fri, 19 Jan 2024 11:45:52 +0100 Subject: [PATCH] fix: apply runtime limits --- src/tracing/js/mod.rs | 48 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 48 insertions(+) diff --git a/src/tracing/js/mod.rs b/src/tracing/js/mod.rs index a2be9ad6..9d773bb4 100644 --- a/src/tracing/js/mod.rs +++ b/src/tracing/js/mod.rs @@ -10,6 +10,7 @@ use crate::tracing::{ types::CallKind, }; use alloy_primitives::{Address, Bytes, B256, U256}; +pub use boa_engine::vm::RuntimeLimits; use boa_engine::{Context, JsError, JsObject, JsResult, JsValue, Source}; use revm::{ interpreter::{ @@ -24,6 +25,17 @@ use tokio::sync::mpsc; pub(crate) mod bindings; pub(crate) mod builtins; +/// The maximum number of iterations in a loop. +/// +/// Once exceeded, the loop will throw an error. +// An empty loop with this limit takes around 50ms to fail. +pub const LOOP_ITERATION_LIMIT: u64 = 200_000; + +/// The recursion limit for function calls. +/// +/// Once exceeded, the function will throw an error. +pub const RECURSION_LIMIT: usize = 10_000; + /// A javascript inspector that will delegate inspector functions to javascript functions /// /// See also @@ -100,6 +112,12 @@ impl JsInspector { ) -> Result { // Instantiate the execution context let mut ctx = Context::default(); + + // Apply the default runtime limits + // This is a safe guard to prevent infinite loops + ctx.runtime_limits_mut().set_loop_iteration_limit(LOOP_ITERATION_LIMIT); + ctx.runtime_limits_mut().set_recursion_limit(RECURSION_LIMIT); + register_builtins(&mut ctx)?; // evaluate the code @@ -179,6 +197,13 @@ impl JsInspector { self.transaction_context = transaction_context; } + /// Applies the runtime limits to the JS context. + /// + /// By default + pub fn set_runtime_limits(&mut self, limits: RuntimeLimits) { + self.ctx.set_runtime_limits(limits); + } + /// Calls the result function and returns the result as [serde_json::Value]. /// /// Note: This is supposed to be called after the inspection has finished. @@ -650,3 +675,26 @@ pub enum JsInspectorError { #[error("invalid JSON config: {0}")] InvalidJsonConfig(JsError), } + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_loop_iteration_limit() { + // Create the JavaScript context. + let mut context = Context::default(); + context.runtime_limits_mut().set_loop_iteration_limit(LOOP_ITERATION_LIMIT); + + // The code below iterates 5 times, so no error is thrown. + let result = context.eval(Source::from_bytes( + r" + let i = 0; + while (true) { + i++; + } + ", + )); + assert!(result.is_err()); + } +}