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 {
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, permitted by
77    /// the `memory_growing` method, has failed.
78    ///
79    /// Note that this method is not called if `memory_growing` returns an
80    /// error.
81    ///
82    /// Reasons for failure include: the growth exceeds the `maximum` passed to
83    /// `memory_growing`, or the operating system failed to allocate additional
84    /// memory. In that case, `error` might be downcastable to a `std::io::Error`.
85    ///
86    /// See the details on the return values for `memory_growing` for what the
87    /// return value of this function indicates.
88    fn memory_grow_failed(&mut self, error: anyhow::Error) -> Result<()> {
89        log::debug!("ignoring memory growth failure error: {error:?}");
90        Ok(())
91    }
92
93    /// Notifies the resource limiter that an instance's table has been
94    /// requested to grow.
95    ///
96    /// * `current` is the current number of elements in the table.
97    /// * `desired` is the desired number of elements in the table.
98    /// * `maximum` is either the table's maximum or a maximum from an instance
99    ///   allocator.  A value of `None` indicates that the table is unbounded.
100    ///
101    /// Currently in Wasmtime each table element requires a pointer's worth of
102    /// space (e.g. `mem::size_of::<usize>()`).
103    ///
104    /// See the details on the return values for `memory_growing` for what the
105    /// return value of this function indicates.
106    fn table_growing(
107        &mut self,
108        current: usize,
109        desired: usize,
110        maximum: Option<usize>,
111    ) -> Result<bool>;
112
113    /// Notifies the resource limiter that growing a linear memory, permitted by
114    /// the `table_growing` method, has failed.
115    ///
116    /// Note that this method is not called if `table_growing` returns an error.
117    ///
118    /// Reasons for failure include: the growth exceeds the `maximum` passed to
119    /// `table_growing`. This could expand in the future.
120    ///
121    /// See the details on the return values for `memory_growing` for what the
122    /// return value of this function indicates.
123    fn table_grow_failed(&mut self, error: anyhow::Error) -> Result<()> {
124        log::debug!("ignoring table growth failure error: {error:?}");
125        Ok(())
126    }
127
128    /// The maximum number of instances that can be created for a `Store`.
129    ///
130    /// Module instantiation will fail if this limit is exceeded.
131    ///
132    /// This value defaults to 10,000.
133    fn instances(&self) -> usize {
134        DEFAULT_INSTANCE_LIMIT
135    }
136
137    /// The maximum number of tables that can be created for a `Store`.
138    ///
139    /// Creation of tables will fail if this limit is exceeded.
140    ///
141    /// This value defaults to 10,000.
142    fn tables(&self) -> usize {
143        DEFAULT_TABLE_LIMIT
144    }
145
146    /// The maximum number of linear memories that can be created for a `Store`
147    ///
148    /// Creation of memories will fail with an error if this limit is exceeded.
149    ///
150    /// This value defaults to 10,000.
151    fn memories(&self) -> usize {
152        DEFAULT_MEMORY_LIMIT
153    }
154}
155
156/// Used by hosts to limit resource consumption of instances, blocking
157/// asynchronously if necessary.
158///
159/// This trait is identical to [`ResourceLimiter`], except that the
160/// `memory_growing` and `table_growing` functions are `async`. Must be used
161/// with an async [`Store`](`crate::Store`) configured via
162/// [`Config::async_support`](crate::Config::async_support).
163///
164/// This trait is used with
165/// [`Store::limiter_async`](`crate::Store::limiter_async`)`: see those docs
166/// for restrictions on using other Wasmtime interfaces with an async resource
167/// limiter. Additionally see [`ResourceLimiter`] for more information about
168/// limiting resources from WebAssembly.
169///
170/// The `async` here enables embedders that are already using asynchronous
171/// execution of WebAssembly to block the WebAssembly, but no the OS thread, to
172/// answer the question whether growing a memory or table is allowed.
173#[cfg(feature = "async")]
174#[async_trait::async_trait]
175pub trait ResourceLimiterAsync {
176    /// Async version of [`ResourceLimiter::memory_growing`]
177    async fn memory_growing(
178        &mut self,
179        current: usize,
180        desired: usize,
181        maximum: Option<usize>,
182    ) -> Result<bool>;
183
184    /// Identical to [`ResourceLimiter::memory_grow_failed`]
185    fn memory_grow_failed(&mut self, error: anyhow::Error) -> Result<()> {
186        log::debug!("ignoring memory growth failure error: {error:?}");
187        Ok(())
188    }
189
190    /// Asynchronous version of [`ResourceLimiter::table_growing`]
191    async fn table_growing(
192        &mut self,
193        current: usize,
194        desired: usize,
195        maximum: Option<usize>,
196    ) -> Result<bool>;
197
198    /// Identical to [`ResourceLimiter::table_grow_failed`]
199    fn table_grow_failed(&mut self, error: anyhow::Error) -> Result<()> {
200        log::debug!("ignoring table growth failure error: {error:?}");
201        Ok(())
202    }
203
204    /// Identical to [`ResourceLimiter::instances`]`
205    fn instances(&self) -> usize {
206        DEFAULT_INSTANCE_LIMIT
207    }
208
209    /// Identical to [`ResourceLimiter::tables`]`
210    fn tables(&self) -> usize {
211        DEFAULT_TABLE_LIMIT
212    }
213
214    /// Identical to [`ResourceLimiter::memories`]`
215    fn memories(&self) -> usize {
216        DEFAULT_MEMORY_LIMIT
217    }
218}
219
220/// Used to build [`StoreLimits`].
221pub struct StoreLimitsBuilder(StoreLimits);
222
223impl StoreLimitsBuilder {
224    /// Creates a new [`StoreLimitsBuilder`].
225    ///
226    /// See the documentation on each builder method for the default for each
227    /// value.
228    pub fn new() -> Self {
229        Self(StoreLimits::default())
230    }
231
232    /// The maximum number of bytes a linear memory can grow to.
233    ///
234    /// Growing a linear memory beyond this limit will fail. This limit is
235    /// applied to each linear memory individually, so if a wasm module has
236    /// multiple linear memories then they're all allowed to reach up to the
237    /// `limit` specified.
238    ///
239    /// By default, linear memory will not be limited.
240    pub fn memory_size(mut self, limit: usize) -> Self {
241        self.0.memory_size = Some(limit);
242        self
243    }
244
245    /// The maximum number of elements in a table.
246    ///
247    /// Growing a table beyond this limit will fail. This limit is applied to
248    /// each table individually, so if a wasm module has multiple tables then
249    /// they're all allowed to reach up to the `limit` specified.
250    ///
251    /// By default, table elements will not be limited.
252    pub fn table_elements(mut self, limit: usize) -> Self {
253        self.0.table_elements = Some(limit);
254        self
255    }
256
257    /// The maximum number of instances that can be created for a [`Store`](crate::Store).
258    ///
259    /// Module instantiation will fail if this limit is exceeded.
260    ///
261    /// This value defaults to 10,000.
262    pub fn instances(mut self, limit: usize) -> Self {
263        self.0.instances = limit;
264        self
265    }
266
267    /// The maximum number of tables that can be created for a [`Store`](crate::Store).
268    ///
269    /// Module instantiation will fail if this limit is exceeded.
270    ///
271    /// This value defaults to 10,000.
272    pub fn tables(mut self, tables: usize) -> Self {
273        self.0.tables = tables;
274        self
275    }
276
277    /// The maximum number of linear memories that can be created for a [`Store`](crate::Store).
278    ///
279    /// Instantiation will fail with an error if this limit is exceeded.
280    ///
281    /// This value defaults to 10,000.
282    pub fn memories(mut self, memories: usize) -> Self {
283        self.0.memories = memories;
284        self
285    }
286
287    /// Indicates that a trap should be raised whenever a growth operation
288    /// would fail.
289    ///
290    /// This operation will force `memory.grow` and `table.grow` instructions
291    /// to raise a trap on failure instead of returning -1. This is not
292    /// necessarily spec-compliant, but it can be quite handy when debugging a
293    /// module that fails to allocate memory and might behave oddly as a result.
294    ///
295    /// This value defaults to `false`.
296    pub fn trap_on_grow_failure(mut self, trap: bool) -> Self {
297        self.0.trap_on_grow_failure = trap;
298        self
299    }
300
301    /// Consumes this builder and returns the [`StoreLimits`].
302    pub fn build(self) -> StoreLimits {
303        self.0
304    }
305}
306
307/// Provides limits for a [`Store`](crate::Store).
308///
309/// This type is created with a [`StoreLimitsBuilder`] and is typically used in
310/// conjunction with [`Store::limiter`](crate::Store::limiter).
311///
312/// This is a convenience type included to avoid needing to implement the
313/// [`ResourceLimiter`] trait if your use case fits in the static configuration
314/// that this [`StoreLimits`] provides.
315#[derive(Clone, Debug)]
316pub struct StoreLimits {
317    memory_size: Option<usize>,
318    table_elements: Option<usize>,
319    instances: usize,
320    tables: usize,
321    memories: usize,
322    trap_on_grow_failure: bool,
323}
324
325impl Default for StoreLimits {
326    fn default() -> Self {
327        Self {
328            memory_size: None,
329            table_elements: None,
330            instances: DEFAULT_INSTANCE_LIMIT,
331            tables: DEFAULT_TABLE_LIMIT,
332            memories: DEFAULT_MEMORY_LIMIT,
333            trap_on_grow_failure: false,
334        }
335    }
336}
337
338impl ResourceLimiter for StoreLimits {
339    fn memory_growing(
340        &mut self,
341        _current: usize,
342        desired: usize,
343        maximum: Option<usize>,
344    ) -> Result<bool> {
345        let allow = match self.memory_size {
346            Some(limit) if desired > limit => false,
347            _ => match maximum {
348                Some(max) if desired > max => false,
349                _ => true,
350            },
351        };
352        if !allow && self.trap_on_grow_failure {
353            bail!("forcing trap when growing memory to {desired} bytes")
354        } else {
355            Ok(allow)
356        }
357    }
358
359    fn memory_grow_failed(&mut self, error: anyhow::Error) -> Result<()> {
360        if self.trap_on_grow_failure {
361            Err(error.context("forcing a memory growth failure to be a trap"))
362        } else {
363            log::debug!("ignoring memory growth failure error: {error:?}");
364            Ok(())
365        }
366    }
367
368    fn table_growing(
369        &mut self,
370        _current: usize,
371        desired: usize,
372        maximum: Option<usize>,
373    ) -> Result<bool> {
374        let allow = match self.table_elements {
375            Some(limit) if desired > limit => false,
376            _ => match maximum {
377                Some(max) if desired > max => false,
378                _ => true,
379            },
380        };
381        if !allow && self.trap_on_grow_failure {
382            bail!("forcing trap when growing table to {desired} elements")
383        } else {
384            Ok(allow)
385        }
386    }
387
388    fn table_grow_failed(&mut self, error: anyhow::Error) -> Result<()> {
389        if self.trap_on_grow_failure {
390            Err(error.context("forcing a table growth failure to be a trap"))
391        } else {
392            log::debug!("ignoring table growth failure error: {error:?}");
393            Ok(())
394        }
395    }
396
397    fn instances(&self) -> usize {
398        self.instances
399    }
400
401    fn tables(&self) -> usize {
402        self.tables
403    }
404
405    fn memories(&self) -> usize {
406        self.memories
407    }
408}