Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Proof of concept implementation of code coverage guided fuzzing for scrypto blueprints #1780

Draft
wants to merge 1 commit into
base: develop
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
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
7 changes: 7 additions & 0 deletions .devcontainer/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
FROM mcr.microsoft.com/devcontainers/rust:latest

RUN apt-get update && apt-get install -y cmake make llvm clang binutils-dev libunwind-dev libblocksruntime-dev git

USER vscode

RUN rustup default nightly && rustup target add wasm32-unknown-unknown && rustup component add rust-src
36 changes: 36 additions & 0 deletions .devcontainer/devcontainer.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
{
"name": "Radix",

"build": {
"dockerfile": "Dockerfile"
},

// Features to add to the dev container. More info: https://containers.dev/features.
"features": {
"ghcr.io/devcontainers/features/docker-in-docker:2": {}
},

// Configure tool-specific properties.
"customizations": {
// Configure properties specific to VS Code.
"vscode": {
"settings": {},
"extensions": [
"GitHub.copilot"
]
}
},

// Use 'forwardPorts' to make a list of ports inside the container available locally.
// "forwardPorts": [9000],

// Use 'portsAttributes' to set default properties for specific forwarded ports.
// More info: https://containers.dev/implementors/json_reference/#port-attributes
"portsAttributes": {},

// Use 'postCreateCommand' to run commands after the container is created.
// "postCreateCommand": "pip3 install -r requirements.txt",

// Uncomment to connect as root instead. More info: https://aka.ms/dev-containers-non-root.
"remoteUser": "vscode"
}
3 changes: 3 additions & 0 deletions .gitmodules
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
[submodule "scrypto-wasm-fuzzer/honggfuzz"]
path = scrypto-wasm-fuzzer/honggfuzz
url = https://github.com/google/honggfuzz
5 changes: 3 additions & 2 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ members = [
"scrypto-derive",
"scrypto-test",
"scrypto",
"scrypto-wasm-fuzzer",
]

[workspace.dependencies]
Expand Down Expand Up @@ -84,9 +85,9 @@ crossbeam = { version = "0.8.2" }
ed25519-dalek = { version = "1.0.1", default-features = false, features = ["u64_backend"] }
ethnum = {version = "1.3.2", default-features = false }
fixedstr = { version = "0.2.9" }
hashbrown = { version = "0.13.2" }
hashbrown = { version = "0.14.3" }
hex = { version = "0.4.3", default-features = false }
indexmap = { version = "2.2.5", default-features = false }
indexmap = { version = "2.2.6", default-features = false }
itertools = { version = "0.10.3" }
lazy_static = { version = "1.4.0" }
linreg = { version = "0.2.0" }
Expand Down
9 changes: 0 additions & 9 deletions examples/no-std/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 radix-common/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@ resource_tracker = []
full_math_benches = [ "dep:rug", "dep:ethnum"]

coverage = []
wasm_fuzzing = []

# Ref: https://bheisler.github.io/criterion.rs/book/faq.html#cargo-bench-gives-unrecognized-option-errors-for-valid-command-line-options
[lib]
Expand Down
9 changes: 5 additions & 4 deletions radix-common/src/constants/transaction_execution.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,15 +26,16 @@ pub const MAX_TRACK_SUBSTATE_TOTAL_BYTES: usize = 64 * 1024 * 1024;
pub const MAX_SUBSTATE_KEY_SIZE: usize = 1024;

/// The maximum substate read and write size.
#[cfg(not(feature = "coverage"))]
#[cfg(all(not(feature = "coverage"), not(feature = "wasm_fuzzing")))]
pub const MAX_SUBSTATE_VALUE_SIZE: usize = 2 * 1024 * 1024;
#[cfg(feature = "coverage")]
#[cfg(any(feature = "coverage", feature = "wasm_fuzzing"))]
pub const MAX_SUBSTATE_VALUE_SIZE: usize = 64 * 1024 * 1024;

/// The maximum invoke payload size.
#[cfg(not(feature = "coverage"))]
#[cfg(all(not(feature = "coverage"), not(feature = "wasm_fuzzing")))]
pub const MAX_INVOKE_PAYLOAD_SIZE: usize = 1 * 1024 * 1024;
#[cfg(feature = "coverage")]
#[cfg(any(feature = "coverage", feature = "wasm_fuzzing"))]

pub const MAX_INVOKE_PAYLOAD_SIZE: usize = 32 * 1024 * 1024;

/// The proposer's share of tips
Expand Down
4 changes: 2 additions & 2 deletions radix-common/src/constants/wasm.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
/// The maximum memory size (per call frame): 64 * 64KiB = 4MiB
#[cfg(not(feature = "coverage"))]
#[cfg(all(not(feature = "coverage"), not(feature = "wasm_fuzzing")))]
pub const MAX_MEMORY_SIZE_IN_PAGES: u32 = 64;
#[cfg(feature = "coverage")]
#[cfg(any(feature = "coverage", feature = "wasm_fuzzing"))]
pub const MAX_MEMORY_SIZE_IN_PAGES: u32 = 512;

/// The maximum initial table size
Expand Down
1 change: 1 addition & 0 deletions radix-engine/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,7 @@ full_wasm_benchmarks = []

# This flag disables package size limit, memory size limit and fee limit
coverage = [ "radix-common/coverage" ]
wasm_fuzzing = [ "radix-common/wasm_fuzzing" ]

# Ref: https://bheisler.github.io/criterion.rs/book/faq.html#cargo-bench-gives-unrecognized-option-errors-for-valid-command-line-options
[lib]
Expand Down
4 changes: 2 additions & 2 deletions radix-engine/src/transaction/transaction_executor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ pub struct CostingParameters {
}

impl CostingParameters {
#[cfg(not(feature = "coverage"))]
#[cfg(all(not(feature = "coverage"), not(feature = "wasm_fuzzing")))]
pub fn babylon_genesis() -> Self {
Self {
execution_cost_unit_price: EXECUTION_COST_UNIT_PRICE_IN_XRD.try_into().unwrap(),
Expand All @@ -74,7 +74,7 @@ impl CostingParameters {
archive_storage_price: ARCHIVE_STORAGE_PRICE_IN_XRD.try_into().unwrap(),
}
}
#[cfg(feature = "coverage")]
#[cfg(any(feature = "coverage", feature = "wasm_fuzzing"))]
pub fn babylon_genesis() -> Self {
Self {
execution_cost_unit_price: Decimal::zero(),
Expand Down
2 changes: 1 addition & 1 deletion radix-engine/src/vm/wasm/prepare.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1129,7 +1129,7 @@ impl WasmModule {
mut self,
rules: &R,
) -> Result<Self, PrepareError> {
#[cfg(not(feature = "coverage"))]
#[cfg(all(not(feature = "coverage"), not(feature = "wasm_fuzzing")))]
{
let backend = gas_metering::host_function::Injector::new(
MODULE_ENV_NAME,
Expand Down
3 changes: 3 additions & 0 deletions scrypto-wasm-fuzzer/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
input
output
fuzz_blueprint/target
17 changes: 17 additions & 0 deletions scrypto-wasm-fuzzer/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
[package]
name = "scrypto-wasm-fuzzer"
version = "0.0.1"
edition = "2021"
build = "build.rs"

[dependencies]
radix-engine = { workspace = true, default-features = true, features = ["wasm_fuzzing"] }
radix-engine-interface = { workspace = true, default-features = true }
radix-transactions = { workspace = true, default-features = true }
scrypto-test = { workspace = true, default-features = true }
scrypto-compiler = { workspace = true, default-features = true }
proc-lock = "0.4.0"

[[bin]]
name = "wasm_fuzzer"
path = "bin/wasm_fuzzer.rs"
79 changes: 79 additions & 0 deletions scrypto-wasm-fuzzer/bin/wasm_fuzzer.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
#![no_main]

use std::path::PathBuf;

use scrypto_wasm_fuzzer::*;
use radix_engine_interface::{blueprints::resource::OwnerRole, metadata_init};
use radix_transactions::builder::ManifestBuilder;
use scrypto_test::ledger_simulator::LedgerSimulatorBuilder;
use scrypto_test::prelude::*;

extern "C" {
fn __sanitizer_cov_8bit_counters_init(start: *mut u8, end: *mut u8);
}

static mut COUNTERS : Option<Vec<u8>> = None;
static mut LEDGER : Option<LedgerSimulator<NoExtension, InMemorySubstateDatabase>> = None;
static mut PACKAGE_ADDRESS : Option<PackageAddress> = None;

#[no_mangle]
pub extern "C" fn LLVMFuzzerInitialize(_argc: *const u32, _argv: *const *const *const u8) -> u32 {
let (code, definition) = build_for_fuzzing(PathBuf::from("fuzz_blueprint"));

let mut ledger = LedgerSimulatorBuilder::new().without_kernel_trace().build();
let manifest = ManifestBuilder::new()
.lock_fee_from_faucet()
.publish_package_advanced(None, code, definition, metadata_init!(), OwnerRole::None)
.build();
let receipt = ledger.execute_manifest(manifest, vec![]);
let package_address = receipt.expect_commit(true).new_package_addresses()[0];
let manifest = ManifestBuilder::new()
.lock_fee_from_faucet()
.call_function(package_address, "FuzzBlueprint", "get_counters_size", ())
.build();
let receipt = ledger.execute_manifest(manifest, vec![]);
let counters_len : usize = receipt.expect_commit_success().output(1);

unsafe {
COUNTERS = Some(vec![0; counters_len]);
LEDGER = Some(ledger);
PACKAGE_ADDRESS = Some(package_address);

let start_ptr = COUNTERS.as_mut().unwrap().as_mut_ptr();
let end_ptr = start_ptr.add(counters_len);
__sanitizer_cov_8bit_counters_init(start_ptr, end_ptr);
}
0
}

#[no_mangle]
pub extern "C" fn LLVMFuzzerTestOneInput(data: *const u8, size: usize) -> u32 {
let slice = unsafe {
std::slice::from_raw_parts(data, size)
};

let data = slice.to_vec();
let counters = unsafe {
let manifest = ManifestBuilder::new()
.call_function(PACKAGE_ADDRESS.unwrap(), "FuzzBlueprint", "fuzz", (data, ))
.build();
let receipt = LEDGER.as_mut().unwrap().preview_manifest(
manifest,
Default::default(),
Default::default(),
PreviewFlags {
use_free_credit: true,
assume_all_signature_proofs: true,
skip_epoch_check: true,
disable_auth: false,
});

let counters : Vec<u8> = receipt.expect_commit_success().output(0);
counters
};

unsafe {
COUNTERS.as_mut().unwrap().iter_mut().zip(counters.iter()).for_each(|(a, b)| *a += b);
}
0
}
17 changes: 17 additions & 0 deletions scrypto-wasm-fuzzer/build.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
use std::{process::Command, env};

fn main() {
let status = Command::new("make")
.args(&["-C", "honggfuzz", "honggfuzz", "libhfuzz/libhfuzz.a", "libhfcommon/libhfcommon.a"])
.status()
.expect("failed to run \"make -C honggfuzz hongfuzz libhfuzz/libhfuzz.a libhfcommon/libhfcommon.a\"");
assert!(status.success());

let lib_dir = env::current_dir().unwrap().join("honggfuzz/libhfuzz");
println!("cargo:rustc-link-search=native={}", lib_dir.display());
let lib_dir = env::current_dir().unwrap().join("honggfuzz/libhfcommon");
println!("cargo:rustc-link-search=native={}", lib_dir.display());

println!("cargo:rustc-link-lib=static=hfuzz");
println!("cargo:rustc-link-lib=static=hfcommon");
}
17 changes: 17 additions & 0 deletions scrypto-wasm-fuzzer/fuzz_blueprint/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
[package]
name = "fuzz_blueprint"
version = "1.2.0-dev"
edition = "2021"

[dependencies]
sbor = { path = "../../sbor" }
scrypto = { path = "../../scrypto" }

[dev-dependencies]
radix-engine = { path = "../../radix-engine" }

[lib]
doctest = false
crate-type = ["cdylib", "lib"]

[workspace]
78 changes: 78 additions & 0 deletions scrypto-wasm-fuzzer/fuzz_blueprint/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
use scrypto::prelude::*;
use scrypto::types::Slice;

static mut START: *const u8 = std::ptr::null();
static mut STOP: *const u8 = std::ptr::null();

#[no_mangle]
pub extern "C" fn __sanitizer_cov_8bit_counters_init(start: *const u8, stop: *const u8) {
unsafe {
START = start;
STOP = stop;
}
}

#[no_mangle]
pub unsafe extern "C" fn dump_coverage_counters() -> Slice {
let length = STOP.offset_from(START) as usize;
Slice::new(START as u32, length as u32)
}

#[blueprint]
mod fuzz_blueprint {
struct FuzzBlueprint;

impl FuzzBlueprint {
fn deposits_and_withdraw(deposits: Vec<Decimal>, withdraws: Vec<Decimal>) -> bool {
if deposits.len() == 0 {
return false;
}

let mut deposits_sum : Decimal = deposits.iter().fold(dec!(0), |acc, x| acc + *x);
let mut deposits : IndexSet<Decimal> = deposits.into_iter().collect();

for withdraw in withdraws {
if !deposits.swap_remove(&withdraw) {
continue;
}
assert!(deposits_sum >= withdraw);
deposits_sum -= withdraw;
}
assert!(deposits.len() != 0 || deposits_sum == dec!(0));

return true;
}

pub fn fuzz(input: Vec<u8>) -> Vec<u8> {
let mut decoder = ScryptoDecoder::new(&input, 10);
let mut deposits = Vec::new();
let mut withdraws = Vec::new();

// fuzzer friendly decoding
while let Ok(decimal) = decoder.decode_deeper_body_with_value_kind::<Decimal>(Decimal::value_kind()) {
if decimal == dec!(0) || decimal > dec!(10000) || decimal < dec!(-10000) {
break;
}
if decimal > dec!(0) {
deposits.push(decimal);
} else {
withdraws.push(-decimal);
}
}
Self::deposits_and_withdraw(deposits, withdraws);

unsafe {
let length = STOP.offset_from(START) as usize;
let slice = std::slice::from_raw_parts(START, length);
slice.to_vec()
}
}

pub fn get_counters_size() -> usize {
unsafe {
STOP.offset_from(START) as usize
}
}
}
}

1 change: 1 addition & 0 deletions scrypto-wasm-fuzzer/honggfuzz
Submodule honggfuzz added at 348a47
6 changes: 6 additions & 0 deletions scrypto-wasm-fuzzer/run.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
#!/bin/bash

cargo build --release
mkdir -p input
mkdir -p output
honggfuzz/honggfuzz -P -i input -o output --keep_output -t 600000 -n 1 -v -- ../target/release/wasm_fuzzer
Loading