Skip to content
This repository has been archived by the owner on Nov 15, 2023. It is now read-only.

Fix potential huge allocation as a result of validate_block output #13183

Merged
merged 5 commits into from
Jan 20, 2023
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions client/executor/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ sp-wasm-interface = { version = "7.0.0", path = "../../primitives/wasm-interface

[dev-dependencies]
array-bytes = "4.1"
assert_matches = "1.3.0"
wat = "1.0"
sc-runtime-test = { version = "2.0.0", path = "runtime-test" }
substrate-test-runtime = { version = "2.0.0", path = "../../test-utils/runtime" }
Expand Down
1 change: 1 addition & 0 deletions client/executor/runtime-test/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ targets = ["x86_64-unknown-linux-gnu"]
sp-core = { version = "7.0.0", default-features = false, path = "../../../primitives/core" }
sp-io = { version = "7.0.0", default-features = false, features = ["improved_panic_error_reporting"], path = "../../../primitives/io" }
sp-runtime = { version = "7.0.0", default-features = false, path = "../../../primitives/runtime" }
sp-runtime-interface = { version = "7.0.0", default-features = false, path = "../../../primitives/runtime-interface" }
sp-std = { version = "5.0.0", default-features = false, path = "../../../primitives/std" }

[build-dependencies]
Expand Down
14 changes: 14 additions & 0 deletions client/executor/runtime-test/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,8 @@ use sp_runtime::{
traits::{BlakeTwo256, Hash},
};

use sp_runtime_interface::pack_ptr_and_len;

extern "C" {
#[allow(dead_code)]
fn missing_external();
Expand Down Expand Up @@ -337,3 +339,15 @@ sp_core::wasm_export_functions! {
return 1234;
}
}

// Returns a huge len. It should result in an error, and not an allocation.
#[no_mangle]
pub extern "C" fn test_return_huge_len(_params: *const u8, _len: usize) -> u64 {
pack_ptr_and_len(0, u32::MAX)
}

// Returns an output that overflows the u32 range. It should result in an error.
#[no_mangle]
pub extern "C" fn test_return_overflow(_params: *const u8, _len: usize) -> u64 {
pack_ptr_and_len(u32::MAX, 1)
}
37 changes: 36 additions & 1 deletion client/executor/src/integration_tests/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
mod linux;

use codec::{Decode, Encode};
use sc_executor_common::{error::Error, runtime_blob::RuntimeBlob, wasm_runtime::WasmModule};
use sc_executor_common::{error::{Error, WasmError}, runtime_blob::RuntimeBlob, wasm_runtime::WasmModule};
use sc_runtime_test::wasm_binary_unwrap;
use sp_core::{
blake2_128, blake2_256, ed25519, map,
Expand All @@ -33,6 +33,7 @@ use sp_runtime::traits::BlakeTwo256;
use sp_state_machine::TestExternalities as CoreTestExternalities;
use sp_trie::{LayoutV1 as Layout, TrieConfiguration};
use std::sync::Arc;
use assert_matches::assert_matches;
use tracing_subscriber::layer::SubscriberExt;

use crate::WasmExecutionMethod;
Expand Down Expand Up @@ -781,3 +782,37 @@ fn return_value(wasm_method: WasmExecutionMethod) {
(1234u64).encode()
);
}

test_wasm_execution!(return_huge_len);
fn return_huge_len(wasm_method: WasmExecutionMethod) {
let mut ext = TestExternalities::default();
let mut ext = ext.ext();

match call_in_wasm("test_return_huge_len", &[], wasm_method, &mut ext).unwrap_err() {
Error::Runtime => {
assert_matches!(wasm_method, WasmExecutionMethod::Interpreted);
}
Error::RuntimeConstruction(WasmError::Other(error)) => {
assert_matches!(wasm_method, WasmExecutionMethod::Compiled{..});
assert_eq!(error, "output exceeds bounds of wasm memory");
},
error => panic!("unexpected error: {:?}", error),
}
}

test_wasm_execution!(return_overflow);
fn return_overflow(wasm_method: WasmExecutionMethod) {
let mut ext = TestExternalities::default();
let mut ext = ext.ext();

match call_in_wasm("test_return_overflow", &[], wasm_method, &mut ext).unwrap_err() {
Error::Runtime => {
assert_matches!(wasm_method, WasmExecutionMethod::Interpreted);
}
Error::RuntimeConstruction(WasmError::Other(error)) => {
assert_matches!(wasm_method, WasmExecutionMethod::Compiled{..});
assert_eq!(error, "output exceeds bounds of wasm memory");
},
error => panic!("unexpected error: {:?}", error),
}
}
17 changes: 15 additions & 2 deletions client/executor/wasmtime/src/runtime.rs
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ use sc_executor_common::{
runtime_blob::{
self, DataSegmentsSnapshot, ExposedMutableGlobalsSet, GlobalsSnapshot, RuntimeBlob,
},
util::checked_range,
wasm_runtime::{InvokeMethod, WasmInstance, WasmModule},
};
use sp_runtime_interface::unpack_ptr_and_len;
Expand All @@ -41,7 +42,7 @@ use std::{
Arc,
},
};
use wasmtime::{Engine, Memory, StoreLimits, Table};
use wasmtime::{Engine, Memory, StoreLimits, Table, AsContext};

pub(crate) struct StoreData {
/// The limits we apply to the store. We need to store it here to return a reference to this
Expand Down Expand Up @@ -793,7 +794,19 @@ fn extract_output_data(
output_ptr: u32,
output_len: u32,
) -> Result<Vec<u8>> {
let ctx = instance.store();

// Do a length check before allocating. The returned output should not be bigger than the
// available WASM memory. Otherwise, a malicious parachain can trigger a large allocation,
// potentially causing memory exhaustion.
//
// Get the size of the WASM memory in bytes.
let memory_size = ctx.as_context().data().memory().data_size(ctx);
if let None = checked_range(output_ptr as usize, output_len as usize, memory_size) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
if let None = checked_range(output_ptr as usize, output_len as usize, memory_size) {
if checked_range(output_ptr as usize, output_len as usize, memory_size).is_none() {

return Err(WasmError::Other("output exceeds bounds of wasm memory".into()))?
bkchr marked this conversation as resolved.
Show resolved Hide resolved
}
let mut output = vec![0; output_len as usize];
util::read_memory_into(instance.store(), Pointer::new(output_ptr), &mut output)?;

util::read_memory_into(ctx, Pointer::new(output_ptr), &mut output)?;
Ok(output)
}