Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Graceful Handling of OOM in table.grow #5394

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -344,6 +344,11 @@ name = "table"
path = "examples/table.rs"
required-features = ["backend"]

[[example]]
name = "table-large-grow"
path = "examples/table_large_grow.rs"
required-features = ["backend"]

[[example]]
name = "memory"
path = "examples/memory.rs"
Expand Down
48 changes: 48 additions & 0 deletions examples/table_large_grow.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
//! Example demonstrating WebAssembly table growth limits
//!
//! Tests growing a table with a very large delta (0xff_ff_ff_ff) which should fail
//! gracefully by returning -1 according to the WebAssembly specification.
//!
//! You can run the example directly by executing:
//! ```shell
//! cargo run --example table-large-grow --release --features backend
//! ```

use wasmer::{imports, Instance, Module, Store};

fn main() -> anyhow::Result<()> {
let module_wat = r#"
(module
(table $t0 0 externref)
(table $t1 10 externref)
(func $init (export "init") (result i32)
(if (i32.ne (table.size $t1) (i32.const 10))
(then (unreachable))
)
;; Store the result instead of dropping it
(table.grow $t0 (ref.null extern) (i32.const 0xff_ff_ff_ff))
)
)
"#;

let mut store = Store::default();
let module = Module::new(&store, &module_wat)?;
let import_object = imports! {};
let instance = Instance::new(&mut store, &module, &import_object)?;

let init = instance.exports.get_function("init")?;
let result = init.call(&mut store, &[])?;

assert_eq!(
result[0],
wasmer::Value::I32(-1),
"table.grow should return -1"
);

Ok(())
}

#[test]
fn test_memory_grow() -> anyhow::Result<()> {
main()
}
28 changes: 23 additions & 5 deletions lib/vm/src/table.rs
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,9 @@ pub struct VMTable {
}

impl VMTable {
// 1,048,576 elements (~1 million)
const DEFAULT_MAX_TABLE_SIZE: u32 = 1 << 20;

/// Create a new linear table instance with specified minimum and maximum number of elements.
///
/// This creates a `Table` with metadata owned by a VM, pointed to by
Expand Down Expand Up @@ -191,18 +194,33 @@ impl VMTable {
pub fn grow(&mut self, delta: u32, init_value: TableElement) -> Option<u32> {
let size = self.size();
let new_len = size.checked_add(delta)?;
if self.maximum.map_or(false, |max| new_len > max) {
return None;

// Apply the table's maximum or the default if unspecified
let max_size = self.maximum.unwrap_or(VMTable::DEFAULT_MAX_TABLE_SIZE);
if new_len > max_size {
return None; // Fail if new size exceeds limit
}

if new_len == size {
debug_assert_eq!(delta, 0);
return Some(size);
}

self.vec
.resize(usize::try_from(new_len).unwrap(), init_value.into());
// Safe: new_len is u32, fits in usize on 64-bit
let new_len_usize = new_len as usize;
let additional = new_len_usize
.checked_sub(self.vec.len())
.expect("new_len should be >= current size");

// Attempt memory reservation; fail on OOM
if self.vec.try_reserve_exact(additional).is_err() {
return None;
}

// Safe to resize after successful reservation
self.vec.resize(new_len_usize, init_value.into());

// update table definition
// Update table definition
unsafe {
let mut td_ptr = self.get_vm_table_definition();
let td = td_ptr.as_mut();
Expand Down
Loading