Skip to content

feat: add vm asm fuzzer #9

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

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
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
1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ tracing = "0.1"
tracing-subscriber = "0.3"
tracing-test = "0.2"

everscale-asm = { git = "https://github.com/broxus/everscale-asm.git", rev = "bbd284a72676300c89ab074bd39cd91fde21d597" }
everscale-asm-macros = { git = "https://github.com/broxus/everscale-asm.git", rev = "1ca1675c0e9b7fa8dde3a5f7422ebd3bd169fb62" }

tycho-vm = { path = "./vm" }
Expand Down
16 changes: 16 additions & 0 deletions fuzz/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,20 @@ test = false
doc = false
bench = false

[[bin]]
name = "vm_asm"
path = "fuzz_targets/vm_asm.rs"
test = false
doc = false
bench = false

[[bin]]
name = "vm_opcode"
path = "fuzz_targets/vm_opcode_fuzz.rs"
test = false
doc = false
bench = false

[[bin]]
name = "action_phase_real"
path = "fuzz_targets/action_phase_real.rs"
Expand All @@ -34,3 +48,5 @@ everscale-types = { workspace = true, features = ["arbitrary", "base64"] }
libfuzzer-sys = { workspace = true }
tycho-executor = { path = "../executor" }
tycho-vm = { path = "../vm", features = ["arbitrary"] }
everscale-asm = { workspace = true }
num-bigint = "0.4.6"
28 changes: 28 additions & 0 deletions fuzz/fuzz_targets/vm_asm.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
#![no_main]

use everscale_asm::Code;
use libfuzzer_sys::{fuzz_target, Corpus};
use tycho_vm::{CustomSmcInfo, GasParams, SafeRc, VmState};

fuzz_target!(|code: String| -> Corpus {
if !code.chars().all(|c| c.is_ascii_graphic() || c == ' ') {
return Corpus::Reject;
}

let code = match Code::assemble(&code) {
Ok(code) => code,
Err(_) => return Corpus::Reject,
};

let mut vm = VmState::builder()
.with_code(code)
.with_smc_info(CustomSmcInfo {
version: VmState::DEFAULT_VERSION,
c7: SafeRc::new(Vec::new()),
})
.with_gas(GasParams::getter())
.build();

_ = vm.run();
Corpus::Keep
});
155 changes: 155 additions & 0 deletions fuzz/fuzz_targets/vm_opcode_fuzz.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,155 @@
#![no_main]

use std::ops::ControlFlow;

use arbitrary::{Arbitrary, Result as ArbitraryResult, Unstructured};
use everscale_types::arbitrary::OrdinaryCell;
use everscale_types::cell::CellBuilder;
use libfuzzer_sys::{fuzz_target, Corpus};
use num_bigint::{BigInt, Sign};
use tycho_vm::{CustomSmcInfo, GasParams, RcStackValue, SafeRc, Stack, Tuple, VmState};

const MAX_BIGINT_BYTES: usize = 32; // Max bytes for BigInt generation (32 bytes = 256 bits)
const MAX_TUPLE_ELEMENTS: u32 = 16;

#[derive(Debug)]
enum ArbitraryStackValue {
Null,
Int(BigInt),
NaN,
Cell(OrdinaryCell),
EmptyBuilder,
Tuple(Vec<ArbitraryStackValue>),
}

impl<'a> Arbitrary<'a> for ArbitraryStackValue {
fn arbitrary(u: &mut Unstructured<'a>) -> ArbitraryResult<Self> {
match u.int_in_range(0..=5)? {
0 => Ok(ArbitraryStackValue::Null),
1 => {
let sign = match u.int_in_range(0..=2)? {
0 => Sign::Plus,
1 => Sign::Minus,
_ => Sign::NoSign,
};

let num_bytes = u.int_in_range(0..=MAX_BIGINT_BYTES)?;
let bytes_slice: &[u8] = u.bytes(num_bytes)?;
Ok(ArbitraryStackValue::Int(BigInt::from_bytes_be(
sign,
bytes_slice,
)))
}
2 => Ok(ArbitraryStackValue::NaN),
3 => {
let cell: OrdinaryCell = u.arbitrary()?;
Ok(ArbitraryStackValue::Cell(cell))
}
4 => Ok(ArbitraryStackValue::EmptyBuilder),
5 => {
// Tuple (recursive, up to MAX_TUPLE_ELEMENTS)
let mut items = Vec::new();
u.arbitrary_loop(None, Some(MAX_TUPLE_ELEMENTS), |u_inner| {
items.push(u_inner.arbitrary()?);
Ok(ControlFlow::Continue(()))
})?;
Ok(ArbitraryStackValue::Tuple(items))
}
_ => unreachable!(),
}
}
}

fn arbitrary_to_rc(item: ArbitraryStackValue) -> Option<RcStackValue> {
Some(match item {
ArbitraryStackValue::Null => Stack::make_null(),
ArbitraryStackValue::Int(val) => SafeRc::new_dyn_value(val),
ArbitraryStackValue::NaN => Stack::make_nan(),
ArbitraryStackValue::Cell(OrdinaryCell(cell)) => SafeRc::new_dyn_value(cell),
ArbitraryStackValue::EmptyBuilder => SafeRc::new_dyn_value(CellBuilder::new()),
ArbitraryStackValue::Tuple(items) => {
let tuple_items: Tuple = items
.into_iter()
.map(arbitrary_to_rc)
.collect::<Option<Vec<_>>>()?;
SafeRc::new_dyn_value(tuple_items)
}
})
}

const MAX_ITEMS: u32 = 32;

#[derive(Debug)]
struct Input {
code: OrdinaryCell,
initial_stack_items: Vec<ArbitraryStackValue>,
c7_items: Vec<ArbitraryStackValue>,
}

impl<'a> Arbitrary<'a> for Input {
fn arbitrary(u: &mut Unstructured<'a>) -> ArbitraryResult<Self> {
let code: OrdinaryCell = u.arbitrary()?;

let mut initial_stack_items = Vec::new();
u.arbitrary_loop(None, Some(MAX_ITEMS), |u_inner| {
initial_stack_items.push(u_inner.arbitrary()?);
Ok(ControlFlow::Continue(()))
})?;

let mut c7_items = Vec::new();
u.arbitrary_loop(None, Some(MAX_ITEMS), |u_inner| {
c7_items.push(u_inner.arbitrary()?);
Ok(ControlFlow::Continue(()))
})?;

Ok(Input {
code,
initial_stack_items,
c7_items,
})
}
}

fuzz_target!(|input: Input| -> Corpus {
let OrdinaryCell(code) = input.code;

let stack_items: Option<Vec<RcStackValue>> = input
.initial_stack_items
.into_iter()
.map(arbitrary_to_rc)
.collect();

let stack_items = match stack_items {
Some(items) => items,
None => return Corpus::Reject,
};

let c7_rc_items: Option<Vec<RcStackValue>> =
input.c7_items.into_iter().map(arbitrary_to_rc).collect();
let c7_rc_items = match c7_rc_items {
Some(items) => items,
None => return Corpus::Reject,
};

let stack = Stack::with_items(stack_items);

let start = std::time::Instant::now();
let mut vm = VmState::builder()
.with_code(code)
.with_raw_stack(stack.into())
.with_smc_info(CustomSmcInfo {
version: VmState::DEFAULT_VERSION,
c7: SafeRc::new(c7_rc_items),
})
.with_gas(GasParams::getter())
.build();

let _ = vm.run();

let elapsed = start.elapsed().as_millis();
if elapsed > 500 {
panic!("Execution took too long: {} ms", elapsed);
}

Corpus::Keep
});
Loading