diff --git a/assets/files/plugin-oob.wasm b/assets/files/plugin-oob.wasm new file mode 100755 index 000000000..6afb74362 Binary files /dev/null and b/assets/files/plugin-oob.wasm differ diff --git a/crates/typst/src/eval/plugin.rs b/crates/typst/src/eval/plugin.rs index 11576080e..ee64ec5fd 100644 --- a/crates/typst/src/eval/plugin.rs +++ b/crates/typst/src/eval/plugin.rs @@ -120,11 +120,19 @@ struct Repr { /// Owns all data associated with the WebAssembly module. type Store = wasmi::Store; +/// If there was an error reading/writing memory, keep the offset + length to +/// display an error message. +struct MemoryError { + offset: u32, + length: u32, + write: bool, +} /// The persistent store data used for communication between store and host. #[derive(Default)] struct StoreData { args: Vec, output: Vec, + memory_error: Option, } #[scope] @@ -245,6 +253,14 @@ impl Plugin { let mut code = wasmi::Value::I32(-1); func.call(store.as_context_mut(), &lengths, std::slice::from_mut(&mut code)) .map_err(|err| eco_format!("plugin panicked: {err}"))?; + if let Some(MemoryError { offset, length, write }) = + store.data_mut().memory_error.take() + { + return Err(eco_format!( + "plugin tried to {kind} out of bounds: pointer {offset:#x} is out of bounds for {kind} of length {length}", + kind = if write { "write" } else { "read" } + )); + } // Extract the returned data. let output = std::mem::take(&mut store.data_mut().output); @@ -294,7 +310,14 @@ fn wasm_minimal_protocol_write_args_to_buffer(mut caller: Caller, ptr let arguments = std::mem::take(&mut caller.data_mut().args); let mut offset = ptr as usize; for arg in arguments { - memory.write(&mut caller, offset, arg.as_slice()).unwrap(); + if memory.write(&mut caller, offset, arg.as_slice()).is_err() { + caller.data_mut().memory_error = Some(MemoryError { + offset: offset as u32, + length: arg.len() as u32, + write: true, + }); + return; + } offset += arg.len(); } } @@ -308,6 +331,10 @@ fn wasm_minimal_protocol_send_result_to_host( let memory = caller.get_export("memory").unwrap().into_memory().unwrap(); let mut buffer = std::mem::take(&mut caller.data_mut().output); buffer.resize(len as usize, 0); - memory.read(&caller, ptr as _, &mut buffer).unwrap(); + if memory.read(&caller, ptr as _, &mut buffer).is_err() { + caller.data_mut().memory_error = + Some(MemoryError { offset: ptr, length: len, write: false }); + return; + } caller.data_mut().output = buffer; } diff --git a/tests/typ/compiler/plugin-oob.typ b/tests/typ/compiler/plugin-oob.typ new file mode 100644 index 000000000..4bc162124 --- /dev/null +++ b/tests/typ/compiler/plugin-oob.typ @@ -0,0 +1,14 @@ +// Test Out Of Bound read/write in WebAssembly plugins communication. +// Ref: false + +--- +#let p = plugin("/files/plugin-oob.wasm") + +// Error: 2-14 plugin tried to read out of bounds: pointer 0x40000000 is out of bounds for read of length 1 +#p.read_oob() + +--- +#let p = plugin("/files/plugin-oob.wasm") + +// Error: 2-27 plugin tried to write out of bounds: pointer 0x40000000 is out of bounds for write of length 3 +#p.write_oob(bytes("xyz"))