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}