From bed9ae4ce3ca7e4b3c88883049e047a5244b8e73 Mon Sep 17 00:00:00 2001 From: Robin Freyler Date: Fri, 8 Dec 2023 15:24:29 +0100 Subject: [PATCH] Add `Module::validate` API from Wasmtime (#840) * remove unneeded wasmparser::validate checks in some test cases * no longer panic in parser when encountering component model definitions Instead return a proper wasmi error indicating usage of unsupported Wasm features. * add Module::validate API * remove unnecessary temporary buffer We do not need this buffer until we actually plan to perform validation in parllel. --- .../engine/translator/tests/regression/mod.rs | 2 - crates/wasmi/src/module/error.rs | 24 ++++++++-- crates/wasmi/src/module/mod.rs | 47 +++++++++++++++++++ crates/wasmi/src/module/parser.rs | 8 ++-- 4 files changed, 71 insertions(+), 10 deletions(-) diff --git a/crates/wasmi/src/engine/translator/tests/regression/mod.rs b/crates/wasmi/src/engine/translator/tests/regression/mod.rs index 974ad79bcb..c4ddb4ace6 100644 --- a/crates/wasmi/src/engine/translator/tests/regression/mod.rs +++ b/crates/wasmi/src/engine/translator/tests/regression/mod.rs @@ -97,7 +97,6 @@ fn fuzz_regression_4() { fn fuzz_regression_5() { let wat = include_str!("fuzz_5.wat"); let wasm = wat2wasm(wat); - _ = wasmparser::validate(&wasm[..]).unwrap(); TranslationTest::new(wasm) .expect_func_instrs([ Instruction::call_internal( @@ -124,7 +123,6 @@ fn fuzz_regression_5() { fn fuzz_regression_6() { let wat = include_str!("fuzz_6.wat"); let wasm = wat2wasm(wat); - _ = wasmparser::validate(&wasm[..]).unwrap(); TranslationTest::new(wasm) .expect_func_instrs([ Instruction::branch_i32_eq_imm(Register::from_i16(0), 0, BranchOffset16::from(4)), diff --git a/crates/wasmi/src/module/error.rs b/crates/wasmi/src/module/error.rs index ba81c0870b..3f011b42b3 100644 --- a/crates/wasmi/src/module/error.rs +++ b/crates/wasmi/src/module/error.rs @@ -15,18 +15,36 @@ pub enum ModuleError { Parser(ParserError), /// Encountered when there is a Wasm to `wasmi` translation error. Translation(TranslationError), + /// Encountered unsupported Wasm feature usage. + Unsupported(UnsupportedFeature), +} + +/// An unsupported Wasm feature. +#[derive(Debug)] +pub enum UnsupportedFeature { + /// The Wasm component model. + ComponentModel, } impl Display for ModuleError { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { - ModuleError::Read(error) => Display::fmt(error, f), - ModuleError::Parser(error) => Display::fmt(error, f), - ModuleError::Translation(error) => Display::fmt(error, f), + Self::Read(error) => Display::fmt(error, f), + Self::Parser(error) => Display::fmt(error, f), + Self::Translation(error) => Display::fmt(error, f), + Self::Unsupported(feature) => { + write!(f, "encountered unsupported Wasm feature: {feature:?}") + } } } } +impl From for ModuleError { + fn from(feature: UnsupportedFeature) -> Self { + Self::Unsupported(feature) + } +} + impl From for ModuleError { fn from(error: ReadError) -> Self { Self::Read(error) diff --git a/crates/wasmi/src/module/mod.rs b/crates/wasmi/src/module/mod.rs index ccbbd796a4..5fb93adee4 100644 --- a/crates/wasmi/src/module/mod.rs +++ b/crates/wasmi/src/module/mod.rs @@ -38,6 +38,7 @@ pub(crate) use self::{ }; use crate::{ engine::{CompiledFunc, DedupFuncType}, + module::error::UnsupportedFeature, Engine, Error, ExternType, @@ -48,6 +49,7 @@ use crate::{ }; use alloc::{boxed::Box, collections::BTreeMap, sync::Arc}; use core::{iter, slice::Iter as SliceIter}; +use wasmparser::{FuncValidatorAllocations, Parser, ValidPayload, Validator}; /// A parsed and validated WebAssembly module. #[derive(Debug)] @@ -187,6 +189,51 @@ impl Module { &self.engine } + /// Validates `wasm` as a WebAssembly binary given the configuration (via [`Config`]) in `engine`. + /// + /// This function performs Wasm validation of the binary input WebAssembly module and + /// returns either `Ok`` or `Err`` depending on the results of the validation. + /// The [`Config`] of the `engine` is used for Wasm validation which indicates which WebAssembly + /// features are valid and invalid for the validation. + /// + /// # Note + /// + /// - The input `wasm` must be in binary form, the text format is not accepted by this function. + /// - This will only validate the `wasm` but not try to translate it. Therefore `Module::new` + /// might still fail if translation of the Wasm binary input fails to translate via the `wasmi` + /// [`Engine`]. + /// - Validation automatically happens as part of [`Module::new`]. + /// + /// # Errors + /// + /// If Wasm validation for `wasm` fails for the given [`Config`] provided via `engine`. + /// + /// [`Config`]: crate::Config + pub fn validate(&self, engine: &Engine, wasm: &[u8]) -> Result<(), Error> { + let mut validator = Validator::new_with_features(engine.config().wasm_features()); + for payload in Parser::new(0).parse_all(wasm) { + let payload = payload.map_err(ModuleError::from)?; + if let ValidPayload::Func(func_to_validate, func_body) = + validator.payload(&payload).map_err(ModuleError::from)? + { + func_to_validate + .into_validator(FuncValidatorAllocations::default()) + .validate(&func_body) + .map_err(ModuleError::from)?; + } + if let wasmparser::Payload::Version { + encoding: wasmparser::Encoding::Component, + .. + } = &payload + { + return Err(Error::from(ModuleError::from( + UnsupportedFeature::ComponentModel, + ))); + } + } + Ok(()) + } + /// Creates a new [`Module`] from the [`ModuleBuilder`]. fn from_builder(builder: ModuleBuilder) -> Self { Self { diff --git a/crates/wasmi/src/module/parser.rs b/crates/wasmi/src/module/parser.rs index 6b275f6b61..1273870d47 100644 --- a/crates/wasmi/src/module/parser.rs +++ b/crates/wasmi/src/module/parser.rs @@ -1,5 +1,6 @@ use super::{ compile::{translate, translate_unchecked}, + error::UnsupportedFeature, export::ExternIdx, global::Global, import::{FuncTypeIdx, Import}, @@ -572,12 +573,9 @@ impl<'engine> ModuleParser<'engine> { /// Process the entries for the Wasm component model proposal. fn process_unsupported_component_model( &mut self, - range: Range, + _range: Range, ) -> Result<(), ModuleError> { - panic!( - "wasmi does not support the `component-model` Wasm proposal: bytes[{}..{}]", - range.start, range.end - ) + Err(ModuleError::from(UnsupportedFeature::ComponentModel)) } /// Process an unknown Wasm module section.