Skip to content

Commit

Permalink
Refactor wasm validation
Browse files Browse the repository at this point in the history
  • Loading branch information
chipshort committed Jul 20, 2023
1 parent 08f77c8 commit 093fba1
Show file tree
Hide file tree
Showing 2 changed files with 55 additions and 30 deletions.
16 changes: 5 additions & 11 deletions packages/vm/src/compatibility.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,13 @@ use wasmer::wasmparser::Import;
use wasmer::wasmparser::ImportSectionEntryType;
use wasmer::wasmparser::ImportSectionReader;
use wasmer::wasmparser::MemorySectionReader;
use wasmer::wasmparser::Parser;
use wasmer::wasmparser::Payload;
use wasmer::wasmparser::TableSectionReader;
use wasmer::wasmparser::Validator;

use crate::capabilities::required_capabilities_from_module;
use crate::errors::{VmError, VmResult};
use crate::limited::LimitedDisplay;
use crate::static_analysis::validate_wasm;
use crate::static_analysis::ExportInfo;

/// Lists all imports we provide upon instantiating the instance in Instance::from_module()
Expand Down Expand Up @@ -81,15 +80,8 @@ const MAX_IMPORTS: u32 = 100;

/// Checks if the data is valid wasm and compatibility with the CosmWasm API (imports and exports)
pub fn check_wasm(wasm_code: &[u8], available_capabilities: &HashSet<String>) -> VmResult<()> {
let parsed = Parser::new(0).parse_all(wasm_code);
let mut validator = Validator::new();
// TODO: some of the validator checks are duplicated in our checks below

let mut memory_section: Option<MemorySectionReader<'_>> = None;
for payload in parsed {
let payload = payload?;
validator.payload(&payload)?;

validate_wasm(wasm_code, |payload| {
match payload {
Payload::TableSection(t) => check_wasm_tables(t)?,
Payload::MemorySection(m) => memory_section = Some(m),
Expand All @@ -102,7 +94,9 @@ pub fn check_wasm(wasm_code: &[u8], available_capabilities: &HashSet<String>) ->
Payload::ImportSection(i) => check_wasm_imports(i, SUPPORTED_IMPORTS)?,
_ => {}
}
}
Ok(())
})?;
// we want to fail if there is no memory section, so this check is delayed until the end
check_wasm_memories(memory_section)?;

Ok(())
Expand Down
69 changes: 50 additions & 19 deletions packages/vm/src/static_analysis.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
use std::collections::HashSet;

use wasmer::wasmparser::{Export, ExportSectionReader, ExternalKind};
use wasmer::wasmparser::{
Export, ExportSectionReader, ExternalKind, Parser, Payload, ValidPayload, Validator,
WasmFeatures,
};

use crate::errors::VmResult;

Expand All @@ -13,29 +16,57 @@ pub const REQUIRED_IBC_EXPORTS: &[&str] = &[
"ibc_packet_timeout",
];

/// Validates the given wasm code and calls the callback for each payload.
/// "Validates" in this case refers to general WebAssembly validation, not specific to CosmWasm.
pub fn validate_wasm<'a>(
wasm_code: &'a [u8],
mut handle_payload: impl FnMut(Payload<'a>) -> VmResult<()>,
) -> VmResult<()> {
let mut validator = Validator::new();
validator.wasm_features(WasmFeatures {
mutable_global: false,
saturating_float_to_int: false,
sign_extension: true,
reference_types: true,
multi_value: false,
bulk_memory: false,
module_linking: false,
simd: false,
relaxed_simd: false,
threads: false,
tail_call: false,
deterministic_only: true,
multi_memory: false,
exceptions: false,
memory64: false,
extended_const: false,
});

for p in Parser::new(0).parse_all(wasm_code) {
let p = p?;
// validate the payload
if let ValidPayload::Func(mut fv, body) = validator.payload(&p)? {
// also validate function bodies
fv.validate(&body)?;
}
// tell caller about the payload
handle_payload(p)?;
}

Ok(())
}

/// A small helper macro to validate the wasm module and extract a reader for a specific section.
macro_rules! extract_reader {
($wasm_code: expr, $payload: ident, $t: ty) => {{
fn extract(wasm_code: &[u8]) -> crate::VmResult<Option<$t>> {
use wasmer::wasmparser::{Parser, Payload, ValidPayload, Validator};

let mut validator = Validator::new();
let parser = Parser::new(0);

fn extract(wasm_code: &[u8]) -> $crate::VmResult<Option<$t>> {
let mut value = None;
for p in parser.parse_all(wasm_code) {
let p = p?;
// validate the payload
if let ValidPayload::Func(mut fv, body) = validator.payload(&p)? {
// also validate function bodies
fv.validate(&body)?;
}
if let Payload::$payload(e) = p {
// do not return immediately, as we want to validate the entire module
value = Some(e);
$crate::static_analysis::validate_wasm(wasm_code, |p| {
if let Payload::$payload(p) = p {
value = Some(p);
}
}

Ok(())
})?;
Ok(value)
}

Expand Down

0 comments on commit 093fba1

Please sign in to comment.