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