1 use crate::prelude::*;
2 
3 /// Value returned by [`ResourceLimiter::instances`] default method
4 pub const DEFAULT_INSTANCE_LIMIT: usize = 10000;
5 /// Value returned by [`ResourceLimiter::tables`] default method
6 pub const DEFAULT_TABLE_LIMIT: usize = 10000;
7 /// Value returned by [`ResourceLimiter::memories`] default method
8 pub 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.
32 pub 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?".
memory_growing( &mut self, current: usize, desired: usize, maximum: Option<usize>, ) -> Result<bool>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.
memory_grow_failed(&mut self, error: crate::Error) -> Result<()>88     fn memory_grow_failed(&mut self, error: crate::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.
table_growing( &mut self, current: usize, desired: usize, maximum: Option<usize>, ) -> Result<bool>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.
table_grow_failed(&mut self, error: crate::Error) -> Result<()>123     fn table_grow_failed(&mut self, error: crate::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.
instances(&self) -> usize133     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.
tables(&self) -> usize142     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.
memories(&self) -> usize151     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`.
161 ///
162 /// This trait is used with
163 /// [`Store::limiter_async`](`crate::Store::limiter_async`)`: see those docs
164 /// for restrictions on using other Wasmtime interfaces with an async resource
165 /// limiter. Additionally see [`ResourceLimiter`] for more information about
166 /// limiting resources from WebAssembly.
167 ///
168 /// The `async` here enables embedders that are already using asynchronous
169 /// execution of WebAssembly to block the WebAssembly, but no the OS thread, to
170 /// answer the question whether growing a memory or table is allowed.
171 #[cfg(feature = "async")]
172 #[async_trait::async_trait]
173 pub trait ResourceLimiterAsync: Send {
174     /// Async version of [`ResourceLimiter::memory_growing`]
memory_growing( &mut self, current: usize, desired: usize, maximum: Option<usize>, ) -> Result<bool>175     async fn memory_growing(
176         &mut self,
177         current: usize,
178         desired: usize,
179         maximum: Option<usize>,
180     ) -> Result<bool>;
181 
182     /// Identical to [`ResourceLimiter::memory_grow_failed`]
memory_grow_failed(&mut self, error: crate::Error) -> Result<()>183     fn memory_grow_failed(&mut self, error: crate::Error) -> Result<()> {
184         log::debug!("ignoring memory growth failure error: {error:?}");
185         Ok(())
186     }
187 
188     /// Asynchronous version of [`ResourceLimiter::table_growing`]
table_growing( &mut self, current: usize, desired: usize, maximum: Option<usize>, ) -> Result<bool>189     async fn table_growing(
190         &mut self,
191         current: usize,
192         desired: usize,
193         maximum: Option<usize>,
194     ) -> Result<bool>;
195 
196     /// Identical to [`ResourceLimiter::table_grow_failed`]
table_grow_failed(&mut self, error: crate::Error) -> Result<()>197     fn table_grow_failed(&mut self, error: crate::Error) -> Result<()> {
198         log::debug!("ignoring table growth failure error: {error:?}");
199         Ok(())
200     }
201 
202     /// Identical to [`ResourceLimiter::instances`]`
instances(&self) -> usize203     fn instances(&self) -> usize {
204         DEFAULT_INSTANCE_LIMIT
205     }
206 
207     /// Identical to [`ResourceLimiter::tables`]`
tables(&self) -> usize208     fn tables(&self) -> usize {
209         DEFAULT_TABLE_LIMIT
210     }
211 
212     /// Identical to [`ResourceLimiter::memories`]`
memories(&self) -> usize213     fn memories(&self) -> usize {
214         DEFAULT_MEMORY_LIMIT
215     }
216 }
217 
218 /// Used to build [`StoreLimits`].
219 pub struct StoreLimitsBuilder(StoreLimits);
220 
221 impl StoreLimitsBuilder {
222     /// Creates a new [`StoreLimitsBuilder`].
223     ///
224     /// See the documentation on each builder method for the default for each
225     /// value.
new() -> Self226     pub fn new() -> Self {
227         Self(StoreLimits::default())
228     }
229 
230     /// The maximum number of bytes a linear memory can grow to.
231     ///
232     /// Growing a linear memory beyond this limit will fail. This limit is
233     /// applied to each linear memory individually, so if a wasm module has
234     /// multiple linear memories then they're all allowed to reach up to the
235     /// `limit` specified.
236     ///
237     /// By default, linear memory will not be limited.
memory_size(mut self, limit: usize) -> Self238     pub fn memory_size(mut self, limit: usize) -> Self {
239         self.0.memory_size = Some(limit);
240         self
241     }
242 
243     /// The maximum number of elements in a table.
244     ///
245     /// Growing a table beyond this limit will fail. This limit is applied to
246     /// each table individually, so if a wasm module has multiple tables then
247     /// they're all allowed to reach up to the `limit` specified.
248     ///
249     /// By default, table elements will not be limited.
table_elements(mut self, limit: usize) -> Self250     pub fn table_elements(mut self, limit: usize) -> Self {
251         self.0.table_elements = Some(limit);
252         self
253     }
254 
255     /// The maximum number of instances that can be created for a [`Store`](crate::Store).
256     ///
257     /// Module instantiation will fail if this limit is exceeded.
258     ///
259     /// This value defaults to 10,000.
instances(mut self, limit: usize) -> Self260     pub fn instances(mut self, limit: usize) -> Self {
261         self.0.instances = limit;
262         self
263     }
264 
265     /// The maximum number of tables that can be created for a [`Store`](crate::Store).
266     ///
267     /// Module instantiation will fail if this limit is exceeded.
268     ///
269     /// This value defaults to 10,000.
tables(mut self, tables: usize) -> Self270     pub fn tables(mut self, tables: usize) -> Self {
271         self.0.tables = tables;
272         self
273     }
274 
275     /// The maximum number of linear memories that can be created for a [`Store`](crate::Store).
276     ///
277     /// Instantiation will fail with an error if this limit is exceeded.
278     ///
279     /// This value defaults to 10,000.
memories(mut self, memories: usize) -> Self280     pub fn memories(mut self, memories: usize) -> Self {
281         self.0.memories = memories;
282         self
283     }
284 
285     /// Indicates that a trap should be raised whenever a growth operation
286     /// would fail.
287     ///
288     /// This operation will force `memory.grow` and `table.grow` instructions
289     /// to raise a trap on failure instead of returning -1. This is not
290     /// necessarily spec-compliant, but it can be quite handy when debugging a
291     /// module that fails to allocate memory and might behave oddly as a result.
292     ///
293     /// This value defaults to `false`.
trap_on_grow_failure(mut self, trap: bool) -> Self294     pub fn trap_on_grow_failure(mut self, trap: bool) -> Self {
295         self.0.trap_on_grow_failure = trap;
296         self
297     }
298 
299     /// Consumes this builder and returns the [`StoreLimits`].
build(self) -> StoreLimits300     pub fn build(self) -> StoreLimits {
301         self.0
302     }
303 }
304 
305 /// Provides limits for a [`Store`](crate::Store).
306 ///
307 /// This type is created with a [`StoreLimitsBuilder`] and is typically used in
308 /// conjunction with [`Store::limiter`](crate::Store::limiter).
309 ///
310 /// This is a convenience type included to avoid needing to implement the
311 /// [`ResourceLimiter`] trait if your use case fits in the static configuration
312 /// that this [`StoreLimits`] provides.
313 #[derive(Clone, Debug)]
314 pub struct StoreLimits {
315     memory_size: Option<usize>,
316     table_elements: Option<usize>,
317     instances: usize,
318     tables: usize,
319     memories: usize,
320     trap_on_grow_failure: bool,
321 }
322 
323 impl Default for StoreLimits {
default() -> Self324     fn default() -> Self {
325         Self {
326             memory_size: None,
327             table_elements: None,
328             instances: DEFAULT_INSTANCE_LIMIT,
329             tables: DEFAULT_TABLE_LIMIT,
330             memories: DEFAULT_MEMORY_LIMIT,
331             trap_on_grow_failure: false,
332         }
333     }
334 }
335 
336 impl ResourceLimiter for StoreLimits {
memory_growing( &mut self, _current: usize, desired: usize, maximum: Option<usize>, ) -> Result<bool>337     fn memory_growing(
338         &mut self,
339         _current: usize,
340         desired: usize,
341         maximum: Option<usize>,
342     ) -> Result<bool> {
343         let allow = match self.memory_size {
344             Some(limit) if desired > limit => false,
345             _ => match maximum {
346                 Some(max) if desired > max => false,
347                 _ => true,
348             },
349         };
350         if !allow && self.trap_on_grow_failure {
351             bail!("forcing trap when growing memory to {desired} bytes")
352         } else {
353             Ok(allow)
354         }
355     }
356 
memory_grow_failed(&mut self, error: crate::Error) -> Result<()>357     fn memory_grow_failed(&mut self, error: crate::Error) -> Result<()> {
358         if self.trap_on_grow_failure {
359             Err(error.context("forcing a memory growth failure to be a trap"))
360         } else {
361             log::debug!("ignoring memory growth failure error: {error:?}");
362             Ok(())
363         }
364     }
365 
table_growing( &mut self, _current: usize, desired: usize, maximum: Option<usize>, ) -> Result<bool>366     fn table_growing(
367         &mut self,
368         _current: usize,
369         desired: usize,
370         maximum: Option<usize>,
371     ) -> Result<bool> {
372         let allow = match self.table_elements {
373             Some(limit) if desired > limit => false,
374             _ => match maximum {
375                 Some(max) if desired > max => false,
376                 _ => true,
377             },
378         };
379         if !allow && self.trap_on_grow_failure {
380             bail!("forcing trap when growing table to {desired} elements")
381         } else {
382             Ok(allow)
383         }
384     }
385 
table_grow_failed(&mut self, error: crate::Error) -> Result<()>386     fn table_grow_failed(&mut self, error: crate::Error) -> Result<()> {
387         if self.trap_on_grow_failure {
388             Err(error.context("forcing a table growth failure to be a trap"))
389         } else {
390             log::debug!("ignoring table growth failure error: {error:?}");
391             Ok(())
392         }
393     }
394 
instances(&self) -> usize395     fn instances(&self) -> usize {
396         self.instances
397     }
398 
tables(&self) -> usize399     fn tables(&self) -> usize {
400         self.tables
401     }
402 
memories(&self) -> usize403     fn memories(&self) -> usize {
404         self.memories
405     }
406 }
407