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:
@@ -111,16 +111,16 @@ async fn test_limits_async() -> Result<()> {
|
||||
_current: usize,
|
||||
desired: usize,
|
||||
_maximum: Option<usize>,
|
||||
) -> bool {
|
||||
desired <= self.memory_size
|
||||
) -> Result<bool> {
|
||||
Ok(desired <= self.memory_size)
|
||||
}
|
||||
async fn table_growing(
|
||||
&mut self,
|
||||
_current: u32,
|
||||
desired: u32,
|
||||
_maximum: Option<u32>,
|
||||
) -> bool {
|
||||
desired <= self.table_elements
|
||||
) -> Result<bool> {
|
||||
Ok(desired <= self.table_elements)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -394,7 +394,12 @@ struct 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)
|
||||
assert!(desired < maximum.unwrap_or(usize::MAX));
|
||||
|
||||
@@ -403,14 +408,19 @@ impl ResourceLimiter for MemoryContext {
|
||||
|
||||
if desired + self.host_memory_used > self.memory_limit {
|
||||
self.limit_exceeded = true;
|
||||
return false;
|
||||
return Ok(false);
|
||||
}
|
||||
|
||||
self.wasm_memory_used = desired;
|
||||
true
|
||||
Ok(true)
|
||||
}
|
||||
fn table_growing(&mut self, _current: u32, _desired: u32, _maximum: Option<u32>) -> bool {
|
||||
true
|
||||
fn table_growing(
|
||||
&mut self,
|
||||
_current: u32,
|
||||
_desired: u32,
|
||||
_maximum: Option<u32>,
|
||||
) -> Result<bool> {
|
||||
Ok(true)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -501,7 +511,7 @@ impl ResourceLimiterAsync for MemoryContext {
|
||||
current: usize,
|
||||
desired: usize,
|
||||
maximum: Option<usize>,
|
||||
) -> bool {
|
||||
) -> Result<bool> {
|
||||
// Show we can await in this async context:
|
||||
tokio::time::sleep(std::time::Duration::from_millis(1)).await;
|
||||
// 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 {
|
||||
self.limit_exceeded = true;
|
||||
return false;
|
||||
return Ok(false);
|
||||
}
|
||||
|
||||
self.wasm_memory_used = desired;
|
||||
true
|
||||
Ok(true)
|
||||
}
|
||||
async fn table_growing(&mut self, _current: u32, _desired: u32, _maximum: Option<u32>) -> bool {
|
||||
true
|
||||
async fn table_growing(
|
||||
&mut self,
|
||||
_current: u32,
|
||||
_desired: u32,
|
||||
_maximum: Option<u32>,
|
||||
) -> Result<bool> {
|
||||
Ok(true)
|
||||
}
|
||||
fn table_grow_failed(&mut self, _e: &anyhow::Error) {}
|
||||
}
|
||||
@@ -619,20 +634,20 @@ impl ResourceLimiter for TableContext {
|
||||
_current: usize,
|
||||
_desired: usize,
|
||||
_maximum: Option<usize>,
|
||||
) -> bool {
|
||||
true
|
||||
) -> Result<bool> {
|
||||
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)
|
||||
assert!(desired < maximum.unwrap_or(u32::MAX));
|
||||
assert_eq!(current, self.elements_used);
|
||||
if desired > self.element_limit {
|
||||
Ok(if desired > self.element_limit {
|
||||
self.limit_exceeded = true;
|
||||
return false;
|
||||
false
|
||||
} else {
|
||||
self.elements_used = desired;
|
||||
true
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -693,18 +708,23 @@ struct 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_desired = desired;
|
||||
true
|
||||
Ok(true)
|
||||
}
|
||||
fn memory_grow_failed(&mut self, err: &anyhow::Error) {
|
||||
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_desired = desired;
|
||||
true
|
||||
Ok(true)
|
||||
}
|
||||
fn table_grow_failed(&mut self, err: &anyhow::Error) {
|
||||
self.table_error = Some(err.to_string());
|
||||
@@ -793,21 +813,26 @@ impl ResourceLimiterAsync for FailureDetector {
|
||||
current: usize,
|
||||
desired: usize,
|
||||
_maximum: Option<usize>,
|
||||
) -> bool {
|
||||
) -> Result<bool> {
|
||||
// Show we can await in this async context:
|
||||
tokio::time::sleep(std::time::Duration::from_millis(1)).await;
|
||||
self.memory_current = current;
|
||||
self.memory_desired = desired;
|
||||
true
|
||||
Ok(true)
|
||||
}
|
||||
fn memory_grow_failed(&mut self, err: &anyhow::Error) {
|
||||
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_desired = desired;
|
||||
true
|
||||
Ok(true)
|
||||
}
|
||||
fn table_grow_failed(&mut self, err: &anyhow::Error) {
|
||||
self.table_error = Some(err.to_string());
|
||||
@@ -903,10 +928,15 @@ impl ResourceLimiter for Panic {
|
||||
_current: usize,
|
||||
_desired: usize,
|
||||
_maximum: Option<usize>,
|
||||
) -> bool {
|
||||
) -> Result<bool> {
|
||||
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");
|
||||
}
|
||||
}
|
||||
@@ -917,10 +947,15 @@ impl ResourceLimiterAsync for Panic {
|
||||
_current: usize,
|
||||
_desired: usize,
|
||||
_maximum: Option<usize>,
|
||||
) -> bool {
|
||||
) -> Result<bool> {
|
||||
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");
|
||||
}
|
||||
}
|
||||
@@ -1059,3 +1094,61 @@ async fn panic_in_async_table_limiter() {
|
||||
.await
|
||||
.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,
|
||||
_request: usize,
|
||||
_max: Option<usize>,
|
||||
) -> bool {
|
||||
) -> Result<bool> {
|
||||
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!()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -528,12 +528,12 @@ fn drop_externref_global_during_module_init() -> Result<()> {
|
||||
struct Limiter;
|
||||
|
||||
impl ResourceLimiter for Limiter {
|
||||
fn memory_growing(&mut self, _: usize, _: usize, _: Option<usize>) -> bool {
|
||||
false
|
||||
fn memory_growing(&mut self, _: usize, _: usize, _: Option<usize>) -> Result<bool> {
|
||||
Ok(false)
|
||||
}
|
||||
|
||||
fn table_growing(&mut self, _: u32, _: u32, _: Option<u32>) -> bool {
|
||||
false
|
||||
fn table_growing(&mut self, _: u32, _: u32, _: Option<u32>) -> Result<bool> {
|
||||
Ok(false)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -13,16 +13,26 @@ struct 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.desired = desired;
|
||||
true
|
||||
Ok(true)
|
||||
}
|
||||
fn memory_grow_failed(&mut self, err: &anyhow::Error) {
|
||||
self.error = Some(err.to_string());
|
||||
}
|
||||
fn table_growing(&mut self, _current: u32, _desired: u32, _maximum: Option<u32>) -> bool {
|
||||
true
|
||||
fn table_growing(
|
||||
&mut self,
|
||||
_current: u32,
|
||||
_desired: u32,
|
||||
_maximum: Option<u32>,
|
||||
) -> Result<bool> {
|
||||
Ok(true)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user