Skip to main content

wasmtime/runtime/
limits.rs

1use crate::prelude::*;
2
3/// Value returned by [`ResourceLimiter::instances`] default method
4pub const DEFAULT_INSTANCE_LIMIT: usize = 10000;
5/// Value returned by [`ResourceLimiter::tables`] default method
6pub const DEFAULT_TABLE_LIMIT: usize = 10000;
7/// Value returned by [`ResourceLimiter::memories`] default method
8pub const DEFAULT_MEMORY_LIMIT: usize = 10000;
9
10/// Used by hosts to limit resource consumption of instances.
11///
12/// This trait is used in conjunction with the
13/// [`Store::limiter`](crate::Store::limiter) to synchronously limit the
14/// allocation of resources within a store. As a store-level limit this means
15/// that all creation of instances, memories, and tables are limited within the
16/// store. Resources limited via this trait are primarily related to memory and
17/// limiting CPU resources needs to be done with something such as
18/// [`Config::consume_fuel`](crate::Config::consume_fuel) or
19/// [`Config::epoch_interruption`](crate::Config::epoch_interruption).
20///
21/// Note that this trait does not limit 100% of memory allocated via a
22/// [`Store`](crate::Store). Wasmtime will still allocate memory to track data
23/// structures and additionally embedder-specific memory allocations are not
24/// tracked via this trait. This trait only limits resources allocated by a
25/// WebAssembly instance itself.
26///
27/// This trait is intended for synchronously limiting the resources of a module.
28/// If your use case requires blocking to answer whether a request is permitted
29/// or not and you're otherwise working in an asynchronous context the
30/// [`ResourceLimiterAsync`] trait is also provided to avoid blocking an OS
31/// thread while a limit is determined.
32pub trait ResourceLimiter: Send {
33    /// Notifies the resource limiter that an instance's linear memory has been
34    /// requested to grow.
35    ///
36    /// * `current` is the current size of the linear memory in bytes.
37    /// * `desired` is the desired size of the linear memory in bytes.
38    /// * `maximum` is either the linear memory's maximum or a maximum from an
39    ///   instance allocator, also in bytes. A value of `None`
40    ///   indicates that the linear memory is unbounded.
41    ///
42    /// The `current` and `desired` amounts are guaranteed to always be
43    /// multiples of the WebAssembly page size, 64KiB.
44    ///
45    /// This function is not invoked when the requested size doesn't fit in
46    /// `usize`. Additionally this function is not invoked for shared memories
47    /// at this time. Otherwise even when `desired` exceeds `maximum` this
48    /// function will still be called.
49    ///
50    /// ## Return Value
51    ///
52    /// If `Ok(true)` is returned from this function then the growth operation
53    /// is allowed. This means that the wasm `memory.grow` instruction will
54    /// return with the `desired` size, in wasm pages. Note that even if
55    /// `Ok(true)` is returned, though, if `desired` exceeds `maximum` then the
56    /// growth operation will still fail.
57    ///
58    /// If `Ok(false)` is returned then this will cause the `memory.grow`
59    /// instruction in a module to return -1 (failure), or in the case of an
60    /// embedder API calling [`Memory::new`](crate::Memory::new) or
61    /// [`Memory::grow`](crate::Memory::grow) an error will be returned from
62    /// those methods.
63    ///
64    /// If `Err(e)` is returned then the `memory.grow` function will behave
65    /// as if a trap has been raised. Note that this is not necessarily
66    /// compliant with the WebAssembly specification but it can be a handy and
67    /// useful tool to get a precise backtrace at "what requested so much memory
68    /// to cause a growth failure?".
69    fn memory_growing(
70        &mut self,
71        current: usize,
72        desired: usize,
73        maximum: Option<usize>,
74    ) -> Result<bool>;
75
76    /// Notifies the resource limiter that growing a linear memory has failed.
77    ///
78    /// This is typically called after `memory_growing` has permitted a growth
79    /// which then failed. It may also be called without a preceding call to
80    /// `memory_growing` when the requested size is invalid for the linear
81    /// memory, for example when it cannot be represented by the memory's type
82    /// (such as a 32-bit memory with a page size of 1 byte growing to 4GiB or
83    /// beyond).
84    ///
85    /// Note that this method is not called if `memory_growing` returns an
86    /// error, nor if it returns `Ok(false)`.
87    ///
88    /// Reasons for failure include: the requested size is not valid for the
89    /// memory's type, the growth exceeds the `maximum` passed to
90    /// `memory_growing`, or the operating system failed to allocate additional
91    /// memory. In that case, `error` might be downcastable to a `std::io::Error`.
92    ///
93    /// See the details on the return values for `memory_growing` for what the
94    /// return value of this function indicates.
95    fn memory_grow_failed(&mut self, error: crate::Error) -> Result<()> {
96        log::debug!("ignoring memory growth failure error: {error:?}");
97        Ok(())
98    }
99
100    /// Notifies the resource limiter that an instance's table has been
101    /// requested to grow.
102    ///
103    /// * `current` is the current number of elements in the table.
104    /// * `desired` is the desired number of elements in the table.
105    /// * `maximum` is either the table's maximum or a maximum from an instance
106    ///   allocator.  A value of `None` indicates that the table is unbounded.
107    ///
108    /// Currently in Wasmtime each table element requires a pointer's worth of
109    /// space (e.g. `mem::size_of::<usize>()`).
110    ///
111    /// See the details on the return values for `memory_growing` for what the
112    /// return value of this function indicates.
113    fn table_growing(
114        &mut self,
115        current: usize,
116        desired: usize,
117        maximum: Option<usize>,
118    ) -> Result<bool>;
119
120    /// Notifies the resource limiter that growing a table has failed.
121    ///
122    /// This is typically called after `table_growing` has permitted a growth
123    /// which then failed. It may also be called without a preceding call to
124    /// `table_growing` when the requested size cannot be represented, for
125    /// example when computing the new size overflows.
126    ///
127    /// Note that this method is not called if `table_growing` returns an
128    /// error, nor if it returns `Ok(false)`.
129    ///
130    /// Reasons for failure include: the requested size overflows, the growth
131    /// exceeds the `maximum` passed to `table_growing`, or allocating the
132    /// table's additional storage failed. This could expand in the future.
133    ///
134    /// See the details on the return values for `memory_growing` for what the
135    /// return value of this function indicates.
136    fn table_grow_failed(&mut self, error: crate::Error) -> Result<()> {
137        log::debug!("ignoring table growth failure error: {error:?}");
138        Ok(())
139    }
140
141    /// The maximum number of instances that can be created for a `Store`.
142    ///
143    /// Module instantiation will fail if this limit is exceeded.
144    ///
145    /// This value defaults to 10,000.
146    fn instances(&self) -> usize {
147        DEFAULT_INSTANCE_LIMIT
148    }
149
150    /// The maximum number of tables that can be created for a `Store`.
151    ///
152    /// Creation of tables will fail if this limit is exceeded.
153    ///
154    /// This value defaults to 10,000.
155    fn tables(&self) -> usize {
156        DEFAULT_TABLE_LIMIT
157    }
158
159    /// The maximum number of linear memories that can be created for a `Store`
160    ///
161    /// Creation of memories will fail with an error if this limit is exceeded.
162    ///
163    /// This value defaults to 10,000.
164    fn memories(&self) -> usize {
165        DEFAULT_MEMORY_LIMIT
166    }
167}
168
169/// Used by hosts to limit resource consumption of instances, blocking
170/// asynchronously if necessary.
171///
172/// This trait is identical to [`ResourceLimiter`], except that the
173/// `memory_growing` and `table_growing` functions are `async`.
174///
175/// This trait is used with
176/// [`Store::limiter_async`](`crate::Store::limiter_async`)`: see those docs
177/// for restrictions on using other Wasmtime interfaces with an async resource
178/// limiter. Additionally see [`ResourceLimiter`] for more information about
179/// limiting resources from WebAssembly.
180///
181/// The `async` here enables embedders that are already using asynchronous
182/// execution of WebAssembly to block the WebAssembly, but no the OS thread, to
183/// answer the question whether growing a memory or table is allowed.
184#[cfg(feature = "async")]
185#[async_trait::async_trait]
186pub trait ResourceLimiterAsync: Send {
187    /// Async version of [`ResourceLimiter::memory_growing`]
188    async fn memory_growing(
189        &mut self,
190        current: usize,
191        desired: usize,
192        maximum: Option<usize>,
193    ) -> Result<bool>;
194
195    /// Identical to [`ResourceLimiter::memory_grow_failed`]
196    fn memory_grow_failed(&mut self, error: crate::Error) -> Result<()> {
197        log::debug!("ignoring memory growth failure error: {error:?}");
198        Ok(())
199    }
200
201    /// Asynchronous version of [`ResourceLimiter::table_growing`]
202    async fn table_growing(
203        &mut self,
204        current: usize,
205        desired: usize,
206        maximum: Option<usize>,
207    ) -> Result<bool>;
208
209    /// Identical to [`ResourceLimiter::table_grow_failed`]
210    fn table_grow_failed(&mut self, error: crate::Error) -> Result<()> {
211        log::debug!("ignoring table growth failure error: {error:?}");
212        Ok(())
213    }
214
215    /// Identical to [`ResourceLimiter::instances`]`
216    fn instances(&self) -> usize {
217        DEFAULT_INSTANCE_LIMIT
218    }
219
220    /// Identical to [`ResourceLimiter::tables`]`
221    fn tables(&self) -> usize {
222        DEFAULT_TABLE_LIMIT
223    }
224
225    /// Identical to [`ResourceLimiter::memories`]`
226    fn memories(&self) -> usize {
227        DEFAULT_MEMORY_LIMIT
228    }
229}
230
231/// Used to build [`StoreLimits`].
232pub struct StoreLimitsBuilder(StoreLimits);
233
234impl StoreLimitsBuilder {
235    /// Creates a new [`StoreLimitsBuilder`].
236    ///
237    /// See the documentation on each builder method for the default for each
238    /// value.
239    pub fn new() -> Self {
240        Self(StoreLimits::default())
241    }
242
243    /// The maximum number of bytes a linear memory can grow to.
244    ///
245    /// Growing a linear memory beyond this limit will fail. This limit is
246    /// applied to each linear memory individually, so if a wasm module has
247    /// multiple linear memories then they're all allowed to reach up to the
248    /// `limit` specified.
249    ///
250    /// By default, linear memory will not be limited.
251    pub fn memory_size(mut self, limit: usize) -> Self {
252        self.0.memory_size = Some(limit);
253        self
254    }
255
256    /// The maximum number of elements in a table.
257    ///
258    /// Growing a table beyond this limit will fail. This limit is applied to
259    /// each table individually, so if a wasm module has multiple tables then
260    /// they're all allowed to reach up to the `limit` specified.
261    ///
262    /// By default, table elements will not be limited.
263    pub fn table_elements(mut self, limit: usize) -> Self {
264        self.0.table_elements = Some(limit);
265        self
266    }
267
268    /// The maximum number of instances that can be created for a [`Store`](crate::Store).
269    ///
270    /// Module instantiation will fail if this limit is exceeded.
271    ///
272    /// This value defaults to 10,000.
273    pub fn instances(mut self, limit: usize) -> Self {
274        self.0.instances = limit;
275        self
276    }
277
278    /// The maximum number of tables that can be created for a [`Store`](crate::Store).
279    ///
280    /// Module instantiation will fail if this limit is exceeded.
281    ///
282    /// This value defaults to 10,000.
283    pub fn tables(mut self, tables: usize) -> Self {
284        self.0.tables = tables;
285        self
286    }
287
288    /// The maximum number of linear memories that can be created for a [`Store`](crate::Store).
289    ///
290    /// Instantiation will fail with an error if this limit is exceeded.
291    ///
292    /// This value defaults to 10,000.
293    pub fn memories(mut self, memories: usize) -> Self {
294        self.0.memories = memories;
295        self
296    }
297
298    /// Indicates that a trap should be raised whenever a growth operation
299    /// would fail.
300    ///
301    /// This operation will force `memory.grow` and `table.grow` instructions
302    /// to raise a trap on failure instead of returning -1. This is not
303    /// necessarily spec-compliant, but it can be quite handy when debugging a
304    /// module that fails to allocate memory and might behave oddly as a result.
305    ///
306    /// This value defaults to `false`.
307    pub fn trap_on_grow_failure(mut self, trap: bool) -> Self {
308        self.0.trap_on_grow_failure = trap;
309        self
310    }
311
312    /// Consumes this builder and returns the [`StoreLimits`].
313    pub fn build(self) -> StoreLimits {
314        self.0
315    }
316}
317
318/// Provides limits for a [`Store`](crate::Store).
319///
320/// This type is created with a [`StoreLimitsBuilder`] and is typically used in
321/// conjunction with [`Store::limiter`](crate::Store::limiter).
322///
323/// This is a convenience type included to avoid needing to implement the
324/// [`ResourceLimiter`] trait if your use case fits in the static configuration
325/// that this [`StoreLimits`] provides.
326#[derive(Clone, Debug)]
327pub struct StoreLimits {
328    memory_size: Option<usize>,
329    table_elements: Option<usize>,
330    instances: usize,
331    tables: usize,
332    memories: usize,
333    trap_on_grow_failure: bool,
334}
335
336impl Default for StoreLimits {
337    fn default() -> Self {
338        Self {
339            memory_size: None,
340            table_elements: None,
341            instances: DEFAULT_INSTANCE_LIMIT,
342            tables: DEFAULT_TABLE_LIMIT,
343            memories: DEFAULT_MEMORY_LIMIT,
344            trap_on_grow_failure: false,
345        }
346    }
347}
348
349impl ResourceLimiter for StoreLimits {
350    fn memory_growing(
351        &mut self,
352        _current: usize,
353        desired: usize,
354        maximum: Option<usize>,
355    ) -> Result<bool> {
356        let allow = match self.memory_size {
357            Some(limit) if desired > limit => false,
358            _ => match maximum {
359                Some(max) if desired > max => false,
360                _ => true,
361            },
362        };
363        if !allow && self.trap_on_grow_failure {
364            bail!("forcing trap when growing memory to {desired} bytes")
365        } else {
366            Ok(allow)
367        }
368    }
369
370    fn memory_grow_failed(&mut self, error: crate::Error) -> Result<()> {
371        if self.trap_on_grow_failure {
372            Err(error.context("forcing a memory growth failure to be a trap"))
373        } else {
374            log::debug!("ignoring memory growth failure error: {error:?}");
375            Ok(())
376        }
377    }
378
379    fn table_growing(
380        &mut self,
381        _current: usize,
382        desired: usize,
383        maximum: Option<usize>,
384    ) -> Result<bool> {
385        let allow = match self.table_elements {
386            Some(limit) if desired > limit => false,
387            _ => match maximum {
388                Some(max) if desired > max => false,
389                _ => true,
390            },
391        };
392        if !allow && self.trap_on_grow_failure {
393            bail!("forcing trap when growing table to {desired} elements")
394        } else {
395            Ok(allow)
396        }
397    }
398
399    fn table_grow_failed(&mut self, error: crate::Error) -> Result<()> {
400        if self.trap_on_grow_failure {
401            Err(error.context("forcing a table growth failure to be a trap"))
402        } else {
403            log::debug!("ignoring table growth failure error: {error:?}");
404            Ok(())
405        }
406    }
407
408    fn instances(&self) -> usize {
409        self.instances
410    }
411
412    fn tables(&self) -> usize {
413        self.tables
414    }
415
416    fn memories(&self) -> usize {
417        self.memories
418    }
419}