soroban_wasmi/
limits.rs

1use crate::{memory::MemoryError, table::TableError};
2
3/// Value returned by [`ResourceLimiter::instances`] default method
4pub const DEFAULT_INSTANCE_LIMIT: usize = 10000;
5
6/// Value returned by [`ResourceLimiter::tables`] default method
7pub const DEFAULT_TABLE_LIMIT: usize = 10000;
8
9/// Value returned by [`ResourceLimiter::memories`] default method
10pub const DEFAULT_MEMORY_LIMIT: usize = 10000;
11
12/// Used by hosts to limit resource consumption of instances.
13///
14/// This trait is used in conjunction with the
15/// [`Store::limiter`](crate::Store::limiter) to limit the
16/// allocation of resources within a store. As a store-level limit this means
17/// that all creation of instances, memories, and tables are limited within the
18/// store. Resources limited via this trait are primarily related to memory and
19/// limiting CPU resources needs to be done with something such as
20/// [`Config::consume_fuel`](crate::Config::consume_fuel).
21///
22/// Note that this trait does not limit 100% of memory allocated via a
23/// [`Store`](crate::Store). Wasmi will still allocate memory to track data
24/// structures and additionally embedder-specific memory allocations are not
25/// tracked via this trait. This trait only limits resources allocated by a
26/// WebAssembly instance itself.
27pub trait ResourceLimiter {
28    /// Notifies the resource limiter that an instance's linear memory has been
29    /// requested to grow.
30    ///
31    /// * `current` is the current size of the linear memory in bytes.
32    /// * `desired` is the desired size of the linear memory in bytes.
33    /// * `maximum` is either the linear memory's maximum or a maximum from an
34    ///   instance allocator, also in bytes. A value of `None`
35    ///   indicates that the linear memory is unbounded.
36    ///
37    /// The `current` and `desired` amounts are guaranteed to always be
38    /// multiples of the WebAssembly page size, 64KiB.
39    ///
40    /// ## Return Value
41    ///
42    /// If `Ok(true)` is returned from this function then the growth operation
43    /// is allowed. This means that the wasm `memory.grow` instruction will
44    /// return with the `desired` size, in wasm pages. Note that even if
45    /// `Ok(true)` is returned, though, if `desired` exceeds `maximum` then the
46    /// growth operation will still fail.
47    ///
48    /// If `Ok(false)` is returned then this will cause the `memory.grow`
49    /// instruction in a module to return -1 (failure), or in the case of an
50    /// embedder API calling [`Memory::new`](crate::Memory::new) or
51    /// [`Memory::grow`](crate::Memory::grow) an error will be returned from
52    /// those methods.
53    ///
54    /// # Errors
55    ///
56    /// If `Err(e)` is returned then the `memory.grow` function will behave
57    /// as if a trap has been raised. Note that this is not necessarily
58    /// compliant with the WebAssembly specification but it can be a handy and
59    /// useful tool to get a precise backtrace at "what requested so much memory
60    /// to cause a growth failure?".
61    fn memory_growing(
62        &mut self,
63        current: usize,
64        desired: usize,
65        maximum: Option<usize>,
66    ) -> Result<bool, MemoryError>;
67
68    /// Notifies the resource limiter that an instance's table has been
69    /// requested to grow.
70    ///
71    /// * `current` is the current number of elements in the table.
72    /// * `desired` is the desired number of elements in the table.
73    /// * `maximum` is either the table's maximum or a maximum from an instance
74    ///   allocator.  A value of `None` indicates that the table is unbounded.
75    ///
76    /// # Errors
77    ///
78    /// See the details on the return values for `memory_growing` for what the
79    /// return values of this function indicates.
80    fn table_growing(
81        &mut self,
82        current: u32,
83        desired: u32,
84        maximum: Option<u32>,
85    ) -> Result<bool, TableError>;
86
87    /// Notifies the resource limiter that growing a linear memory, permitted by
88    /// the `memory_growing` method, has failed.
89    fn memory_grow_failed(&mut self, _error: &MemoryError) {}
90
91    /// Notifies the resource limiter that growing a linear memory, permitted by
92    /// the `table_growing` method, has failed.
93    fn table_grow_failed(&mut self, _error: &TableError) {}
94
95    /// The maximum number of instances that can be created for a `Store`.
96    ///
97    /// Module instantiation will fail if this limit is exceeded.
98    ///
99    /// This value defaults to 10,000.
100    fn instances(&self) -> usize {
101        DEFAULT_INSTANCE_LIMIT
102    }
103
104    /// The maximum number of tables that can be created for a `Store`.
105    ///
106    /// Creation of tables will fail if this limit is exceeded.
107    ///
108    /// This value defaults to 10,000.
109    fn tables(&self) -> usize {
110        DEFAULT_TABLE_LIMIT
111    }
112
113    /// The maximum number of linear memories that can be created for a `Store`
114    ///
115    /// Creation of memories will fail with an error if this limit is exceeded.
116    ///
117    /// This value defaults to 10,000.
118    fn memories(&self) -> usize {
119        DEFAULT_MEMORY_LIMIT
120    }
121}
122
123/// Used to build [`StoreLimits`].
124pub struct StoreLimitsBuilder(StoreLimits);
125
126impl StoreLimitsBuilder {
127    /// Creates a new [`StoreLimitsBuilder`].
128    ///
129    /// See the documentation on each builder method for the default for each
130    /// value.
131    pub fn new() -> Self {
132        Self(StoreLimits::default())
133    }
134
135    /// The maximum number of bytes a linear memory can grow to.
136    ///
137    /// Growing a linear memory beyond this limit will fail. This limit is
138    /// applied to each linear memory individually, so if a wasm module has
139    /// multiple linear memories then they're all allowed to reach up to the
140    /// `limit` specified.
141    ///
142    /// By default, linear memory will not be limited.
143    pub fn memory_size(mut self, limit: usize) -> Self {
144        self.0.memory_size = Some(limit);
145        self
146    }
147
148    /// The maximum number of elements in a table.
149    ///
150    /// Growing a table beyond this limit will fail. This limit is applied to
151    /// each table individually, so if a wasm module has multiple tables then
152    /// they're all allowed to reach up to the `limit` specified.
153    ///
154    /// By default, table elements will not be limited.
155    pub fn table_elements(mut self, limit: u32) -> Self {
156        self.0.table_elements = Some(limit);
157        self
158    }
159
160    /// The maximum number of instances that can be created for a [`Store`](crate::Store).
161    ///
162    /// Module instantiation will fail if this limit is exceeded.
163    ///
164    /// This value defaults to 10,000.
165    pub fn instances(mut self, limit: usize) -> Self {
166        self.0.instances = limit;
167        self
168    }
169
170    /// The maximum number of tables that can be created for a [`Store`](crate::Store).
171    ///
172    /// Module instantiation will fail if this limit is exceeded.
173    ///
174    /// This value defaults to 10,000.
175    pub fn tables(mut self, tables: usize) -> Self {
176        self.0.tables = tables;
177        self
178    }
179
180    /// The maximum number of linear memories that can be created for a [`Store`](crate::Store).
181    ///
182    /// Instantiation will fail with an error if this limit is exceeded.
183    ///
184    /// This value defaults to 10,000.
185    pub fn memories(mut self, memories: usize) -> Self {
186        self.0.memories = memories;
187        self
188    }
189
190    /// Indicates that a trap should be raised whenever a growth operation
191    /// would fail.
192    ///
193    /// This operation will force `memory.grow` and `table.grow` instructions
194    /// to raise a trap on failure instead of returning -1. This is not
195    /// necessarily spec-compliant, but it can be quite handy when debugging a
196    /// module that fails to allocate memory and might behave oddly as a result.
197    ///
198    /// This value defaults to `false`.
199    pub fn trap_on_grow_failure(mut self, trap: bool) -> Self {
200        self.0.trap_on_grow_failure = trap;
201        self
202    }
203
204    /// Consumes this builder and returns the [`StoreLimits`].
205    pub fn build(self) -> StoreLimits {
206        self.0
207    }
208}
209
210impl Default for StoreLimitsBuilder {
211    fn default() -> Self {
212        Self::new()
213    }
214}
215
216/// Provides limits for a [`Store`](crate::Store).
217///
218/// This type is created with a [`StoreLimitsBuilder`] and is typically used in
219/// conjunction with [`Store::limiter`](crate::Store::limiter).
220///
221/// This is a convenience type included to avoid needing to implement the
222/// [`ResourceLimiter`] trait if your use case fits in the static configuration
223/// that this [`StoreLimits`] provides.
224#[derive(Clone, Debug)]
225pub struct StoreLimits {
226    memory_size: Option<usize>,
227    table_elements: Option<u32>,
228    instances: usize,
229    tables: usize,
230    memories: usize,
231    trap_on_grow_failure: bool,
232}
233
234impl Default for StoreLimits {
235    fn default() -> Self {
236        Self {
237            memory_size: None,
238            table_elements: None,
239            instances: DEFAULT_INSTANCE_LIMIT,
240            tables: DEFAULT_TABLE_LIMIT,
241            memories: DEFAULT_MEMORY_LIMIT,
242            trap_on_grow_failure: false,
243        }
244    }
245}
246
247impl ResourceLimiter for StoreLimits {
248    fn memory_growing(
249        &mut self,
250        _current: usize,
251        desired: usize,
252        maximum: Option<usize>,
253    ) -> Result<bool, MemoryError> {
254        let allow = match self.memory_size {
255            Some(limit) if desired > limit => false,
256            _ => match maximum {
257                Some(max) if desired > max => false,
258                Some(_) | None => true,
259            },
260        };
261        if !allow && self.trap_on_grow_failure {
262            Err(MemoryError::OutOfBoundsGrowth)
263        } else {
264            Ok(allow)
265        }
266    }
267
268    fn table_growing(
269        &mut self,
270        current: u32,
271        desired: u32,
272        maximum: Option<u32>,
273    ) -> Result<bool, TableError> {
274        let allow = match self.table_elements {
275            Some(limit) if desired > limit => false,
276            _ => match maximum {
277                Some(max) if desired > max => false,
278                Some(_) | None => true,
279            },
280        };
281        if !allow && self.trap_on_grow_failure {
282            Err(TableError::GrowOutOfBounds {
283                maximum: maximum.unwrap_or(u32::MAX),
284                current,
285                delta: desired - current,
286            })
287        } else {
288            Ok(allow)
289        }
290    }
291
292    fn instances(&self) -> usize {
293        self.instances
294    }
295
296    fn tables(&self) -> usize {
297        self.tables
298    }
299
300    fn memories(&self) -> usize {
301        self.memories
302    }
303}