diff --git a/crates/c-api/src/config.rs b/crates/c-api/src/config.rs index f84bc320cd8d..3e6e313ba9d1 100644 --- a/crates/c-api/src/config.rs +++ b/crates/c-api/src/config.rs @@ -176,8 +176,3 @@ pub extern "C" fn wasmtime_config_static_memory_guard_size_set(c: &mut wasm_conf pub extern "C" fn wasmtime_config_dynamic_memory_guard_size_set(c: &mut wasm_config_t, size: u64) { c.config.dynamic_memory_guard_size(size); } - -#[no_mangle] -pub extern "C" fn wasmtime_config_max_instances_set(c: &mut wasm_config_t, limit: usize) { - c.config.max_instances(limit); -} diff --git a/crates/fuzzing/src/lib.rs b/crates/fuzzing/src/lib.rs index d131149ddb29..7ef338241109 100644 --- a/crates/fuzzing/src/lib.rs +++ b/crates/fuzzing/src/lib.rs @@ -39,9 +39,6 @@ pub fn fuzz_default_config(strategy: wasmtime::Strategy) -> anyhow::Result Store { + Store::new_with_limits( + &engine, + StoreLimitsBuilder::new() + .instances(100) + .tables(100) + .memories(100) + .build(), + ) +} + /// Methods of timing out execution of a WebAssembly module #[derive(Debug)] pub enum Timeout { @@ -91,7 +102,7 @@ pub fn instantiate_with_config( _ => false, }); let engine = Engine::new(&config).unwrap(); - let store = Store::new(&engine); + let store = create_store(&engine); let mut timeout_state = SignalOnDrop::default(); match timeout { @@ -181,7 +192,7 @@ pub fn differential_execution( for config in &configs { let engine = Engine::new(config).unwrap(); - let store = Store::new(&engine); + let store = create_store(&engine); let module = Module::new(&engine, &wasm).unwrap(); @@ -326,7 +337,7 @@ pub fn make_api_calls(api: crate::generators::api::ApiCalls) { ApiCall::StoreNew => { log::trace!("creating store"); assert!(store.is_none()); - store = Some(Store::new(engine.as_ref().unwrap())); + store = Some(create_store(engine.as_ref().unwrap())); } ApiCall::ModuleNew { id, wasm } => { @@ -416,7 +427,7 @@ pub fn spectest(fuzz_config: crate::generators::Config, test: crate::generators: let mut config = fuzz_config.to_wasmtime(); config.wasm_reference_types(false); config.wasm_bulk_memory(false); - let store = Store::new(&Engine::new(&config).unwrap()); + let store = create_store(&Engine::new(&config).unwrap()); if fuzz_config.consume_fuel { store.add_fuel(u64::max_value()).unwrap(); } @@ -440,7 +451,7 @@ pub fn table_ops( let mut config = fuzz_config.to_wasmtime(); config.wasm_reference_types(true); let engine = Engine::new(&config).unwrap(); - let store = Store::new(&engine); + let store = create_store(&engine); if fuzz_config.consume_fuel { store.add_fuel(u64::max_value()).unwrap(); } @@ -555,7 +566,7 @@ pub fn differential_wasmi_execution(wasm: &[u8], config: &crate::generators::Con let mut wasmtime_config = config.to_wasmtime(); wasmtime_config.cranelift_nan_canonicalization(true); let wasmtime_engine = Engine::new(&wasmtime_config).unwrap(); - let wasmtime_store = Store::new(&wasmtime_engine); + let wasmtime_store = create_store(&wasmtime_engine); if config.consume_fuel { wasmtime_store.add_fuel(u64::max_value()).unwrap(); } diff --git a/crates/runtime/src/instance.rs b/crates/runtime/src/instance.rs index c8c19848b37f..4a7fe94b2a53 100644 --- a/crates/runtime/src/instance.rs +++ b/crates/runtime/src/instance.rs @@ -66,6 +66,21 @@ pub trait ResourceLimiter { /// `false` if not permitted. Returning `true` when a maximum has been exceeded will have no /// effect as the table will not grow. fn table_growing(&self, current: u32, desired: u32, maximum: Option) -> bool; + + /// The maximum number of instances that can be created for a `Store`. + /// + /// Module instantiation will fail if this limit is exceeded. + fn instances(&self) -> usize; + + /// The maximum number of tables that can be created for a `Store`. + /// + /// Module instantiation will fail if this limit is exceeded. + fn tables(&self) -> usize; + + /// The maximum number of tables that can be created for a `Store`. + /// + /// Module instantiation will fail if this limit is exceeded. + fn memories(&self) -> usize; } /// Runtime representation of an instance value, which erases all `Instance` @@ -100,9 +115,6 @@ pub(crate) struct Instance { /// Hosts can store arbitrary per-instance information here. host_state: Box, - /// The limiter to use for the instance, if there is one. - limiter: Option>, - /// Additional context used by compiled wasm code. This field is last, and /// represents a dynamically-sized array that extends beyond the nominal /// end of the struct (similar to a flexible array member). @@ -417,15 +429,6 @@ impl Instance { .get(memory_index) .unwrap_or_else(|| panic!("no memory for index {}", memory_index.index())); - if let Some(limiter) = &self.limiter { - let current = memory.size(); - let desired = current.checked_add(delta)?; - - if !limiter.memory_growing(current, desired, memory.maximum()) { - return None; - } - } - let result = unsafe { memory.grow(delta) }; // Keep current the VMContext pointers used by compiled wasm code. @@ -509,15 +512,6 @@ impl Instance { .get(table_index) .unwrap_or_else(|| panic!("no table for index {}", table_index.index())); - if let Some(limiter) = &self.limiter { - let current = table.size(); - let desired = current.checked_add(delta)?; - - if !limiter.table_growing(current, desired, table.maximum()) { - return None; - } - } - let result = unsafe { table.grow(delta, init_value) }; // Keep the `VMContext` pointers used by compiled Wasm code up to diff --git a/crates/runtime/src/instance/allocator.rs b/crates/runtime/src/instance/allocator.rs index 51bb2be899dd..341211636dbb 100644 --- a/crates/runtime/src/instance/allocator.rs +++ b/crates/runtime/src/instance/allocator.rs @@ -61,7 +61,7 @@ pub struct InstanceAllocationRequest<'a> { pub stack_map_registry: *mut StackMapRegistry, /// The resource limiter to use for the instance. - pub limiter: Option>, + pub limiter: Option<&'a Arc>, } /// An link error while instantiating a module. @@ -550,12 +550,15 @@ impl OnDemandInstanceAllocator { Self { mem_creator } } - fn create_tables(module: &Module) -> PrimaryMap { + fn create_tables( + module: &Module, + limiter: &Option<&Arc>, + ) -> PrimaryMap { let num_imports = module.num_imported_tables; let mut tables: PrimaryMap = PrimaryMap::with_capacity(module.table_plans.len() - num_imports); for table in &module.table_plans.values().as_slice()[num_imports..] { - tables.push(Table::new_dynamic(table)); + tables.push(Table::new_dynamic(table, limiter)); } tables } @@ -563,6 +566,7 @@ impl OnDemandInstanceAllocator { fn create_memories( &self, module: &Module, + limiter: &Option<&Arc>, ) -> Result, InstantiationError> { let creator = self .mem_creator @@ -572,8 +576,10 @@ impl OnDemandInstanceAllocator { let mut memories: PrimaryMap = PrimaryMap::with_capacity(module.memory_plans.len() - num_imports); for plan in &module.memory_plans.values().as_slice()[num_imports..] { - memories - .push(Memory::new_dynamic(plan, creator).map_err(InstantiationError::Resource)?); + memories.push( + Memory::new_dynamic(plan, creator, limiter) + .map_err(InstantiationError::Resource)?, + ); } Ok(memories) } @@ -584,11 +590,10 @@ unsafe impl InstanceAllocator for OnDemandInstanceAllocator { &self, mut req: InstanceAllocationRequest, ) -> Result { - let memories = self.create_memories(&req.module)?; - let tables = Self::create_tables(&req.module); + let memories = self.create_memories(&req.module, &req.limiter)?; + let tables = Self::create_tables(&req.module, &req.limiter); let host_state = std::mem::replace(&mut req.host_state, Box::new(())); - let limiter = std::mem::take(&mut req.limiter); let handle = { let instance = Instance { @@ -601,7 +606,6 @@ unsafe impl InstanceAllocator for OnDemandInstanceAllocator { )), dropped_data: RefCell::new(EntitySet::with_capacity(req.module.passive_data.len())), host_state, - limiter, vmctx: VMContext {}, }; let layout = instance.alloc_layout(); diff --git a/crates/runtime/src/instance/allocator/pooling.rs b/crates/runtime/src/instance/allocator/pooling.rs index 51923af9e782..87a77a80da26 100644 --- a/crates/runtime/src/instance/allocator/pooling.rs +++ b/crates/runtime/src/instance/allocator/pooling.rs @@ -9,7 +9,7 @@ use super::{ initialize_instance, initialize_vmcontext, FiberStackError, InstanceAllocationRequest, - InstanceAllocator, InstanceHandle, InstantiationError, + InstanceAllocator, InstanceHandle, InstantiationError, ResourceLimiter, }; use crate::{instance::Instance, Memory, Mmap, Table, VMContext}; use anyhow::{anyhow, bail, Context, Result}; @@ -367,7 +367,6 @@ impl InstancePool { dropped_elements: RefCell::new(EntitySet::new()), dropped_data: RefCell::new(EntitySet::new()), host_state: Box::new(()), - limiter: None, vmctx: VMContext {}, }, ); @@ -389,7 +388,6 @@ impl InstancePool { }; let host_state = std::mem::replace(&mut req.host_state, Box::new(())); - let limiter = std::mem::take(&mut req.limiter); unsafe { let instance = self.instance(index); @@ -400,14 +398,20 @@ impl InstancePool { instance.module.as_ref(), ); instance.host_state = host_state; - instance.limiter = limiter; Self::set_instance_memories( instance, self.memories.get(index), self.memories.max_wasm_pages, + &req.limiter, + )?; + + Self::set_instance_tables( + instance, + self.tables.get(index), + self.tables.max_elements, + &req.limiter, )?; - Self::set_instance_tables(instance, self.tables.get(index), self.tables.max_elements)?; initialize_vmcontext(instance, req); @@ -465,9 +469,8 @@ impl InstancePool { instance.tables.clear(); instance.dropped_elements.borrow_mut().clear(); - // Drop any host state and limiter + // Drop any host state instance.host_state = Box::new(()); - instance.limiter = None; self.free_list.lock().unwrap().push(index); } @@ -476,6 +479,7 @@ impl InstancePool { instance: &mut Instance, mut memories: impl Iterator, max_pages: u32, + limiter: &Option<&Arc>, ) -> Result<(), InstantiationError> { let module = instance.module.as_ref(); @@ -490,6 +494,7 @@ impl InstancePool { memories.next().unwrap(), max_pages, commit_memory_pages, + limiter, ) .map_err(InstantiationError::Resource)?, ); @@ -506,6 +511,7 @@ impl InstancePool { instance: &mut Instance, mut tables: impl Iterator, max_elements: u32, + limiter: &Option<&Arc>, ) -> Result<(), InstantiationError> { let module = instance.module.as_ref(); @@ -519,7 +525,7 @@ impl InstancePool { instance .tables - .push(Table::new_static(plan, base as _, max_elements)); + .push(Table::new_static(plan, base as _, max_elements, limiter)); } let mut dropped_elements = instance.dropped_elements.borrow_mut(); diff --git a/crates/runtime/src/memory.rs b/crates/runtime/src/memory.rs index bc3aa8b63a08..a8dea35c62e5 100644 --- a/crates/runtime/src/memory.rs +++ b/crates/runtime/src/memory.rs @@ -4,12 +4,14 @@ use crate::mmap::Mmap; use crate::vmcontext::VMMemoryDefinition; +use crate::ResourceLimiter; use anyhow::Result; use more_asserts::{assert_ge, assert_le}; use std::cell::{Cell, RefCell}; use std::cmp::min; use std::convert::TryFrom; use std::ptr; +use std::sync::Arc; use wasmtime_environ::{MemoryPlan, MemoryStyle, WASM_MAX_PAGES, WASM_PAGE_SIZE}; /// A memory allocator @@ -199,12 +201,22 @@ enum MemoryStorage { } /// Represents an instantiation of a WebAssembly memory. -pub struct Memory(MemoryStorage); +pub struct Memory { + storage: MemoryStorage, + limiter: Option>, +} impl Memory { /// Create a new dynamic (movable) memory instance for the specified plan. - pub fn new_dynamic(plan: &MemoryPlan, creator: &dyn RuntimeMemoryCreator) -> Result { - Ok(Self(MemoryStorage::Dynamic(creator.new_memory(plan)?))) + pub fn new_dynamic( + plan: &MemoryPlan, + creator: &dyn RuntimeMemoryCreator, + limiter: &Option<&Arc>, + ) -> Result { + Ok(Self { + storage: MemoryStorage::Dynamic(creator.new_memory(plan)?), + limiter: limiter.cloned(), + }) } /// Create a new static (immovable) memory instance for the specified plan. @@ -213,33 +225,41 @@ impl Memory { base: *mut u8, maximum: u32, make_accessible: fn(*mut u8, usize) -> Result<()>, + limiter: &Option<&Arc>, ) -> Result { if plan.memory.minimum > 0 { make_accessible(base, plan.memory.minimum as usize * WASM_PAGE_SIZE as usize)?; } - Ok(Self(MemoryStorage::Static { - base, - size: Cell::new(plan.memory.minimum), - maximum: min(plan.memory.maximum.unwrap_or(maximum), maximum), - make_accessible, - #[cfg(all(feature = "uffd", target_os = "linux"))] - guard_page_faults: RefCell::new(Vec::new()), - })) + Ok(Self { + storage: MemoryStorage::Static { + base, + size: Cell::new(plan.memory.minimum), + maximum: min(plan.memory.maximum.unwrap_or(maximum), maximum), + make_accessible, + #[cfg(all(feature = "uffd", target_os = "linux"))] + guard_page_faults: RefCell::new(Vec::new()), + }, + limiter: limiter.cloned(), + }) } /// Returns the number of allocated wasm pages. pub fn size(&self) -> u32 { - match &self.0 { + match &self.storage { MemoryStorage::Static { size, .. } => size.get(), MemoryStorage::Dynamic(mem) => mem.size(), } } - /// Returns the maximum number of pages the memory can grow to. + /// Returns the maximum number of pages the memory can grow to at runtime. + /// /// Returns `None` if the memory is unbounded. + /// + /// The runtime maximum may not be equal to the maximum from the linear memory's + /// Wasm type when it is being constrained by an instance allocator. pub fn maximum(&self) -> Option { - match &self.0 { + match &self.storage { MemoryStorage::Static { maximum, .. } => Some(*maximum), MemoryStorage::Dynamic(mem) => mem.maximum(), } @@ -247,7 +267,7 @@ impl Memory { /// Returns whether or not the underlying storage of the memory is "static". pub(crate) fn is_static(&self) -> bool { - if let MemoryStorage::Static { .. } = &self.0 { + if let MemoryStorage::Static { .. } = &self.storage { true } else { false @@ -268,7 +288,16 @@ impl Memory { /// Generally, prefer using `InstanceHandle::memory_grow`, which encapsulates /// this unsafety. pub unsafe fn grow(&self, delta: u32) -> Option { - match &self.0 { + if let Some(limiter) = &self.limiter { + let current = self.size(); + let desired = current.checked_add(delta)?; + + if !limiter.memory_growing(current, desired, self.maximum()) { + return None; + } + } + + match &self.storage { MemoryStorage::Static { base, size, @@ -306,7 +335,7 @@ impl Memory { /// Return a `VMMemoryDefinition` for exposing the memory to compiled wasm code. pub fn vmmemory(&self) -> VMMemoryDefinition { - match &self.0 { + match &self.storage { MemoryStorage::Static { base, size, .. } => VMMemoryDefinition { base: *base, current_length: size.get() as usize * WASM_PAGE_SIZE as usize, @@ -327,7 +356,7 @@ impl Memory { size: usize, reset: fn(*mut u8, usize) -> Result<()>, ) { - match &self.0 { + match &self.storage { MemoryStorage::Static { guard_page_faults, .. } => { @@ -348,7 +377,7 @@ impl Memory { /// This function will panic if called on a dynamic memory. #[cfg(all(feature = "uffd", target_os = "linux"))] pub(crate) fn reset_guard_pages(&self) -> Result<()> { - match &self.0 { + match &self.storage { MemoryStorage::Static { guard_page_faults, .. } => { @@ -373,13 +402,16 @@ impl Default for Memory { unreachable!() } - Self(MemoryStorage::Static { - base: ptr::null_mut(), - size: Cell::new(0), - maximum: 0, - make_accessible, - #[cfg(all(feature = "uffd", target_os = "linux"))] - guard_page_faults: RefCell::new(Vec::new()), - }) + Self { + storage: MemoryStorage::Static { + base: ptr::null_mut(), + size: Cell::new(0), + maximum: 0, + make_accessible, + #[cfg(all(feature = "uffd", target_os = "linux"))] + guard_page_faults: RefCell::new(Vec::new()), + }, + limiter: None, + } } } diff --git a/crates/runtime/src/table.rs b/crates/runtime/src/table.rs index 8c857add455a..7be92eb1d133 100644 --- a/crates/runtime/src/table.rs +++ b/crates/runtime/src/table.rs @@ -3,19 +3,20 @@ //! `Table` is to WebAssembly tables what `LinearMemory` is to WebAssembly linear memories. use crate::vmcontext::{VMCallerCheckedAnyfunc, VMTableDefinition}; -use crate::{Trap, VMExternRef}; +use crate::{ResourceLimiter, Trap, VMExternRef}; use std::cell::{Cell, RefCell}; use std::cmp::min; use std::convert::TryInto; use std::ops::Range; use std::ptr; +use std::sync::Arc; use wasmtime_environ::wasm::TableElementType; use wasmtime_environ::{ir, TablePlan}; /// An element going into or coming out of a table. /// /// Table elements are stored as pointers and are default-initialized with `ptr::null_mut`. -#[derive(Clone, Debug)] +#[derive(Clone)] pub enum TableElement { /// A `funcref`. FuncRef(*mut VMCallerCheckedAnyfunc), @@ -92,7 +93,6 @@ impl From for TableElement { } } -#[derive(Debug)] enum TableStorage { Static { data: *mut *mut u8, @@ -108,38 +108,51 @@ enum TableStorage { } /// Represents an instance's table. -#[derive(Debug)] -pub struct Table(TableStorage); +pub struct Table { + storage: TableStorage, + limiter: Option>, +} impl Table { /// Create a new dynamic (movable) table instance for the specified table plan. - pub fn new_dynamic(plan: &TablePlan) -> Self { + pub fn new_dynamic(plan: &TablePlan, limiter: &Option<&Arc>) -> Self { let elements = RefCell::new(vec![ptr::null_mut(); plan.table.minimum as usize]); let ty = plan.table.ty.clone(); let maximum = plan.table.maximum; - Self(TableStorage::Dynamic { - elements, - ty, - maximum, - }) + Self { + storage: TableStorage::Dynamic { + elements, + ty, + maximum, + }, + limiter: limiter.cloned(), + } } /// Create a new static (immovable) table instance for the specified table plan. - pub fn new_static(plan: &TablePlan, data: *mut *mut u8, maximum: u32) -> Self { + pub fn new_static( + plan: &TablePlan, + data: *mut *mut u8, + maximum: u32, + limiter: &Option<&Arc>, + ) -> Self { let size = Cell::new(plan.table.minimum); let ty = plan.table.ty.clone(); let maximum = min(plan.table.maximum.unwrap_or(maximum), maximum); - Self(TableStorage::Static { - data, - size, - ty, - maximum, - }) + Self { + storage: TableStorage::Static { + data, + size, + ty, + maximum, + }, + limiter: limiter.cloned(), + } } /// Returns the type of the elements in this table. pub fn element_type(&self) -> TableElementType { - match &self.0 { + match &self.storage { TableStorage::Static { ty, .. } => *ty, TableStorage::Dynamic { ty, .. } => *ty, } @@ -147,7 +160,7 @@ impl Table { /// Returns whether or not the underlying storage of the table is "static". pub(crate) fn is_static(&self) -> bool { - if let TableStorage::Static { .. } = &self.0 { + if let TableStorage::Static { .. } = &self.storage { true } else { false @@ -156,15 +169,20 @@ impl Table { /// Returns the number of allocated elements. pub fn size(&self) -> u32 { - match &self.0 { + match &self.storage { TableStorage::Static { size, .. } => size.get(), TableStorage::Dynamic { elements, .. } => elements.borrow().len().try_into().unwrap(), } } - /// Returns the maximum number of elements. + /// Returns the maximum number of elements at runtime. + /// + /// Returns `None` if the table is unbounded. + /// + /// The runtime maximum may not be equal to the maximum from the table's Wasm type + /// when it is being constrained by an instance allocator. pub fn maximum(&self) -> Option { - match &self.0 { + match &self.storage { TableStorage::Static { maximum, .. } => Some(*maximum), TableStorage::Dynamic { maximum, .. } => maximum.clone(), } @@ -217,6 +235,15 @@ impl Table { /// Generally, prefer using `InstanceHandle::table_grow`, which encapsulates /// this unsafety. pub unsafe fn grow(&self, delta: u32, init_value: TableElement) -> Option { + if let Some(limiter) = &self.limiter { + let current = self.size(); + let desired = current.checked_add(delta)?; + + if !limiter.table_growing(current, desired, self.maximum()) { + return None; + } + } + let old_size = self.size(); let new_size = old_size.checked_add(delta)?; @@ -229,7 +256,7 @@ impl Table { debug_assert!(self.type_matches(&init_value)); // First resize the storage and then fill with the init value - match &self.0 { + match &self.storage { TableStorage::Static { size, .. } => { size.set(new_size); } @@ -319,7 +346,7 @@ impl Table { /// Return a `VMTableDefinition` for exposing the table to compiled wasm code. pub fn vmtable(&self) -> VMTableDefinition { - match &self.0 { + match &self.storage { TableStorage::Static { data, size, .. } => VMTableDefinition { base: *data as _, current_elements: size.get(), @@ -346,7 +373,7 @@ impl Table { where F: FnOnce(&[*mut u8]) -> R, { - match &self.0 { + match &self.storage { TableStorage::Static { data, size, .. } => unsafe { f(std::slice::from_raw_parts(*data, size.get() as usize)) }, @@ -361,7 +388,7 @@ impl Table { where F: FnOnce(&mut [*mut u8]) -> R, { - match &self.0 { + match &self.storage { TableStorage::Static { data, size, .. } => unsafe { f(std::slice::from_raw_parts_mut(*data, size.get() as usize)) }, @@ -463,11 +490,14 @@ impl Drop for Table { // The default table representation is an empty funcref table that cannot grow. impl Default for Table { fn default() -> Self { - Self(TableStorage::Static { - data: std::ptr::null_mut(), - size: Cell::new(0), - ty: TableElementType::Func, - maximum: 0, - }) + Self { + storage: TableStorage::Static { + data: std::ptr::null_mut(), + size: Cell::new(0), + ty: TableElementType::Func, + maximum: 0, + }, + limiter: None, + } } } diff --git a/crates/wasmtime/src/config.rs b/crates/wasmtime/src/config.rs index da85f832b8cd..e45ce14e2641 100644 --- a/crates/wasmtime/src/config.rs +++ b/crates/wasmtime/src/config.rs @@ -378,9 +378,6 @@ pub struct Config { pub(crate) max_wasm_stack: usize, pub(crate) features: WasmFeatures, pub(crate) wasm_backtrace_details_env_used: bool, - pub(crate) max_instances: usize, - pub(crate) max_tables: usize, - pub(crate) max_memories: usize, #[cfg(feature = "async")] pub(crate) async_stack_size: usize, host_funcs: HostFuncMap, @@ -432,9 +429,6 @@ impl Config { multi_value: true, ..WasmFeatures::default() }, - max_instances: 10_000, - max_tables: 10_000, - max_memories: 10_000, #[cfg(feature = "async")] async_stack_size: 2 << 20, host_funcs: HostFuncMap::new(), @@ -1167,39 +1161,6 @@ impl Config { self } - /// Configures the maximum number of instances which can be created within - /// this `Store`. - /// - /// Instantiation will fail with an error if this limit is exceeded. - /// - /// This value defaults to 10,000. - pub fn max_instances(&mut self, instances: usize) -> &mut Self { - self.max_instances = instances; - self - } - - /// Configures the maximum number of tables which can be created within - /// this `Store`. - /// - /// Instantiation will fail with an error if this limit is exceeded. - /// - /// This value defaults to 10,000. - pub fn max_tables(&mut self, tables: usize) -> &mut Self { - self.max_tables = tables; - self - } - - /// Configures the maximum number of memories which can be created within - /// this `Store`. - /// - /// Instantiation will fail with an error if this limit is exceeded. - /// - /// This value defaults to 10,000. - pub fn max_memories(&mut self, memories: usize) -> &mut Self { - self.max_memories = memories; - self - } - /// Defines a host function for the [`Config`] for the given callback. /// /// Use [`Store::get_host_func`](crate::Store::get_host_func) to get a [`Func`](crate::Func) representing the function. diff --git a/crates/wasmtime/src/instance.rs b/crates/wasmtime/src/instance.rs index 66c4dd42c132..8f5e5db0eefa 100644 --- a/crates/wasmtime/src/instance.rs +++ b/crates/wasmtime/src/instance.rs @@ -528,7 +528,7 @@ impl<'a> Instantiator<'a> { as *mut _, stack_map_registry: self.store.stack_map_registry() as *const StackMapRegistry as *mut _, - limiter: self.store.limiter(), + limiter: self.store.limiter().as_ref(), })?; // After we've created the `InstanceHandle` we still need to run diff --git a/crates/wasmtime/src/lib.rs b/crates/wasmtime/src/lib.rs index 97cb8ac60722..97ccbb24285b 100644 --- a/crates/wasmtime/src/lib.rs +++ b/crates/wasmtime/src/lib.rs @@ -280,7 +280,7 @@ mod engine; mod externals; mod frame_info; mod instance; -mod limiter; +mod limits; mod linker; mod memory; mod module; @@ -298,7 +298,7 @@ pub use crate::externals::*; pub use crate::frame_info::{FrameInfo, FrameSymbol}; pub use crate::func::*; pub use crate::instance::Instance; -pub use crate::limiter::*; +pub use crate::limits::*; pub use crate::linker::*; pub use crate::memory::*; pub use crate::module::Module; diff --git a/crates/wasmtime/src/limiter.rs b/crates/wasmtime/src/limiter.rs deleted file mode 100644 index 31751fc30741..000000000000 --- a/crates/wasmtime/src/limiter.rs +++ /dev/null @@ -1,129 +0,0 @@ -use crate::{Store, StoreInner}; -use std::rc::Weak; - -/// Used by hosts to limit resource consumption of instances. -/// -/// A [`Store`] can be created with a resource limiter so that hosts can take into account -/// non-WebAssembly resource usage to determine if a linear memory or table should grow. -pub trait ResourceLimiter { - /// Notifies the resource limiter that an instance's linear memory has been requested to grow. - /// - /// * `current` is the current size of the linear memory in WebAssembly page units. - /// * `desired` is the desired size of the linear memory in WebAssembly page units. - /// * `maximum` is either the linear memory's maximum or a maximum from an instance allocator, - /// also in WebAssembly page units. A value of `None` indicates that the linear memory is - /// unbounded. - /// - /// This function should return `true` to indicate that the growing operation is permitted or - /// `false` if not permitted. Returning `true` when a maximum has been exceeded will have no - /// effect as the linear memory will not grow. - fn memory_growing( - &self, - store: &Store, - current: u32, - desired: u32, - maximum: Option, - ) -> bool; - - /// Notifies the resource limiter that an instance's table has been requested to grow. - /// - /// * `current` is the current number of elements in the table. - /// * `desired` is the desired number of elements in the table. - /// * `maximum` is either the table's maximum or a maximum from an instance allocator, - /// A value of `None` indicates that the table is unbounded. - /// - /// This function should return `true` to indicate that the growing operation is permitted or - /// `false` if not permitted. Returning `true` when a maximum has been exceeded will have no - /// effect as the table will not grow. - fn table_growing( - &self, - store: &Store, - current: u32, - desired: u32, - maximum: Option, - ) -> bool; -} - -pub(crate) struct ResourceLimiterProxy { - store: Weak, - limiter: Box, -} - -impl ResourceLimiterProxy { - pub(crate) fn new(store: &Store, limiter: impl ResourceLimiter + 'static) -> Self { - Self { - store: store.weak(), - limiter: Box::new(limiter), - } - } -} - -impl wasmtime_runtime::ResourceLimiter for ResourceLimiterProxy { - fn memory_growing(&self, current: u32, desired: u32, maximum: Option) -> bool { - self.limiter.memory_growing( - &Store::upgrade(&self.store).unwrap(), - current, - desired, - maximum, - ) - } - - fn table_growing(&self, current: u32, desired: u32, maximum: Option) -> bool { - self.limiter.table_growing( - &Store::upgrade(&self.store).unwrap(), - current, - desired, - maximum, - ) - } -} - -/// A resource limiter that statically limits how much memories and tables can grow. -pub struct StaticResourceLimiter { - memory_limit: Option, - table_limit: Option, -} - -impl StaticResourceLimiter { - /// Creates a new [`StaticResourceLimiter`]. - /// - /// The `memory_limit` parameter is the number of WebAssembly pages a linear memory can grow to. - /// If `None`, the limiter will not limit linear memory growth. - /// - /// The `table_limit` parameter is the number of elements a table can grow to. - /// If `None`, the limiter will not limit table growth. - pub fn new(memory_limit: Option, table_limit: Option) -> Self { - Self { - memory_limit, - table_limit, - } - } -} - -impl ResourceLimiter for StaticResourceLimiter { - fn memory_growing( - &self, - _store: &Store, - _current: u32, - desired: u32, - _maximum: Option, - ) -> bool { - match self.memory_limit { - Some(limit) if desired > limit => false, - _ => true, - } - } - - fn table_growing( - &self, - _store: &Store, - _current: u32, - desired: u32, - _maximum: Option, - ) -> bool { - match self.table_limit { - Some(limit) if desired > limit => false, - _ => true, - } - } -} diff --git a/crates/wasmtime/src/limits.rs b/crates/wasmtime/src/limits.rs new file mode 100644 index 000000000000..0406f6bbbc23 --- /dev/null +++ b/crates/wasmtime/src/limits.rs @@ -0,0 +1,256 @@ +use crate::{Store, StoreInner}; +use std::rc::Weak; + +pub(crate) const DEFAULT_INSTANCE_LIMIT: usize = 10000; +pub(crate) const DEFAULT_TABLE_LIMIT: usize = 10000; +pub(crate) const DEFAULT_MEMORY_LIMIT: usize = 10000; + +/// Used by hosts to limit resource consumption of instances at runtime. +/// +/// A [`Store`] can be created with a resource limiter so that hosts can take into account +/// non-WebAssembly resource usage to determine if a linear memory or table should grow. +pub trait ResourceLimiter { + /// Notifies the resource limiter that an instance's linear memory has been requested to grow. + /// + /// * `current` is the current size of the linear memory in WebAssembly page units. + /// * `desired` is the desired size of the linear memory in WebAssembly page units. + /// * `maximum` is either the linear memory's maximum or a maximum from an instance allocator, + /// also in WebAssembly page units. A value of `None` indicates that the linear memory is + /// unbounded. + /// + /// This function should return `true` to indicate that the growing operation is permitted or + /// `false` if not permitted. + /// + /// Note that this function will be called even when the desired count exceeds the given maximum. + /// + /// Returning `true` when a maximum has been exceeded will have no effect as the linear memory + /// will not be grown. + fn memory_growing( + &self, + store: &Store, + current: u32, + desired: u32, + maximum: Option, + ) -> bool; + + /// Notifies the resource limiter that an instance's table has been requested to grow. + /// + /// * `current` is the current number of elements in the table. + /// * `desired` is the desired number of elements in the table. + /// * `maximum` is either the table's maximum or a maximum from an instance allocator, + /// A value of `None` indicates that the table is unbounded. + /// + /// This function should return `true` to indicate that the growing operation is permitted or + /// `false` if not permitted. + /// + /// Note that this function will be called even when the desired count exceeds the given maximum. + /// + /// Returning `true` when a maximum has been exceeded will have no effect as the table will + /// not be grown. + fn table_growing( + &self, + store: &Store, + current: u32, + desired: u32, + maximum: Option, + ) -> bool; + + /// The maximum number of instances that can be created for a `Store`. + /// + /// Module instantiation will fail if this limit is exceeded. + /// + /// This value defaults to 10,000. + fn instances(&self) -> usize { + DEFAULT_INSTANCE_LIMIT + } + + /// The maximum number of tables that can be created for a `Store`. + /// + /// Module instantiation will fail if this limit is exceeded. + /// + /// This value defaults to 10,000. + fn tables(&self) -> usize { + DEFAULT_TABLE_LIMIT + } + + /// The maximum number of linear memories that can be created for a `Store`. + /// + /// Instantiation will fail with an error if this limit is exceeded. + /// + /// This value defaults to 10,000. + fn memories(&self) -> usize { + DEFAULT_MEMORY_LIMIT + } +} + +pub(crate) struct ResourceLimiterProxy { + store: Weak, + limiter: T, +} + +impl ResourceLimiterProxy { + pub(crate) fn new(store: &Store, limiter: T) -> Self { + Self { + store: store.weak(), + limiter, + } + } +} + +impl wasmtime_runtime::ResourceLimiter for ResourceLimiterProxy { + fn memory_growing(&self, current: u32, desired: u32, maximum: Option) -> bool { + self.limiter.memory_growing( + &Store::upgrade(&self.store).unwrap(), + current, + desired, + maximum, + ) + } + + fn table_growing(&self, current: u32, desired: u32, maximum: Option) -> bool { + self.limiter.table_growing( + &Store::upgrade(&self.store).unwrap(), + current, + desired, + maximum, + ) + } + + fn instances(&self) -> usize { + self.limiter.instances() + } + + fn tables(&self) -> usize { + self.limiter.tables() + } + + fn memories(&self) -> usize { + self.limiter.memories() + } +} + +/// Used to build [`StoreLimits`]. +pub struct StoreLimitsBuilder(StoreLimits); + +impl StoreLimitsBuilder { + /// Creates a new [`StoreLimitsBuilder`]. + pub fn new() -> Self { + Self(StoreLimits::default()) + } + + /// The maximum number of WebAssembly pages a linear memory can grow to. + /// + /// Growing a linear memory beyond this limit will fail. + /// + /// By default, linear memory pages will not be limited. + pub fn memory_pages(mut self, limit: u32) -> Self { + self.0.memory_pages = Some(limit); + self + } + + /// The maximum number of elements in a table. + /// + /// Growing a table beyond this limit will fail. + /// + /// By default, table elements will not be limited. + pub fn table_elements(mut self, limit: u32) -> Self { + self.0.table_elements = Some(limit); + self + } + + /// The maximum number of instances that can be created for a `Store`. + /// + /// Module instantiation will fail if this limit is exceeded. + /// + /// This value defaults to 10,000. + pub fn instances(mut self, limit: usize) -> Self { + self.0.instances = limit; + self + } + + /// The maximum number of tables that can be created for a `Store`. + /// + /// Module instantiation will fail if this limit is exceeded. + /// + /// This value defaults to 10,000. + pub fn tables(mut self, tables: usize) -> Self { + self.0.tables = tables; + self + } + + /// The maximum number of linear memories that can be created for a `Store`. + /// + /// Instantiation will fail with an error if this limit is exceeded. + /// + /// This value defaults to 10,000. + pub fn memories(mut self, memories: usize) -> Self { + self.0.memories = memories; + self + } + + /// Consumes this builder and returns the [`StoreLimits`]. + pub fn build(self) -> StoreLimits { + self.0 + } +} + +/// Provides limits for a [`Store`]. +pub struct StoreLimits { + memory_pages: Option, + table_elements: Option, + instances: usize, + tables: usize, + memories: usize, +} + +impl Default for StoreLimits { + fn default() -> Self { + Self { + memory_pages: None, + table_elements: None, + instances: DEFAULT_INSTANCE_LIMIT, + tables: DEFAULT_TABLE_LIMIT, + memories: DEFAULT_MEMORY_LIMIT, + } + } +} + +impl ResourceLimiter for StoreLimits { + fn memory_growing( + &self, + _store: &Store, + _current: u32, + desired: u32, + _maximum: Option, + ) -> bool { + match self.memory_pages { + Some(limit) if desired > limit => false, + _ => true, + } + } + + fn table_growing( + &self, + _store: &Store, + _current: u32, + desired: u32, + _maximum: Option, + ) -> bool { + match self.table_elements { + Some(limit) if desired > limit => false, + _ => true, + } + } + + fn instances(&self) -> usize { + self.instances + } + + fn tables(&self) -> usize { + self.tables + } + + fn memories(&self) -> usize { + self.memories + } +} diff --git a/crates/wasmtime/src/store.rs b/crates/wasmtime/src/store.rs index 0b24d6f0b7f7..1e91e7801d88 100644 --- a/crates/wasmtime/src/store.rs +++ b/crates/wasmtime/src/store.rs @@ -1,10 +1,12 @@ -use crate::frame_info::StoreFrameInfo; use crate::sig_registry::SignatureRegistry; use crate::trampoline::StoreInstanceHandle; +use crate::{ + frame_info::StoreFrameInfo, DEFAULT_INSTANCE_LIMIT, DEFAULT_MEMORY_LIMIT, DEFAULT_TABLE_LIMIT, +}; use crate::{Engine, Func, FuncType, Module, ResourceLimiter, ResourceLimiterProxy, Trap}; use anyhow::{bail, Result}; use std::any::{Any, TypeId}; -use std::cell::{Cell, RefCell}; +use std::cell::{Cell, Ref, RefCell}; use std::collections::{hash_map::Entry, HashMap, HashSet}; use std::convert::TryFrom; use std::fmt; @@ -166,9 +168,17 @@ impl Store { } } - /// Creates a new store to be associated with the given [`Engine`] and using the given - /// resource limiter for any instances created in the store. - pub fn new_with_limiter(engine: &Engine, limiter: impl ResourceLimiter + 'static) -> Self { + /// Creates a new store to be associated with the given [`Engine`] and using the supplied + /// resource limiter. + /// + /// # Example + /// + /// ```rust + /// # use wasmtime::{Engine, Store, StoreLimitsBuilder}; + /// let engine = Engine::default(); + /// let store = Store::new_with_limits(&engine, StoreLimitsBuilder::new().instances(10).build()); + /// ``` + pub fn new_with_limits(engine: &Engine, limiter: impl ResourceLimiter + 'static) -> Self { let store = Store::new(engine); { @@ -256,8 +266,8 @@ impl Store { } } - pub(crate) fn limiter(&self) -> Option> { - self.inner.limiter.borrow().clone() + pub(crate) fn limiter(&self) -> Ref>> { + self.inner.limiter.borrow() } pub(crate) fn signatures(&self) -> &RefCell { @@ -344,8 +354,6 @@ impl Store { } pub(crate) fn bump_resource_counts(&self, module: &Module) -> Result<()> { - let config = self.engine().config(); - fn bump(slot: &Cell, max: usize, amt: usize, desc: &str) -> Result<()> { let new = slot.get().saturating_add(amt); if new > max { @@ -363,19 +371,11 @@ impl Store { let memories = module.memory_plans.len() - module.num_imported_memories; let tables = module.table_plans.len() - module.num_imported_tables; - bump( - &self.inner.instance_count, - config.max_instances, - 1, - "instance", - )?; - bump( - &self.inner.memory_count, - config.max_memories, - memories, - "memory", - )?; - bump(&self.inner.table_count, config.max_tables, tables, "table")?; + let (max_instances, max_memories, max_tables) = self.limits(); + + bump(&self.inner.instance_count, max_instances, 1, "instance")?; + bump(&self.inner.memory_count, max_memories, memories, "memory")?; + bump(&self.inner.table_count, max_tables, tables, "table")?; Ok(()) } @@ -962,6 +962,19 @@ impl Store { Err(trap) => unsafe { wasmtime_runtime::raise_user_trap(trap.into()) }, } } + + fn limits(&self) -> (usize, usize, usize) { + let limiter = self.inner.limiter.borrow(); + + limiter + .as_ref() + .map(|l| (l.instances(), l.memories(), l.tables())) + .unwrap_or(( + DEFAULT_INSTANCE_LIMIT, + DEFAULT_MEMORY_LIMIT, + DEFAULT_TABLE_LIMIT, + )) + } } unsafe impl TrapInfo for Store { diff --git a/crates/wasmtime/src/trampoline.rs b/crates/wasmtime/src/trampoline.rs index 5e76b05dd475..11cd9cde51f2 100644 --- a/crates/wasmtime/src/trampoline.rs +++ b/crates/wasmtime/src/trampoline.rs @@ -77,7 +77,7 @@ fn create_handle( as *const VMExternRefActivationsTable as *mut _, stack_map_registry: store.stack_map_registry() as *const StackMapRegistry as *mut _, - limiter: None, + limiter: store.limiter().as_ref(), })?; Ok(store.add_instance(handle, true)) diff --git a/tests/all/limiter.rs b/tests/all/limits.rs similarity index 91% rename from tests/all/limiter.rs rename to tests/all/limits.rs index 27956fad093d..2ce2d781faf0 100644 --- a/tests/all/limiter.rs +++ b/tests/all/limits.rs @@ -4,14 +4,20 @@ use std::rc::Rc; use wasmtime::*; #[test] -fn test_static_limiter() -> Result<()> { +fn test_limits() -> Result<()> { let engine = Engine::default(); let module = Module::new( &engine, r#"(module (memory (export "m") 0) (table (export "t") 0 anyfunc))"#, )?; - let store = Store::new_with_limiter(&engine, StaticResourceLimiter::new(Some(10), Some(5))); + let store = Store::new_with_limits( + &engine, + StoreLimitsBuilder::new() + .memory_pages(10) + .table_elements(5) + .build(), + ); let instance = Instance::new(&store, &module, &[])?; @@ -44,14 +50,14 @@ fn test_static_limiter() -> Result<()> { } #[test] -fn test_static_limiter_memory_only() -> Result<()> { +fn test_limits_memory_only() -> Result<()> { let engine = Engine::default(); let module = Module::new( &engine, r#"(module (memory (export "m") 0) (table (export "t") 0 anyfunc))"#, )?; - let store = Store::new_with_limiter(&engine, StaticResourceLimiter::new(Some(10), None)); + let store = Store::new_with_limits(&engine, StoreLimitsBuilder::new().memory_pages(10).build()); let instance = Instance::new(&store, &module, &[])?; @@ -77,14 +83,15 @@ fn test_static_limiter_memory_only() -> Result<()> { } #[test] -fn test_static_limiter_table_only() -> Result<()> { +fn test_limits_table_only() -> Result<()> { let engine = Engine::default(); let module = Module::new( &engine, r#"(module (memory (export "m") 0) (table (export "t") 0 anyfunc))"#, )?; - let store = Store::new_with_limiter(&engine, StaticResourceLimiter::new(None, Some(5))); + let store = + Store::new_with_limits(&engine, StoreLimitsBuilder::new().table_elements(5).build()); let instance = Instance::new(&store, &module, &[])?; @@ -198,7 +205,7 @@ fn test_custom_limiter() -> Result<()> { )?; let dropped = Rc::new(Cell::new(false)); - let store = Store::new_with_limiter(&engine, HostMemoryLimiter(dropped.clone())); + let store = Store::new_with_limits(&engine, HostMemoryLimiter(dropped.clone())); assert!(store .set(Rc::new(RefCell::new(MemoryContext { diff --git a/tests/all/main.rs b/tests/all/main.rs index 172d6b9fd951..89c686119e42 100644 --- a/tests/all/main.rs +++ b/tests/all/main.rs @@ -13,7 +13,7 @@ mod import_calling_export; mod import_indexes; mod instance; mod invoke_func_via_table; -mod limiter; +mod limits; mod linker; mod memory_creator; mod module; diff --git a/tests/all/module_linking.rs b/tests/all/module_linking.rs index b010573c6e7d..ea6b036a01b2 100644 --- a/tests/all/module_linking.rs +++ b/tests/all/module_linking.rs @@ -190,7 +190,6 @@ fn imports_exports() -> Result<()> { fn limit_instances() -> Result<()> { let mut config = Config::new(); config.wasm_module_linking(true); - config.max_instances(10); let engine = Engine::new(&config)?; let module = Module::new( &engine, @@ -216,7 +215,7 @@ fn limit_instances() -> Result<()> { ) "#, )?; - let store = Store::new(&engine); + let store = Store::new_with_limits(&engine, StoreLimitsBuilder::new().instances(10).build()); let err = Instance::new(&store, &module, &[]).err().unwrap(); assert!( err.to_string().contains("resource limit exceeded"), @@ -231,7 +230,6 @@ fn limit_memories() -> Result<()> { let mut config = Config::new(); config.wasm_module_linking(true); config.wasm_multi_memory(true); - config.max_memories(10); let engine = Engine::new(&config)?; let module = Module::new( &engine, @@ -252,7 +250,7 @@ fn limit_memories() -> Result<()> { ) "#, )?; - let store = Store::new(&engine); + let store = Store::new_with_limits(&engine, StoreLimitsBuilder::new().memories(10).build()); let err = Instance::new(&store, &module, &[]).err().unwrap(); assert!( err.to_string().contains("resource limit exceeded"), @@ -266,7 +264,6 @@ fn limit_memories() -> Result<()> { fn limit_tables() -> Result<()> { let mut config = Config::new(); config.wasm_module_linking(true); - config.max_tables(10); let engine = Engine::new(&config)?; let module = Module::new( &engine, @@ -287,7 +284,7 @@ fn limit_tables() -> Result<()> { ) "#, )?; - let store = Store::new(&engine); + let store = Store::new_with_limits(&engine, StoreLimitsBuilder::new().tables(10).build()); let err = Instance::new(&store, &module, &[]).err().unwrap(); assert!( err.to_string().contains("resource limit exceeded"),