Add a limits and trap-on-OOM options to the CLI (#6149)
* Add a limits and trap-on-OOM options to the CLI This commit adds new options to the `wasmtime` CLI to control the `Store::limiter` behavior at runtime. This enables artificially restriction the memory usage of the wasm instance, for example. Additionally a new option is added to `StoreLimits` to force a trap on growth failure. This is intended to help quickly debug modules with backtraces if OOM is happening, or even diagnosing if OOM is happening in the first place. * Fix compile of fuzzing oracle
This commit is contained in:
@@ -97,13 +97,18 @@ impl StoreLimits {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl ResourceLimiter for StoreLimits {
|
impl ResourceLimiter for StoreLimits {
|
||||||
fn memory_growing(&mut self, current: usize, desired: usize, _maximum: Option<usize>) -> bool {
|
fn memory_growing(
|
||||||
self.alloc(desired - current)
|
&mut self,
|
||||||
|
current: usize,
|
||||||
|
desired: usize,
|
||||||
|
_maximum: Option<usize>,
|
||||||
|
) -> Result<bool> {
|
||||||
|
Ok(self.alloc(desired - current))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn table_growing(&mut self, current: u32, desired: u32, _maximum: Option<u32>) -> bool {
|
fn table_growing(&mut self, current: u32, desired: u32, _maximum: Option<u32>) -> Result<bool> {
|
||||||
let delta = (desired - current) as usize * std::mem::size_of::<usize>();
|
let delta = (desired - current) as usize * std::mem::size_of::<usize>();
|
||||||
self.alloc(delta)
|
Ok(self.alloc(delta))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
use anyhow::{bail, Result};
|
||||||
|
|
||||||
/// Value returned by [`ResourceLimiter::instances`] default method
|
/// Value returned by [`ResourceLimiter::instances`] default method
|
||||||
pub const DEFAULT_INSTANCE_LIMIT: usize = 10000;
|
pub const DEFAULT_INSTANCE_LIMIT: usize = 10000;
|
||||||
/// Value returned by [`ResourceLimiter::tables`] default method
|
/// Value returned by [`ResourceLimiter::tables`] default method
|
||||||
@@ -40,22 +42,36 @@ pub trait ResourceLimiter {
|
|||||||
/// The `current` and `desired` amounts are guaranteed to always be
|
/// The `current` and `desired` amounts are guaranteed to always be
|
||||||
/// multiples of the WebAssembly page size, 64KiB.
|
/// multiples of the WebAssembly page size, 64KiB.
|
||||||
///
|
///
|
||||||
/// This function should return `true` to indicate that the growing
|
/// This function is not invoked when the requested size doesn't fit in
|
||||||
/// operation is permitted or `false` if not permitted. Returning `true`
|
/// `usize`. Additionally this function is not invoked for shared memories
|
||||||
/// when a maximum has been exceeded will have no effect as the linear
|
/// at this time. Otherwise even when `desired` exceeds `maximum` this
|
||||||
/// memory will not grow.
|
/// function will still be called.
|
||||||
///
|
///
|
||||||
/// This function is not guaranteed to be invoked for all requests to
|
/// ## Return Value
|
||||||
/// `memory.grow`. Requests where the allocation requested size doesn't fit
|
|
||||||
/// in `usize` or exceeds the memory's listed maximum size may not invoke
|
|
||||||
/// this method.
|
|
||||||
///
|
///
|
||||||
/// Returning `false` from this method will cause the `memory.grow`
|
/// If `Ok(true)` is returned from this function then the growth operation
|
||||||
|
/// is allowed. This means that the wasm `memory.grow` instruction will
|
||||||
|
/// return with the `desired` size, in wasm pages. Note that even if
|
||||||
|
/// `Ok(true)` is returned, though, if `desired` exceeds `maximum` then the
|
||||||
|
/// growth operation will still fail.
|
||||||
|
///
|
||||||
|
/// If `Ok(false)` is returned then this will cause the `memory.grow`
|
||||||
/// instruction in a module to return -1 (failure), or in the case of an
|
/// instruction in a module to return -1 (failure), or in the case of an
|
||||||
/// embedder API calling [`Memory::new`](crate::Memory::new) or
|
/// embedder API calling [`Memory::new`](crate::Memory::new) or
|
||||||
/// [`Memory::grow`](crate::Memory::grow) an error will be returned from
|
/// [`Memory::grow`](crate::Memory::grow) an error will be returned from
|
||||||
/// those methods.
|
/// those methods.
|
||||||
fn memory_growing(&mut self, current: usize, desired: usize, maximum: Option<usize>) -> bool;
|
///
|
||||||
|
/// If `Err(e)` is returned then the `memory.grow` function will behave
|
||||||
|
/// as if a trap has been raised. Note that this is not necessarily
|
||||||
|
/// compliant with the WebAssembly specification but it can be a handy and
|
||||||
|
/// useful tool to get a precise backtrace at "what requested so much memory
|
||||||
|
/// to cause a growth failure?".
|
||||||
|
fn memory_growing(
|
||||||
|
&mut self,
|
||||||
|
current: usize,
|
||||||
|
desired: usize,
|
||||||
|
maximum: Option<usize>,
|
||||||
|
) -> Result<bool>;
|
||||||
|
|
||||||
/// Notifies the resource limiter that growing a linear memory, permitted by
|
/// Notifies the resource limiter that growing a linear memory, permitted by
|
||||||
/// the `memory_growing` method, has failed.
|
/// the `memory_growing` method, has failed.
|
||||||
@@ -73,17 +89,12 @@ pub trait ResourceLimiter {
|
|||||||
/// * `maximum` is either the table's maximum or a maximum from an instance
|
/// * `maximum` is either the table's maximum or a maximum from an instance
|
||||||
/// allocator. A value of `None` indicates that the table is unbounded.
|
/// allocator. A value of `None` indicates that the table is unbounded.
|
||||||
///
|
///
|
||||||
/// This function should return `true` to indicate that the growing
|
|
||||||
/// operation is permitted or `false` if not permitted. Returning `true`
|
|
||||||
/// when a maximum has been exceeded will have no effect as the table will
|
|
||||||
/// not grow.
|
|
||||||
///
|
|
||||||
/// Currently in Wasmtime each table element requires a pointer's worth of
|
/// Currently in Wasmtime each table element requires a pointer's worth of
|
||||||
/// space (e.g. `mem::size_of::<usize>()`).
|
/// space (e.g. `mem::size_of::<usize>()`).
|
||||||
///
|
///
|
||||||
/// Like `memory_growing` returning `false` from this function will cause
|
/// See the details on the return values for `memory_growing` for what the
|
||||||
/// `table.grow` to return -1 or embedder APIs will return an error.
|
/// return value of this function indicates.
|
||||||
fn table_growing(&mut self, current: u32, desired: u32, maximum: Option<u32>) -> bool;
|
fn table_growing(&mut self, current: u32, desired: u32, maximum: Option<u32>) -> Result<bool>;
|
||||||
|
|
||||||
/// Notifies the resource limiter that growing a linear memory, permitted by
|
/// Notifies the resource limiter that growing a linear memory, permitted by
|
||||||
/// the `table_growing` method, has failed.
|
/// the `table_growing` method, has failed.
|
||||||
@@ -146,13 +157,18 @@ pub trait ResourceLimiterAsync {
|
|||||||
current: usize,
|
current: usize,
|
||||||
desired: usize,
|
desired: usize,
|
||||||
maximum: Option<usize>,
|
maximum: Option<usize>,
|
||||||
) -> bool;
|
) -> Result<bool>;
|
||||||
|
|
||||||
/// Identical to [`ResourceLimiter::memory_grow_failed`]
|
/// Identical to [`ResourceLimiter::memory_grow_failed`]
|
||||||
fn memory_grow_failed(&mut self, _error: &anyhow::Error) {}
|
fn memory_grow_failed(&mut self, _error: &anyhow::Error) {}
|
||||||
|
|
||||||
/// Asynchronous version of [`ResourceLimiter::table_growing`]
|
/// Asynchronous version of [`ResourceLimiter::table_growing`]
|
||||||
async fn table_growing(&mut self, current: u32, desired: u32, maximum: Option<u32>) -> bool;
|
async fn table_growing(
|
||||||
|
&mut self,
|
||||||
|
current: u32,
|
||||||
|
desired: u32,
|
||||||
|
maximum: Option<u32>,
|
||||||
|
) -> Result<bool>;
|
||||||
|
|
||||||
/// Identical to [`ResourceLimiter::table_grow_failed`]
|
/// Identical to [`ResourceLimiter::table_grow_failed`]
|
||||||
fn table_grow_failed(&mut self, _error: &anyhow::Error) {}
|
fn table_grow_failed(&mut self, _error: &anyhow::Error) {}
|
||||||
@@ -187,7 +203,10 @@ impl StoreLimitsBuilder {
|
|||||||
|
|
||||||
/// The maximum number of bytes a linear memory can grow to.
|
/// The maximum number of bytes a linear memory can grow to.
|
||||||
///
|
///
|
||||||
/// Growing a linear memory beyond this limit will fail.
|
/// Growing a linear memory beyond this limit will fail. This limit is
|
||||||
|
/// applied to each linear memory individually, so if a wasm module has
|
||||||
|
/// multiple linear memories then they're all allowed to reach up to the
|
||||||
|
/// `limit` specified.
|
||||||
///
|
///
|
||||||
/// By default, linear memory will not be limited.
|
/// By default, linear memory will not be limited.
|
||||||
pub fn memory_size(mut self, limit: usize) -> Self {
|
pub fn memory_size(mut self, limit: usize) -> Self {
|
||||||
@@ -197,7 +216,9 @@ impl StoreLimitsBuilder {
|
|||||||
|
|
||||||
/// The maximum number of elements in a table.
|
/// The maximum number of elements in a table.
|
||||||
///
|
///
|
||||||
/// Growing a table beyond this limit will fail.
|
/// Growing a table beyond this limit will fail. This limit is applied to
|
||||||
|
/// each table individually, so if a wasm module has multiple tables then
|
||||||
|
/// they're all allowed to reach up to the `limit` specified.
|
||||||
///
|
///
|
||||||
/// By default, table elements will not be limited.
|
/// By default, table elements will not be limited.
|
||||||
pub fn table_elements(mut self, limit: u32) -> Self {
|
pub fn table_elements(mut self, limit: u32) -> Self {
|
||||||
@@ -235,6 +256,20 @@ impl StoreLimitsBuilder {
|
|||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Indicates that a trap should be raised whenever a growth operation
|
||||||
|
/// would fail.
|
||||||
|
///
|
||||||
|
/// This operation will force `memory.grow` and `table.grow` instructions
|
||||||
|
/// to raise a trap on failure instead of returning -1. This is not
|
||||||
|
/// necessarily spec-compliant, but it can be quite handy when debugging a
|
||||||
|
/// module that fails to allocate memory and might behave oddly as a result.
|
||||||
|
///
|
||||||
|
/// This value defaults to `false`.
|
||||||
|
pub fn trap_on_grow_failure(mut self, trap: bool) -> Self {
|
||||||
|
self.0.trap_on_grow_failure = trap;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
/// Consumes this builder and returns the [`StoreLimits`].
|
/// Consumes this builder and returns the [`StoreLimits`].
|
||||||
pub fn build(self) -> StoreLimits {
|
pub fn build(self) -> StoreLimits {
|
||||||
self.0
|
self.0
|
||||||
@@ -249,12 +284,14 @@ impl StoreLimitsBuilder {
|
|||||||
/// This is a convenience type included to avoid needing to implement the
|
/// This is a convenience type included to avoid needing to implement the
|
||||||
/// [`ResourceLimiter`] trait if your use case fits in the static configuration
|
/// [`ResourceLimiter`] trait if your use case fits in the static configuration
|
||||||
/// that this [`StoreLimits`] provides.
|
/// that this [`StoreLimits`] provides.
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
pub struct StoreLimits {
|
pub struct StoreLimits {
|
||||||
memory_size: Option<usize>,
|
memory_size: Option<usize>,
|
||||||
table_elements: Option<u32>,
|
table_elements: Option<u32>,
|
||||||
instances: usize,
|
instances: usize,
|
||||||
tables: usize,
|
tables: usize,
|
||||||
memories: usize,
|
memories: usize,
|
||||||
|
trap_on_grow_failure: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for StoreLimits {
|
impl Default for StoreLimits {
|
||||||
@@ -265,22 +302,44 @@ impl Default for StoreLimits {
|
|||||||
instances: DEFAULT_INSTANCE_LIMIT,
|
instances: DEFAULT_INSTANCE_LIMIT,
|
||||||
tables: DEFAULT_TABLE_LIMIT,
|
tables: DEFAULT_TABLE_LIMIT,
|
||||||
memories: DEFAULT_MEMORY_LIMIT,
|
memories: DEFAULT_MEMORY_LIMIT,
|
||||||
|
trap_on_grow_failure: false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ResourceLimiter for StoreLimits {
|
impl ResourceLimiter for StoreLimits {
|
||||||
fn memory_growing(&mut self, _current: usize, desired: usize, _maximum: Option<usize>) -> bool {
|
fn memory_growing(
|
||||||
match self.memory_size {
|
&mut self,
|
||||||
|
_current: usize,
|
||||||
|
desired: usize,
|
||||||
|
maximum: Option<usize>,
|
||||||
|
) -> Result<bool> {
|
||||||
|
let allow = match self.memory_size {
|
||||||
Some(limit) if desired > limit => false,
|
Some(limit) if desired > limit => false,
|
||||||
_ => true,
|
_ => match maximum {
|
||||||
|
Some(max) if desired > max => false,
|
||||||
|
_ => true,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
if !allow && self.trap_on_grow_failure {
|
||||||
|
bail!("forcing trap when growing memory to {desired} bytes")
|
||||||
|
} else {
|
||||||
|
Ok(allow)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn table_growing(&mut self, _current: u32, desired: u32, _maximum: Option<u32>) -> bool {
|
fn table_growing(&mut self, _current: u32, desired: u32, maximum: Option<u32>) -> Result<bool> {
|
||||||
match self.table_elements {
|
let allow = match self.table_elements {
|
||||||
Some(limit) if desired > limit => false,
|
Some(limit) if desired > limit => false,
|
||||||
_ => true,
|
_ => match maximum {
|
||||||
|
Some(max) if desired > max => false,
|
||||||
|
_ => true,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
if !allow && self.trap_on_grow_failure {
|
||||||
|
bail!("forcing trap when growing table to {desired} elements")
|
||||||
|
} else {
|
||||||
|
Ok(allow)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1874,19 +1874,18 @@ unsafe impl<T> wasmtime_runtime::Store for StoreInner<T> {
|
|||||||
) -> Result<bool, anyhow::Error> {
|
) -> Result<bool, anyhow::Error> {
|
||||||
match self.limiter {
|
match self.limiter {
|
||||||
Some(ResourceLimiterInner::Sync(ref mut limiter)) => {
|
Some(ResourceLimiterInner::Sync(ref mut limiter)) => {
|
||||||
Ok(limiter(&mut self.data).memory_growing(current, desired, maximum))
|
limiter(&mut self.data).memory_growing(current, desired, maximum)
|
||||||
}
|
}
|
||||||
#[cfg(feature = "async")]
|
#[cfg(feature = "async")]
|
||||||
Some(ResourceLimiterInner::Async(ref mut limiter)) => unsafe {
|
Some(ResourceLimiterInner::Async(ref mut limiter)) => unsafe {
|
||||||
Ok(self
|
self.inner
|
||||||
.inner
|
|
||||||
.async_cx()
|
.async_cx()
|
||||||
.expect("ResourceLimiterAsync requires async Store")
|
.expect("ResourceLimiterAsync requires async Store")
|
||||||
.block_on(
|
.block_on(
|
||||||
limiter(&mut self.data)
|
limiter(&mut self.data)
|
||||||
.memory_growing(current, desired, maximum)
|
.memory_growing(current, desired, maximum)
|
||||||
.as_mut(),
|
.as_mut(),
|
||||||
)?)
|
)?
|
||||||
},
|
},
|
||||||
None => Ok(true),
|
None => Ok(true),
|
||||||
}
|
}
|
||||||
@@ -1923,17 +1922,17 @@ unsafe impl<T> wasmtime_runtime::Store for StoreInner<T> {
|
|||||||
|
|
||||||
match self.limiter {
|
match self.limiter {
|
||||||
Some(ResourceLimiterInner::Sync(ref mut limiter)) => {
|
Some(ResourceLimiterInner::Sync(ref mut limiter)) => {
|
||||||
Ok(limiter(&mut self.data).table_growing(current, desired, maximum))
|
limiter(&mut self.data).table_growing(current, desired, maximum)
|
||||||
}
|
}
|
||||||
#[cfg(feature = "async")]
|
#[cfg(feature = "async")]
|
||||||
Some(ResourceLimiterInner::Async(ref mut limiter)) => unsafe {
|
Some(ResourceLimiterInner::Async(ref mut limiter)) => unsafe {
|
||||||
Ok(async_cx
|
async_cx
|
||||||
.expect("ResourceLimiterAsync requires async Store")
|
.expect("ResourceLimiterAsync requires async Store")
|
||||||
.block_on(
|
.block_on(
|
||||||
limiter(&mut self.data)
|
limiter(&mut self.data)
|
||||||
.table_growing(current, desired, maximum)
|
.table_growing(current, desired, maximum)
|
||||||
.as_mut(),
|
.as_mut(),
|
||||||
)?)
|
)?
|
||||||
},
|
},
|
||||||
None => Ok(true),
|
None => Ok(true),
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,7 +9,9 @@ use std::io::Write;
|
|||||||
use std::path::{Component, Path, PathBuf};
|
use std::path::{Component, Path, PathBuf};
|
||||||
use std::thread;
|
use std::thread;
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
use wasmtime::{Engine, Func, Linker, Module, Store, Val, ValType};
|
use wasmtime::{
|
||||||
|
Engine, Func, Linker, Module, Store, StoreLimits, StoreLimitsBuilder, Val, ValType,
|
||||||
|
};
|
||||||
use wasmtime_cli_flags::{CommonOptions, WasiModules};
|
use wasmtime_cli_flags::{CommonOptions, WasiModules};
|
||||||
use wasmtime_wasi::maybe_exit_on_error;
|
use wasmtime_wasi::maybe_exit_on_error;
|
||||||
use wasmtime_wasi::sync::{ambient_authority, Dir, TcpListener, WasiCtxBuilder};
|
use wasmtime_wasi::sync::{ambient_authority, Dir, TcpListener, WasiCtxBuilder};
|
||||||
@@ -166,6 +168,38 @@ pub struct RunCommand {
|
|||||||
/// The arguments to pass to the module
|
/// The arguments to pass to the module
|
||||||
#[clap(value_name = "ARGS")]
|
#[clap(value_name = "ARGS")]
|
||||||
module_args: Vec<String>,
|
module_args: Vec<String>,
|
||||||
|
|
||||||
|
/// Maximum size, in bytes, that a linear memory is allowed to reach.
|
||||||
|
///
|
||||||
|
/// Growth beyond this limit will cause `memory.grow` instructions in
|
||||||
|
/// WebAssembly modules to return -1 and fail.
|
||||||
|
#[clap(long, value_name = "BYTES")]
|
||||||
|
max_memory_size: Option<usize>,
|
||||||
|
|
||||||
|
/// Maximum size, in table elements, that a table is allowed to reach.
|
||||||
|
#[clap(long)]
|
||||||
|
max_table_elements: Option<u32>,
|
||||||
|
|
||||||
|
/// Maximum number of WebAssembly instances allowed to be created.
|
||||||
|
#[clap(long)]
|
||||||
|
max_instances: Option<usize>,
|
||||||
|
|
||||||
|
/// Maximum number of WebAssembly tables allowed to be created.
|
||||||
|
#[clap(long)]
|
||||||
|
max_tables: Option<usize>,
|
||||||
|
|
||||||
|
/// Maximum number of WebAssembly linear memories allowed to be created.
|
||||||
|
#[clap(long)]
|
||||||
|
max_memories: Option<usize>,
|
||||||
|
|
||||||
|
/// Force a trap to be raised on `memory.grow` and `table.grow` failure
|
||||||
|
/// instead of returning -1 from these instructions.
|
||||||
|
///
|
||||||
|
/// This is not necessarily a spec-compliant option to enable but can be
|
||||||
|
/// useful for tracking down a backtrace of what is requesting so much
|
||||||
|
/// memory, for example.
|
||||||
|
#[clap(long)]
|
||||||
|
trap_on_grow_failure: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl RunCommand {
|
impl RunCommand {
|
||||||
@@ -212,6 +246,27 @@ impl RunCommand {
|
|||||||
preopen_sockets,
|
preopen_sockets,
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
|
let mut limits = StoreLimitsBuilder::new();
|
||||||
|
if let Some(max) = self.max_memory_size {
|
||||||
|
limits = limits.memory_size(max);
|
||||||
|
}
|
||||||
|
if let Some(max) = self.max_table_elements {
|
||||||
|
limits = limits.table_elements(max);
|
||||||
|
}
|
||||||
|
if let Some(max) = self.max_instances {
|
||||||
|
limits = limits.instances(max);
|
||||||
|
}
|
||||||
|
if let Some(max) = self.max_tables {
|
||||||
|
limits = limits.tables(max);
|
||||||
|
}
|
||||||
|
if let Some(max) = self.max_memories {
|
||||||
|
limits = limits.memories(max);
|
||||||
|
}
|
||||||
|
store.data_mut().limits = limits
|
||||||
|
.trap_on_grow_failure(self.trap_on_grow_failure)
|
||||||
|
.build();
|
||||||
|
store.limiter(|t| &mut t.limits);
|
||||||
|
|
||||||
// If fuel has been configured, we want to add the configured
|
// If fuel has been configured, we want to add the configured
|
||||||
// fuel amount to this store.
|
// fuel amount to this store.
|
||||||
if let Some(fuel) = self.common.fuel {
|
if let Some(fuel) = self.common.fuel {
|
||||||
@@ -470,6 +525,7 @@ struct Host {
|
|||||||
wasi_nn: Option<Arc<WasiNnCtx>>,
|
wasi_nn: Option<Arc<WasiNnCtx>>,
|
||||||
#[cfg(feature = "wasi-threads")]
|
#[cfg(feature = "wasi-threads")]
|
||||||
wasi_threads: Option<Arc<WasiThreadsCtx<Host>>>,
|
wasi_threads: Option<Arc<WasiThreadsCtx<Host>>>,
|
||||||
|
limits: StoreLimits,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Populates the given `Linker` with WASI APIs.
|
/// Populates the given `Linker` with WASI APIs.
|
||||||
|
|||||||
@@ -111,16 +111,16 @@ async fn test_limits_async() -> Result<()> {
|
|||||||
_current: usize,
|
_current: usize,
|
||||||
desired: usize,
|
desired: usize,
|
||||||
_maximum: Option<usize>,
|
_maximum: Option<usize>,
|
||||||
) -> bool {
|
) -> Result<bool> {
|
||||||
desired <= self.memory_size
|
Ok(desired <= self.memory_size)
|
||||||
}
|
}
|
||||||
async fn table_growing(
|
async fn table_growing(
|
||||||
&mut self,
|
&mut self,
|
||||||
_current: u32,
|
_current: u32,
|
||||||
desired: u32,
|
desired: u32,
|
||||||
_maximum: Option<u32>,
|
_maximum: Option<u32>,
|
||||||
) -> bool {
|
) -> Result<bool> {
|
||||||
desired <= self.table_elements
|
Ok(desired <= self.table_elements)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -394,7 +394,12 @@ struct MemoryContext {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl ResourceLimiter for MemoryContext {
|
impl ResourceLimiter for MemoryContext {
|
||||||
fn memory_growing(&mut self, current: usize, desired: usize, maximum: Option<usize>) -> bool {
|
fn memory_growing(
|
||||||
|
&mut self,
|
||||||
|
current: usize,
|
||||||
|
desired: usize,
|
||||||
|
maximum: Option<usize>,
|
||||||
|
) -> Result<bool> {
|
||||||
// Check if the desired exceeds a maximum (either from Wasm or from the host)
|
// Check if the desired exceeds a maximum (either from Wasm or from the host)
|
||||||
assert!(desired < maximum.unwrap_or(usize::MAX));
|
assert!(desired < maximum.unwrap_or(usize::MAX));
|
||||||
|
|
||||||
@@ -403,14 +408,19 @@ impl ResourceLimiter for MemoryContext {
|
|||||||
|
|
||||||
if desired + self.host_memory_used > self.memory_limit {
|
if desired + self.host_memory_used > self.memory_limit {
|
||||||
self.limit_exceeded = true;
|
self.limit_exceeded = true;
|
||||||
return false;
|
return Ok(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
self.wasm_memory_used = desired;
|
self.wasm_memory_used = desired;
|
||||||
true
|
Ok(true)
|
||||||
}
|
}
|
||||||
fn table_growing(&mut self, _current: u32, _desired: u32, _maximum: Option<u32>) -> bool {
|
fn table_growing(
|
||||||
true
|
&mut self,
|
||||||
|
_current: u32,
|
||||||
|
_desired: u32,
|
||||||
|
_maximum: Option<u32>,
|
||||||
|
) -> Result<bool> {
|
||||||
|
Ok(true)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -501,7 +511,7 @@ impl ResourceLimiterAsync for MemoryContext {
|
|||||||
current: usize,
|
current: usize,
|
||||||
desired: usize,
|
desired: usize,
|
||||||
maximum: Option<usize>,
|
maximum: Option<usize>,
|
||||||
) -> bool {
|
) -> Result<bool> {
|
||||||
// Show we can await in this async context:
|
// Show we can await in this async context:
|
||||||
tokio::time::sleep(std::time::Duration::from_millis(1)).await;
|
tokio::time::sleep(std::time::Duration::from_millis(1)).await;
|
||||||
// Check if the desired exceeds a maximum (either from Wasm or from the host)
|
// Check if the desired exceeds a maximum (either from Wasm or from the host)
|
||||||
@@ -512,14 +522,19 @@ impl ResourceLimiterAsync for MemoryContext {
|
|||||||
|
|
||||||
if desired + self.host_memory_used > self.memory_limit {
|
if desired + self.host_memory_used > self.memory_limit {
|
||||||
self.limit_exceeded = true;
|
self.limit_exceeded = true;
|
||||||
return false;
|
return Ok(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
self.wasm_memory_used = desired;
|
self.wasm_memory_used = desired;
|
||||||
true
|
Ok(true)
|
||||||
}
|
}
|
||||||
async fn table_growing(&mut self, _current: u32, _desired: u32, _maximum: Option<u32>) -> bool {
|
async fn table_growing(
|
||||||
true
|
&mut self,
|
||||||
|
_current: u32,
|
||||||
|
_desired: u32,
|
||||||
|
_maximum: Option<u32>,
|
||||||
|
) -> Result<bool> {
|
||||||
|
Ok(true)
|
||||||
}
|
}
|
||||||
fn table_grow_failed(&mut self, _e: &anyhow::Error) {}
|
fn table_grow_failed(&mut self, _e: &anyhow::Error) {}
|
||||||
}
|
}
|
||||||
@@ -619,20 +634,20 @@ impl ResourceLimiter for TableContext {
|
|||||||
_current: usize,
|
_current: usize,
|
||||||
_desired: usize,
|
_desired: usize,
|
||||||
_maximum: Option<usize>,
|
_maximum: Option<usize>,
|
||||||
) -> bool {
|
) -> Result<bool> {
|
||||||
true
|
Ok(true)
|
||||||
}
|
}
|
||||||
fn table_growing(&mut self, current: u32, desired: u32, maximum: Option<u32>) -> bool {
|
fn table_growing(&mut self, current: u32, desired: u32, maximum: Option<u32>) -> Result<bool> {
|
||||||
// Check if the desired exceeds a maximum (either from Wasm or from the host)
|
// Check if the desired exceeds a maximum (either from Wasm or from the host)
|
||||||
assert!(desired < maximum.unwrap_or(u32::MAX));
|
assert!(desired < maximum.unwrap_or(u32::MAX));
|
||||||
assert_eq!(current, self.elements_used);
|
assert_eq!(current, self.elements_used);
|
||||||
if desired > self.element_limit {
|
Ok(if desired > self.element_limit {
|
||||||
self.limit_exceeded = true;
|
self.limit_exceeded = true;
|
||||||
return false;
|
false
|
||||||
} else {
|
} else {
|
||||||
self.elements_used = desired;
|
self.elements_used = desired;
|
||||||
true
|
true
|
||||||
}
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -693,18 +708,23 @@ struct FailureDetector {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl ResourceLimiter for FailureDetector {
|
impl ResourceLimiter for FailureDetector {
|
||||||
fn memory_growing(&mut self, current: usize, desired: usize, _maximum: Option<usize>) -> bool {
|
fn memory_growing(
|
||||||
|
&mut self,
|
||||||
|
current: usize,
|
||||||
|
desired: usize,
|
||||||
|
_maximum: Option<usize>,
|
||||||
|
) -> Result<bool> {
|
||||||
self.memory_current = current;
|
self.memory_current = current;
|
||||||
self.memory_desired = desired;
|
self.memory_desired = desired;
|
||||||
true
|
Ok(true)
|
||||||
}
|
}
|
||||||
fn memory_grow_failed(&mut self, err: &anyhow::Error) {
|
fn memory_grow_failed(&mut self, err: &anyhow::Error) {
|
||||||
self.memory_error = Some(err.to_string());
|
self.memory_error = Some(err.to_string());
|
||||||
}
|
}
|
||||||
fn table_growing(&mut self, current: u32, desired: u32, _maximum: Option<u32>) -> bool {
|
fn table_growing(&mut self, current: u32, desired: u32, _maximum: Option<u32>) -> Result<bool> {
|
||||||
self.table_current = current;
|
self.table_current = current;
|
||||||
self.table_desired = desired;
|
self.table_desired = desired;
|
||||||
true
|
Ok(true)
|
||||||
}
|
}
|
||||||
fn table_grow_failed(&mut self, err: &anyhow::Error) {
|
fn table_grow_failed(&mut self, err: &anyhow::Error) {
|
||||||
self.table_error = Some(err.to_string());
|
self.table_error = Some(err.to_string());
|
||||||
@@ -793,21 +813,26 @@ impl ResourceLimiterAsync for FailureDetector {
|
|||||||
current: usize,
|
current: usize,
|
||||||
desired: usize,
|
desired: usize,
|
||||||
_maximum: Option<usize>,
|
_maximum: Option<usize>,
|
||||||
) -> bool {
|
) -> Result<bool> {
|
||||||
// Show we can await in this async context:
|
// Show we can await in this async context:
|
||||||
tokio::time::sleep(std::time::Duration::from_millis(1)).await;
|
tokio::time::sleep(std::time::Duration::from_millis(1)).await;
|
||||||
self.memory_current = current;
|
self.memory_current = current;
|
||||||
self.memory_desired = desired;
|
self.memory_desired = desired;
|
||||||
true
|
Ok(true)
|
||||||
}
|
}
|
||||||
fn memory_grow_failed(&mut self, err: &anyhow::Error) {
|
fn memory_grow_failed(&mut self, err: &anyhow::Error) {
|
||||||
self.memory_error = Some(err.to_string());
|
self.memory_error = Some(err.to_string());
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn table_growing(&mut self, current: u32, desired: u32, _maximum: Option<u32>) -> bool {
|
async fn table_growing(
|
||||||
|
&mut self,
|
||||||
|
current: u32,
|
||||||
|
desired: u32,
|
||||||
|
_maximum: Option<u32>,
|
||||||
|
) -> Result<bool> {
|
||||||
self.table_current = current;
|
self.table_current = current;
|
||||||
self.table_desired = desired;
|
self.table_desired = desired;
|
||||||
true
|
Ok(true)
|
||||||
}
|
}
|
||||||
fn table_grow_failed(&mut self, err: &anyhow::Error) {
|
fn table_grow_failed(&mut self, err: &anyhow::Error) {
|
||||||
self.table_error = Some(err.to_string());
|
self.table_error = Some(err.to_string());
|
||||||
@@ -903,10 +928,15 @@ impl ResourceLimiter for Panic {
|
|||||||
_current: usize,
|
_current: usize,
|
||||||
_desired: usize,
|
_desired: usize,
|
||||||
_maximum: Option<usize>,
|
_maximum: Option<usize>,
|
||||||
) -> bool {
|
) -> Result<bool> {
|
||||||
panic!("resource limiter memory growing");
|
panic!("resource limiter memory growing");
|
||||||
}
|
}
|
||||||
fn table_growing(&mut self, _current: u32, _desired: u32, _maximum: Option<u32>) -> bool {
|
fn table_growing(
|
||||||
|
&mut self,
|
||||||
|
_current: u32,
|
||||||
|
_desired: u32,
|
||||||
|
_maximum: Option<u32>,
|
||||||
|
) -> Result<bool> {
|
||||||
panic!("resource limiter table growing");
|
panic!("resource limiter table growing");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -917,10 +947,15 @@ impl ResourceLimiterAsync for Panic {
|
|||||||
_current: usize,
|
_current: usize,
|
||||||
_desired: usize,
|
_desired: usize,
|
||||||
_maximum: Option<usize>,
|
_maximum: Option<usize>,
|
||||||
) -> bool {
|
) -> Result<bool> {
|
||||||
panic!("async resource limiter memory growing");
|
panic!("async resource limiter memory growing");
|
||||||
}
|
}
|
||||||
async fn table_growing(&mut self, _current: u32, _desired: u32, _maximum: Option<u32>) -> bool {
|
async fn table_growing(
|
||||||
|
&mut self,
|
||||||
|
_current: u32,
|
||||||
|
_desired: u32,
|
||||||
|
_maximum: Option<u32>,
|
||||||
|
) -> Result<bool> {
|
||||||
panic!("async resource limiter table growing");
|
panic!("async resource limiter table growing");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1059,3 +1094,61 @@ async fn panic_in_async_table_limiter() {
|
|||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn growth_trap() -> Result<()> {
|
||||||
|
let engine = Engine::default();
|
||||||
|
let module = Module::new(
|
||||||
|
&engine,
|
||||||
|
r#"(module
|
||||||
|
(memory $m (export "m") 0)
|
||||||
|
(table (export "t") 0 anyfunc)
|
||||||
|
(func (export "grow") (param i32) (result i32)
|
||||||
|
(memory.grow $m (local.get 0)))
|
||||||
|
)"#,
|
||||||
|
)?;
|
||||||
|
|
||||||
|
let mut store = Store::new(
|
||||||
|
&engine,
|
||||||
|
StoreLimitsBuilder::new()
|
||||||
|
.memory_size(WASM_PAGE_SIZE)
|
||||||
|
.table_elements(1)
|
||||||
|
.trap_on_grow_failure(true)
|
||||||
|
.build(),
|
||||||
|
);
|
||||||
|
store.limiter(|s| s as &mut dyn ResourceLimiter);
|
||||||
|
|
||||||
|
let instance = Instance::new(&mut store, &module, &[])?;
|
||||||
|
|
||||||
|
// Test instance exports and host objects hitting the limit
|
||||||
|
for memory in [
|
||||||
|
instance.get_memory(&mut store, "m").unwrap(),
|
||||||
|
Memory::new(&mut store, MemoryType::new(0, None))?,
|
||||||
|
] {
|
||||||
|
memory.grow(&mut store, 1)?;
|
||||||
|
assert!(memory.grow(&mut store, 1).is_err());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test instance exports and host objects hitting the limit
|
||||||
|
for table in [
|
||||||
|
instance.get_table(&mut store, "t").unwrap(),
|
||||||
|
Table::new(
|
||||||
|
&mut store,
|
||||||
|
TableType::new(ValType::FuncRef, 0, None),
|
||||||
|
Val::FuncRef(None),
|
||||||
|
)?,
|
||||||
|
] {
|
||||||
|
table.grow(&mut store, 1, Val::FuncRef(None))?;
|
||||||
|
assert!(table.grow(&mut store, 1, Val::FuncRef(None)).is_err());
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut store = Store::new(&engine, store.data().clone());
|
||||||
|
store.limiter(|s| s as &mut dyn ResourceLimiter);
|
||||||
|
let instance = Instance::new(&mut store, &module, &[])?;
|
||||||
|
let grow = instance.get_func(&mut store, "grow").unwrap();
|
||||||
|
let grow = grow.typed::<i32, i32>(&store).unwrap();
|
||||||
|
grow.call(&mut store, 1)?;
|
||||||
|
assert!(grow.call(&mut store, 1).is_err());
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|||||||
@@ -306,11 +306,16 @@ fn massive_64_bit_still_limited() -> Result<()> {
|
|||||||
_current: usize,
|
_current: usize,
|
||||||
_request: usize,
|
_request: usize,
|
||||||
_max: Option<usize>,
|
_max: Option<usize>,
|
||||||
) -> bool {
|
) -> Result<bool> {
|
||||||
self.hit = true;
|
self.hit = true;
|
||||||
true
|
Ok(true)
|
||||||
}
|
}
|
||||||
fn table_growing(&mut self, _current: u32, _request: u32, _max: Option<u32>) -> bool {
|
fn table_growing(
|
||||||
|
&mut self,
|
||||||
|
_current: u32,
|
||||||
|
_request: u32,
|
||||||
|
_max: Option<u32>,
|
||||||
|
) -> Result<bool> {
|
||||||
unreachable!()
|
unreachable!()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -528,12 +528,12 @@ fn drop_externref_global_during_module_init() -> Result<()> {
|
|||||||
struct Limiter;
|
struct Limiter;
|
||||||
|
|
||||||
impl ResourceLimiter for Limiter {
|
impl ResourceLimiter for Limiter {
|
||||||
fn memory_growing(&mut self, _: usize, _: usize, _: Option<usize>) -> bool {
|
fn memory_growing(&mut self, _: usize, _: usize, _: Option<usize>) -> Result<bool> {
|
||||||
false
|
Ok(false)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn table_growing(&mut self, _: u32, _: u32, _: Option<u32>) -> bool {
|
fn table_growing(&mut self, _: u32, _: u32, _: Option<u32>) -> Result<bool> {
|
||||||
false
|
Ok(false)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -13,16 +13,26 @@ struct MemoryGrowFailureDetector {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl ResourceLimiter for MemoryGrowFailureDetector {
|
impl ResourceLimiter for MemoryGrowFailureDetector {
|
||||||
fn memory_growing(&mut self, current: usize, desired: usize, _maximum: Option<usize>) -> bool {
|
fn memory_growing(
|
||||||
|
&mut self,
|
||||||
|
current: usize,
|
||||||
|
desired: usize,
|
||||||
|
_maximum: Option<usize>,
|
||||||
|
) -> Result<bool> {
|
||||||
self.current = current;
|
self.current = current;
|
||||||
self.desired = desired;
|
self.desired = desired;
|
||||||
true
|
Ok(true)
|
||||||
}
|
}
|
||||||
fn memory_grow_failed(&mut self, err: &anyhow::Error) {
|
fn memory_grow_failed(&mut self, err: &anyhow::Error) {
|
||||||
self.error = Some(err.to_string());
|
self.error = Some(err.to_string());
|
||||||
}
|
}
|
||||||
fn table_growing(&mut self, _current: u32, _desired: u32, _maximum: Option<u32>) -> bool {
|
fn table_growing(
|
||||||
true
|
&mut self,
|
||||||
|
_current: u32,
|
||||||
|
_desired: u32,
|
||||||
|
_maximum: Option<u32>,
|
||||||
|
) -> Result<bool> {
|
||||||
|
Ok(true)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user