Skip to content

Commit

Permalink
feat(utils, ci): add lazy pages fuzzer (#4005)
Browse files Browse the repository at this point in the history
Co-authored-by: Sabaun Taraki <[email protected]>
  • Loading branch information
ByteNacked and techraed authored Jul 8, 2024
1 parent 64ace22 commit dec469d
Show file tree
Hide file tree
Showing 29 changed files with 1,583 additions and 42 deletions.
3 changes: 3 additions & 0 deletions .github/workflows/check.yml
Original file line number Diff line number Diff line change
Expand Up @@ -111,3 +111,6 @@ jobs:

- name: "Check fuzzer competence with mutation test"
run: ./scripts/check-fuzzer.sh

- name: "Check lazy pages fuzzer with smoke test"
run: ./scripts/check-lazy-pages-fuzzer.sh
6 changes: 4 additions & 2 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -11,5 +11,7 @@ weight-dumps/
.log
*.meta.txt
.terraform*
utils/runtime-fuzzer/fuzz/corpus/*
utils/runtime-fuzzer/fuzz/coverage/*
utils/**/fuzz/corpus/*
utils/**/fuzz/coverage/*
utils/**/fuzz/artifacts/*
utils/**/fuzz/fuzz/*
29 changes: 29 additions & 0 deletions Cargo.lock

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

4 changes: 3 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,8 @@ members = [
"runtime/*",
"runtime-interface/sandbox",
"utils/*",
"utils/runtime-fuzzer/fuzz"
"utils/runtime-fuzzer/fuzz",
"utils/lazy-pages-fuzzer/fuzz"
]

[workspace.dependencies]
Expand Down Expand Up @@ -272,6 +273,7 @@ wasm-smith = { version = "0.12.21", git = "https://github.com/gear-tech/wasm-too
# Common executors between `sandbox-host` and `calc-stack-height`
sandbox-wasmer = { package = "wasmer", version = "2.2", features = ["singlepass"] }
sandbox-wasmer-types = { package = "wasmer-types", version = "2.2" }
sandbox-wasmi = { package = "wasmi", git = "https://github.com/gear-tech/wasmi", branch = "v0.13.2-sign-ext", features = ["virtual_memory"] }

# Substrate deps
frame-benchmarking = { version = "4.0.0-dev", git = "https://github.com/gear-tech/polkadot-sdk.git", branch = "gear-v1.3.0", default-features = false }
Expand Down
2 changes: 1 addition & 1 deletion lazy-pages/src/common.rs
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ use std::{fmt, mem, num::NonZeroU32};

// TODO: investigate error allocations #2441
#[derive(Debug, derive_more::Display, derive_more::From)]
pub(crate) enum Error {
pub enum Error {
#[display(fmt = "Accessed memory interval is out of wasm memory")]
OutOfWasmMemoryAccess,
#[display(fmt = "Signals cannot come from WASM program virtual stack memory")]
Expand Down
7 changes: 4 additions & 3 deletions lazy-pages/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -53,14 +53,15 @@ use crate::{
WasmSizeNo, SIZES_AMOUNT,
},
};
pub use common::LazyPagesVersion;
pub use common::{Error as LazyPagesError, LazyPagesVersion};
use common::{LazyPagesExecutionContext, LazyPagesRuntimeContext};
use gear_lazy_pages_common::{GlobalsAccessConfig, LazyPagesInitContext, Status};
pub use host_func::pre_process_memory_accesses;
use mprotect::MprotectError;
use numerated::iterators::IntervalIterator;
use pages::GearPage;
use signal::{DefaultUserSignalHandler, UserSignalHandler};
use signal::DefaultUserSignalHandler;
pub use signal::{ExceptionInfo, UserSignalHandler};
use std::{cell::RefCell, convert::TryInto, num::NonZeroU32};

/// Initialize lazy-pages once for process.
Expand Down Expand Up @@ -377,7 +378,7 @@ pub(crate) fn reset_init_flag() {
}

/// Initialize lazy-pages for current thread.
fn init_with_handler<H: UserSignalHandler, S: LazyPagesStorage + 'static>(
pub fn init_with_handler<H: UserSignalHandler, S: LazyPagesStorage + 'static>(
_version: LazyPagesVersion,
ctx: LazyPagesInitContext,
pages_storage: S,
Expand Down
4 changes: 2 additions & 2 deletions lazy-pages/src/signal.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ use crate::{
use gear_lazy_pages_common::Status;
use std::convert::TryFrom;

pub(crate) trait UserSignalHandler {
pub trait UserSignalHandler {
/// # Safety
///
/// It's expected handler calls syscalls to protect memory
Expand All @@ -44,7 +44,7 @@ impl UserSignalHandler for DefaultUserSignalHandler {
}

#[derive(Debug)]
pub(crate) struct ExceptionInfo {
pub struct ExceptionInfo {
/// Address where fault is occurred
pub fault_addr: *const (),
pub is_write: Option<bool>,
Expand Down
2 changes: 1 addition & 1 deletion sandbox/host/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ thiserror.workspace = true
log = { workspace = true, features = ["std"] }
sandbox-wasmer.workspace = true
sandbox-wasmer-types.workspace = true
wasmi = { git = "https://github.com/gear-tech/wasmi", branch = "v0.13.2-sign-ext", features = ["virtual_memory"] }
sandbox-wasmi = { workspace = true, features = ["virtual_memory"] }
sp-allocator = { workspace = true, features = ["std"] }
sp-wasm-interface-common = { workspace = true, features = ["std"] }
gear-sandbox-env = { workspace = true, features = ["std"] }
Expand Down
6 changes: 2 additions & 4 deletions sandbox/host/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,6 @@

//! Rust executor possible errors.

use wasmi;

/// Result type alias.
pub type Result<T> = std::result::Result<T, Error>;

Expand All @@ -28,7 +26,7 @@ pub type Result<T> = std::result::Result<T, Error>;
#[allow(missing_docs)]
pub enum Error {
#[error(transparent)]
Wasmi(#[from] wasmi::Error),
Wasmi(#[from] sandbox_wasmi::Error),

#[error("Sandbox error: {0}")]
Sandbox(String),
Expand Down Expand Up @@ -109,7 +107,7 @@ pub enum Error {
AbortedDueToTrap(MessageWithBacktrace),
}

impl wasmi::HostError for Error {}
impl sandbox_wasmi::HostError for Error {}

impl From<&'static str> for Error {
fn from(err: &'static str) -> Error {
Expand Down
2 changes: 1 addition & 1 deletion sandbox/host/src/sandbox.rs
Original file line number Diff line number Diff line change
Expand Up @@ -176,7 +176,7 @@ pub struct GuestExternals<'a> {
/// Module instance in terms of selected backend
enum BackendInstance {
/// Wasmi module instance
Wasmi(wasmi::ModuleRef),
Wasmi(sandbox_wasmi::ModuleRef),

/// Wasmer module instance
Wasmer(sandbox_wasmer::Instance),
Expand Down
54 changes: 30 additions & 24 deletions sandbox/host/src/sandbox/wasmi_backend.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,11 +22,11 @@ use std::fmt;

use codec::{Decode, Encode};
use gear_sandbox_env::HostError;
use sp_wasm_interface_common::{util, Pointer, ReturnValue, Value, WordSize};
use wasmi::{
use sandbox_wasmi::{
memory_units::Pages, ImportResolver, MemoryInstance, Module, ModuleInstance, RuntimeArgs,
RuntimeValue, Trap, TrapCode,
};
use sp_wasm_interface_common::{util, Pointer, ReturnValue, Value, WordSize};

use crate::{
error::{self, Error},
Expand All @@ -48,7 +48,7 @@ impl fmt::Display for CustomHostError {
}
}

impl wasmi::HostError for CustomHostError {}
impl sandbox_wasmi::HostError for CustomHostError {}

/// Construct trap error from specified message
fn trap(msg: &'static str) -> Trap {
Expand All @@ -60,32 +60,38 @@ impl ImportResolver for Imports {
&self,
module_name: &str,
field_name: &str,
signature: &wasmi::Signature,
) -> std::result::Result<wasmi::FuncRef, wasmi::Error> {
signature: &sandbox_wasmi::Signature,
) -> std::result::Result<sandbox_wasmi::FuncRef, sandbox_wasmi::Error> {
let idx = self.func_by_name(module_name, field_name).ok_or_else(|| {
wasmi::Error::Instantiation(format!("Export {}:{} not found", module_name, field_name))
sandbox_wasmi::Error::Instantiation(format!(
"Export {}:{} not found",
module_name, field_name
))
})?;

Ok(wasmi::FuncInstance::alloc_host(signature.clone(), idx.0))
Ok(sandbox_wasmi::FuncInstance::alloc_host(
signature.clone(),
idx.0,
))
}

fn resolve_memory(
&self,
module_name: &str,
field_name: &str,
_memory_type: &wasmi::MemoryDescriptor,
) -> std::result::Result<wasmi::MemoryRef, wasmi::Error> {
_memory_type: &sandbox_wasmi::MemoryDescriptor,
) -> std::result::Result<sandbox_wasmi::MemoryRef, sandbox_wasmi::Error> {
let mem = self
.memory_by_name(module_name, field_name)
.ok_or_else(|| {
wasmi::Error::Instantiation(format!(
sandbox_wasmi::Error::Instantiation(format!(
"Export {}:{} not found",
module_name, field_name
))
})?;

let wrapper = mem.as_wasmi().ok_or_else(|| {
wasmi::Error::Instantiation(format!(
sandbox_wasmi::Error::Instantiation(format!(
"Unsupported non-wasmi export {}:{}",
module_name, field_name
))
Expand All @@ -103,9 +109,9 @@ impl ImportResolver for Imports {
&self,
module_name: &str,
field_name: &str,
_global_type: &wasmi::GlobalDescriptor,
) -> std::result::Result<wasmi::GlobalRef, wasmi::Error> {
Err(wasmi::Error::Instantiation(format!(
_global_type: &sandbox_wasmi::GlobalDescriptor,
) -> std::result::Result<sandbox_wasmi::GlobalRef, sandbox_wasmi::Error> {
Err(sandbox_wasmi::Error::Instantiation(format!(
"Export {}:{} not found",
module_name, field_name
)))
Expand All @@ -115,9 +121,9 @@ impl ImportResolver for Imports {
&self,
module_name: &str,
field_name: &str,
_table_type: &wasmi::TableDescriptor,
) -> std::result::Result<wasmi::TableRef, wasmi::Error> {
Err(wasmi::Error::Instantiation(format!(
_table_type: &sandbox_wasmi::TableDescriptor,
) -> std::result::Result<sandbox_wasmi::TableRef, sandbox_wasmi::Error> {
Err(sandbox_wasmi::Error::Instantiation(format!(
"Export {}:{} not found",
module_name, field_name
)))
Expand All @@ -138,11 +144,11 @@ pub fn new_memory(initial: u32, maximum: Option<u32>) -> crate::error::Result<Me
///
/// This wrapper limits the scope where the slice can be taken to
#[derive(Debug, Clone)]
pub struct MemoryWrapper(wasmi::MemoryRef);
pub struct MemoryWrapper(sandbox_wasmi::MemoryRef);

impl MemoryWrapper {
/// Take ownership of the memory region and return a wrapper object
fn new(memory: wasmi::MemoryRef) -> Self {
fn new(memory: sandbox_wasmi::MemoryRef) -> Self {
Self(memory)
}
}
Expand Down Expand Up @@ -198,7 +204,7 @@ impl MemoryTransfer for MemoryWrapper {
}
}

impl<'a> wasmi::Externals for GuestExternals<'a> {
impl<'a> sandbox_wasmi::Externals for GuestExternals<'a> {
fn invoke_index(
&mut self,
index: usize,
Expand Down Expand Up @@ -339,7 +345,7 @@ pub fn instantiate(
/// Invoke a function within a sandboxed module
pub fn invoke(
instance: &SandboxInstance,
module: &wasmi::ModuleRef,
module: &sandbox_wasmi::ModuleRef,
export_name: &str,
args: &[Value],
sandbox_context: &mut dyn SandboxContext,
Expand All @@ -352,7 +358,7 @@ pub fn invoke(
.invoke_export(export_name, &args, guest_externals)
.map(|result| result.map(Into::into))
.map_err(|error| {
if matches!(error, wasmi::Error::Trap(Trap::Code(TrapCode::StackOverflow))) {
if matches!(error, sandbox_wasmi::Error::Trap(Trap::Code(TrapCode::StackOverflow))) {
// Panic stops process queue execution in that case.
// This allows to avoid error lead to consensus failures, that must be handled
// in node binaries forever. If this panic occur, then we must increase stack memory size,
Expand All @@ -367,15 +373,15 @@ pub fn invoke(
}

/// Get global value by name
pub fn get_global(instance: &wasmi::ModuleRef, name: &str) -> Option<Value> {
pub fn get_global(instance: &sandbox_wasmi::ModuleRef, name: &str) -> Option<Value> {
Some(Into::into(
instance.export_by_name(name)?.as_global()?.get(),
))
}

/// Set global value by name
pub fn set_global(
instance: &wasmi::ModuleRef,
instance: &sandbox_wasmi::ModuleRef,
name: &str,
value: Value,
) -> std::result::Result<Option<()>, error::Error> {
Expand Down
1 change: 1 addition & 0 deletions scripts/check-fuzzer.sh
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ SCRIPTS="$(cd "$(dirname "$SELF")"/ && pwd)"
. "$SCRIPTS"/fuzzer_consts.sh

main() {
echo " >> Checking runtime fuzzer"
echo " >> Getting random bytes from /dev/urandom"
# Fuzzer expects a minimal input size of 350 KiB. Without providing a corpus of the same or larger
# size fuzzer will stuck for a long time with trying to test the target using 0..100 bytes.
Expand Down
48 changes: 48 additions & 0 deletions scripts/check-lazy-pages-fuzzer.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
#!/usr/bin/env sh

SELF="$0"
SCRIPTS="$(cd "$(dirname "$SELF")"/ && pwd)"

. "$SCRIPTS"/fuzzer_consts.sh

RUN_DURATION_SECS=10
PROCESS_NAME="lazy-pages-fuzzer-fuzz"
OUTPUT_FILE="lazy_pages_fuzz_run"

main() {
echo " >> Checking lazy pages fuzzer"
echo " >> Getting random bytes from /dev/urandom"
# Fuzzer expects a minimal input size of 350 KiB. Without providing a corpus of the same or larger
# size fuzzer will stuck for a long time with trying to test the target using 0..100 bytes.
mkdir -p utils/lazy-pages-fuzzer/fuzz/corpus/main
dd if=/dev/urandom of=utils/lazy-pages-fuzzer/fuzz/corpus/main/check-fuzzer-bytes bs=1 count="$INITIAL_INPUT_SIZE"

# Remove lazy pages fuzzer run file
rm -f $OUTPUT_FILE

# Build lazy pages fuzzer
LAZY_PAGES_FUZZER_ONLY_BUILD=1 ./scripts/gear.sh test lazy-pages-fuzz

echo " >> Running lazy pages fuzzer for ${RUN_DURATION_SECS} seconds"

# Run lazy pages fuzzer for a few seconds
( RUST_LOG="error,lazy_pages_fuzzer::lazy_pages=trace" RUST_BACKTRACE=1 ./scripts/gear.sh test lazy-pages-fuzz "" > $OUTPUT_FILE 2>&1 ) & \
sleep ${RUN_DURATION_SECS} ; \
kill -s KILL $(pidof $PROCESS_NAME) 2> /dev/null ; \
echo " >> Lazy pages fuzzer run completed" ;

# Trim output after SIGKILL backtrace
OUTPUT=$(sed '/SIGKILL/,$d' $OUTPUT_FILE)

if echo $OUTPUT | grep -q 'SIG: Unprotect WASM memory at address' && \
! echo $OUTPUT | grep -iq "ERROR"
then
echo "Success"
exit 0
else
echo "Failed"
exit 1
fi
}

main
4 changes: 4 additions & 0 deletions scripts/gear.sh
Original file line number Diff line number Diff line change
Expand Up @@ -302,6 +302,10 @@ case "$COMMAND" in
header "Running fuzzer for runtime panic checks"
run_fuzzer "$ROOT_DIR" "$1" "$2"; ;;

lazy-pages-fuzz)
header "Running lazy pages fuzzer smoke test"
run_lazy_pages_fuzzer "$ROOT_DIR" "$1" "$2"; ;;

fuzzer-tests)
header "Running runtime-fuzzer crate tests"
run_fuzzer_tests ;;
Expand Down
Loading

0 comments on commit dec469d

Please sign in to comment.