Wasm executor should provide stubs for unknown externs (wasmi) (#4550)

Related to #4456
This commit is contained in:
Cecile Tonglet
2020-01-09 11:18:14 +01:00
committed by GitHub
parent 189bd2349c
commit cc573bc106
6 changed files with 193 additions and 25 deletions
+87 -22
View File
@@ -21,7 +21,7 @@ use sc_executor_common::{
sandbox,
allocator,
};
use std::{str, mem};
use std::{str, mem, cell::RefCell};
use wasmi::{
Module, ModuleInstance, MemoryInstance, MemoryRef, TableRef, ImportsBuilder, ModuleRef,
memory_units::Pages, RuntimeValue::{I32, I64, self},
@@ -42,6 +42,8 @@ struct FunctionExecutor<'a> {
memory: MemoryRef,
table: Option<TableRef>,
host_functions: &'a [&'static dyn Function],
allow_missing_imports: bool,
missing_functions: &'a [String],
}
impl<'a> FunctionExecutor<'a> {
@@ -50,6 +52,8 @@ impl<'a> FunctionExecutor<'a> {
heap_base: u32,
t: Option<TableRef>,
host_functions: &'a [&'static dyn Function],
allow_missing_imports: bool,
missing_functions: &'a [String],
) -> Result<Self, Error> {
Ok(FunctionExecutor {
sandbox_store: sandbox::Store::new(),
@@ -57,6 +61,8 @@ impl<'a> FunctionExecutor<'a> {
memory: m,
table: t,
host_functions,
allow_missing_imports,
missing_functions,
})
}
}
@@ -269,14 +275,28 @@ impl<'a> Sandbox for FunctionExecutor<'a> {
}
}
struct Resolver<'a>(&'a[&'static dyn Function]);
struct Resolver<'a> {
host_functions: &'a[&'static dyn Function],
allow_missing_imports: bool,
missing_functions: RefCell<Vec<String>>,
}
impl<'a> Resolver<'a> {
fn new(host_functions: &'a[&'static dyn Function], allow_missing_imports: bool) -> Resolver<'a> {
Resolver {
host_functions,
allow_missing_imports,
missing_functions: RefCell::new(Vec::new()),
}
}
}
impl<'a> wasmi::ModuleImportResolver for Resolver<'a> {
fn resolve_func(&self, name: &str, signature: &wasmi::Signature)
-> std::result::Result<wasmi::FuncRef, wasmi::Error>
{
let signature = sp_wasm_interface::Signature::from(signature);
for (function_index, function) in self.0.iter().enumerate() {
for (function_index, function) in self.host_functions.iter().enumerate() {
if name == function.name() {
if signature == function.signature() {
return Ok(
@@ -295,9 +315,17 @@ impl<'a> wasmi::ModuleImportResolver for Resolver<'a> {
}
}
Err(wasmi::Error::Instantiation(
format!("Export {} not found", name),
))
if self.allow_missing_imports {
trace!(target: "wasm-executor", "Could not find function `{}`, a stub will be provided instead.", name);
let id = self.missing_functions.borrow().len() + self.host_functions.len();
self.missing_functions.borrow_mut().push(name.to_string());
Ok(wasmi::FuncInstance::alloc_host(signature.into(), id))
} else {
Err(wasmi::Error::Instantiation(
format!("Export {} not found", name),
))
}
}
}
@@ -306,16 +334,23 @@ impl<'a> wasmi::Externals for FunctionExecutor<'a> {
-> Result<Option<wasmi::RuntimeValue>, wasmi::Trap>
{
let mut args = args.as_ref().iter().copied().map(Into::into);
let function = self.host_functions.get(index).ok_or_else(||
Error::from(
format!("Could not find host function with index: {}", index),
)
)?;
function.execute(self, &mut args)
.map_err(|msg| Error::FunctionExecution(function.name().to_string(), msg))
.map_err(wasmi::Trap::from)
.map(|v| v.map(Into::into))
if let Some(function) = self.host_functions.get(index) {
function.execute(self, &mut args)
.map_err(|msg| Error::FunctionExecution(function.name().to_string(), msg))
.map_err(wasmi::Trap::from)
.map(|v| v.map(Into::into))
} else if self.allow_missing_imports
&& index >= self.host_functions.len()
&& index < self.host_functions.len() + self.missing_functions.len()
{
Err(Error::from(format!(
"Function `{}` is only a stub. Calling a stub is not allowed.",
self.missing_functions[index - self.host_functions.len()],
)).into())
} else {
Err(Error::from(format!("Could not find host function with index: {}", index)).into())
}
}
}
@@ -351,6 +386,8 @@ fn call_in_wasm_module(
method: &str,
data: &[u8],
host_functions: &[&'static dyn Function],
allow_missing_imports: bool,
missing_functions: &Vec<String>,
) -> Result<Vec<u8>, Error> {
// extract a reference to a linear memory, optional reference to a table
// and then initialize FunctionExecutor.
@@ -360,7 +397,14 @@ fn call_in_wasm_module(
.and_then(|e| e.as_table().cloned());
let heap_base = get_heap_base(module_instance)?;
let mut fec = FunctionExecutor::new(memory.clone(), heap_base, table, host_functions)?;
let mut fec = FunctionExecutor::new(
memory.clone(),
heap_base,
table,
host_functions,
allow_missing_imports,
missing_functions,
)?;
// Write the call data
let offset = fec.allocate_memory(data.len() as u32)?;
@@ -397,8 +441,9 @@ fn instantiate_module(
heap_pages: usize,
module: &Module,
host_functions: &[&'static dyn Function],
) -> Result<ModuleRef, Error> {
let resolver = Resolver(host_functions);
allow_missing_imports: bool,
) -> Result<(ModuleRef, Vec<String>), Error> {
let resolver = Resolver::new(host_functions, allow_missing_imports);
// start module instantiation. Don't run 'start' function yet.
let intermediate_instance = ModuleInstance::new(
module,
@@ -416,7 +461,7 @@ fn instantiate_module(
// Runtime is not allowed to have the `start` function.
Err(Error::RuntimeHasStartFn)
} else {
Ok(intermediate_instance.assert_no_start())
Ok((intermediate_instance.assert_no_start(), resolver.missing_functions.into_inner()))
}
}
@@ -536,6 +581,11 @@ pub struct WasmiRuntime {
state_snapshot: StateSnapshot,
/// The host functions registered for this instance.
host_functions: Vec<&'static dyn Function>,
/// Enable stub generation for functions that are not available in `host_functions`.
/// These stubs will error when the wasm blob tries to call them.
allow_missing_imports: bool,
/// List of missing functions detected during function resolution
missing_functions: Vec<String>,
}
impl WasmRuntime for WasmiRuntime {
@@ -561,7 +611,15 @@ impl WasmRuntime for WasmiRuntime {
error!(target: "wasm-executor", "snapshot restoration failed: {}", e);
e
})?;
call_in_wasm_module(ext, &self.instance, method, data, &self.host_functions)
call_in_wasm_module(
ext,
&self.instance,
method,
data,
&self.host_functions,
self.allow_missing_imports,
&self.missing_functions,
)
}
}
@@ -569,6 +627,7 @@ pub fn create_instance(
code: &[u8],
heap_pages: u64,
host_functions: Vec<&'static dyn Function>,
allow_missing_imports: bool,
) -> Result<WasmiRuntime, WasmError> {
let module = Module::from_buffer(&code).map_err(|_| WasmError::InvalidModule)?;
@@ -579,8 +638,12 @@ pub fn create_instance(
let data_segments = extract_data_segments(&code)?;
// Instantiate this module.
let instance = instantiate_module(heap_pages as usize, &module, &host_functions)
.map_err(|e| WasmError::Instantiation(e.to_string()))?;
let (instance, missing_functions) = instantiate_module(
heap_pages as usize,
&module,
&host_functions,
allow_missing_imports,
).map_err(|e| WasmError::Instantiation(e.to_string()))?;
// Take state snapshot before executing anything.
let state_snapshot = StateSnapshot::take(&instance, data_segments, heap_pages)
@@ -595,6 +658,8 @@ pub fn create_instance(
instance,
state_snapshot,
host_functions,
allow_missing_imports,
missing_functions,
})
}