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