// Copyright 2018-2020 Parity Technologies (UK) Ltd.
// This file is part of Substrate.
// Substrate is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Substrate is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Substrate. If not, see .
//! This module takes care of loading, checking and preprocessing of a
//! wasm module before execution. It also extracts some essential information
//! from a module.
use crate::{
wasm::{env_def::ImportSatisfyCheck, PrefabWasmModule},
Schedule,
};
use parity_wasm::elements::{self, External, Internal, MemoryType, Type, ValueType};
use pwasm_utils::{self, rules};
use sp_runtime::traits::SaturatedConversion;
use sp_std::prelude::*;
struct ContractModule<'a> {
/// A deserialized module. The module is valid (this is Guaranteed by `new` method).
module: elements::Module,
schedule: &'a Schedule,
}
impl<'a> ContractModule<'a> {
/// Creates a new instance of `ContractModule`.
///
/// Returns `Err` if the `original_code` couldn't be decoded or
/// if it contains an invalid module.
fn new(original_code: &[u8], schedule: &'a Schedule) -> Result {
use wasmi_validation::{validate_module, PlainValidator};
let module =
elements::deserialize_buffer(original_code).map_err(|_| "Can't decode wasm code")?;
// Make sure that the module is valid.
validate_module::(&module).map_err(|_| "Module is not valid")?;
// Return a `ContractModule` instance with
// __valid__ module.
Ok(ContractModule { module, schedule })
}
/// Ensures that module doesn't declare internal memories.
///
/// In this runtime we only allow wasm module to import memory from the environment.
/// Memory section contains declarations of internal linear memories, so if we find one
/// we reject such a module.
fn ensure_no_internal_memory(&self) -> Result<(), &'static str> {
if self
.module
.memory_section()
.map_or(false, |ms| ms.entries().len() > 0)
{
return Err("module declares internal memory");
}
Ok(())
}
/// Ensures that tables declared in the module are not too big.
fn ensure_table_size_limit(&self, limit: u32) -> Result<(), &'static str> {
if let Some(table_section) = self.module.table_section() {
// In Wasm MVP spec, there may be at most one table declared. Double check this
// explicitly just in case the Wasm version changes.
if table_section.entries().len() > 1 {
return Err("multiple tables declared");
}
if let Some(table_type) = table_section.entries().first() {
// Check the table's initial size as there is no instruction or environment function
// capable of growing the table.
if table_type.limits().initial() > limit {
return Err("table exceeds maximum size allowed");
}
}
}
Ok(())
}
/// Ensures that no floating point types are in use.
fn ensure_no_floating_types(&self) -> Result<(), &'static str> {
if let Some(global_section) = self.module.global_section() {
for global in global_section.entries() {
match global.global_type().content_type() {
ValueType::F32 | ValueType::F64 => {
return Err("use of floating point type in globals is forbidden")
}
_ => {}
}
}
}
if let Some(code_section) = self.module.code_section() {
for func_body in code_section.bodies() {
for local in func_body.locals() {
match local.value_type() {
ValueType::F32 | ValueType::F64 => {
return Err("use of floating point type in locals is forbidden")
}
_ => {}
}
}
}
}
if let Some(type_section) = self.module.type_section() {
for wasm_type in type_section.types() {
match wasm_type {
Type::Function(func_type) => {
let return_type = func_type.return_type();
for value_type in func_type.params().iter().chain(return_type.iter()) {
match value_type {
ValueType::F32 | ValueType::F64 => {
return Err(
"use of floating point type in function types is forbidden",
)
}
_ => {}
}
}
}
}
}
}
Ok(())
}
fn inject_gas_metering(self) -> Result {
let gas_rules = rules::Set::new(
self.schedule.regular_op_cost.clone().saturated_into(),
Default::default(),
)
.with_grow_cost(self.schedule.grow_mem_cost.clone().saturated_into())
.with_forbidden_floats();
let contract_module = pwasm_utils::inject_gas_counter(self.module, &gas_rules)
.map_err(|_| "gas instrumentation failed")?;
Ok(ContractModule {
module: contract_module,
schedule: self.schedule,
})
}
fn inject_stack_height_metering(self) -> Result {
let contract_module =
pwasm_utils::stack_height::inject_limiter(self.module, self.schedule.max_stack_height)
.map_err(|_| "stack height instrumentation failed")?;
Ok(ContractModule {
module: contract_module,
schedule: self.schedule,
})
}
/// Check that the module has required exported functions. For now
/// these are just entrypoints:
///
/// - 'call'
/// - 'deploy'
///
/// Any other exports are not allowed.
fn scan_exports(&self) -> Result<(), &'static str> {
let mut deploy_found = false;
let mut call_found = false;
let module = &self.module;
let types = module.type_section().map(|ts| ts.types()).unwrap_or(&[]);
let export_entries = module
.export_section()
.map(|is| is.entries())
.unwrap_or(&[]);
let func_entries = module
.function_section()
.map(|fs| fs.entries())
.unwrap_or(&[]);
// Function index space consists of imported function following by
// declared functions. Calculate the total number of imported functions so
// we can use it to convert indexes from function space to declared function space.
let fn_space_offset = module
.import_section()
.map(|is| is.entries())
.unwrap_or(&[])
.iter()
.filter(|entry| match *entry.external() {
External::Function(_) => true,
_ => false,
})
.count();
for export in export_entries {
match export.field() {
"call" => call_found = true,
"deploy" => deploy_found = true,
_ => return Err("unknown export: expecting only deploy and call functions"),
}
// Then check the export kind. "call" and "deploy" are
// functions.
let fn_idx = match export.internal() {
Internal::Function(ref fn_idx) => *fn_idx,
_ => return Err("expected a function"),
};
// convert index from function index space to declared index space.
let fn_idx = match fn_idx.checked_sub(fn_space_offset as u32) {
Some(fn_idx) => fn_idx,
None => {
// Underflow here means fn_idx points to imported function which we don't allow!
return Err("entry point points to an imported function");
}
};
// Then check the signature.
// Both "call" and "deploy" has a [] -> [] or [] -> [i32] function type.
//
// The [] -> [] signature predates the [] -> [i32] signature and is supported for
// backwards compatibility. This will likely be removed once ink! is updated to
// generate modules with the new function signatures.
let func_ty_idx = func_entries
.get(fn_idx as usize)
.ok_or_else(|| "export refers to non-existent function")?
.type_ref();
let Type::Function(ref func_ty) = types
.get(func_ty_idx as usize)
.ok_or_else(|| "function has a non-existent type")?;
if !func_ty.params().is_empty()
|| !(func_ty.return_type().is_none()
|| func_ty.return_type() == Some(ValueType::I32))
{
return Err("entry point has wrong signature");
}
}
if !deploy_found {
return Err("deploy function isn't exported");
}
if !call_found {
return Err("call function isn't exported");
}
Ok(())
}
/// Scan an import section if any.
///
/// This accomplishes two tasks:
///
/// - checks any imported function against defined host functions set, incl.
/// their signatures.
/// - if there is a memory import, returns it's descriptor
fn scan_imports(&self) -> Result