Skip to content

Commit 897ab0c

Browse files
committed
feat: add vm asm fuzzer
1 parent c224351 commit 897ab0c

File tree

5 files changed

+14142
-0
lines changed

5 files changed

+14142
-0
lines changed

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ tracing = "0.1"
3939
tracing-subscriber = "0.3"
4040
tracing-test = "0.2"
4141

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

4445
tycho-vm = { path = "./vm" }

fuzz/Cargo.toml

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,20 @@ test = false
1414
doc = false
1515
bench = false
1616

17+
[[bin]]
18+
name = "vm_asm"
19+
path = "fuzz_targets/vm_asm.rs"
20+
test = false
21+
doc = false
22+
bench = false
23+
24+
[[bin]]
25+
name = "vm_opcode"
26+
path = "fuzz_targets/vm_opcode_fuzz.rs"
27+
test = false
28+
doc = false
29+
bench = false
30+
1731
[[bin]]
1832
name = "action_phase_real"
1933
path = "fuzz_targets/action_phase_real.rs"
@@ -34,3 +48,5 @@ everscale-types = { workspace = true, features = ["arbitrary", "base64"] }
3448
libfuzzer-sys = { workspace = true }
3549
tycho-executor = { path = "../executor" }
3650
tycho-vm = { path = "../vm", features = ["arbitrary"] }
51+
everscale-asm = { workspace = true }
52+
num-bigint = "0.4.6"

fuzz/fuzz_targets/vm_asm.rs

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
#![no_main]
2+
3+
use everscale_asm::Code;
4+
use libfuzzer_sys::{fuzz_target, Corpus};
5+
use tycho_vm::{CustomSmcInfo, GasParams, SafeRc, VmState};
6+
7+
fuzz_target!(|code: String| -> Corpus {
8+
if !code.chars().all(|c| c.is_ascii_graphic() || c == ' ') {
9+
return Corpus::Reject;
10+
}
11+
12+
let code = match Code::assemble(&code) {
13+
Ok(code) => code,
14+
Err(_) => return Corpus::Reject,
15+
};
16+
17+
let mut vm = VmState::builder()
18+
.with_code(code)
19+
.with_smc_info(CustomSmcInfo {
20+
version: VmState::DEFAULT_VERSION,
21+
c7: SafeRc::new(Vec::new()),
22+
})
23+
.with_gas(GasParams::getter())
24+
.build();
25+
26+
_ = vm.run();
27+
Corpus::Keep
28+
});

fuzz/fuzz_targets/vm_opcode_fuzz.rs

Lines changed: 156 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,156 @@
1+
#![no_main]
2+
3+
use std::ops::ControlFlow;
4+
5+
use arbitrary::{Arbitrary, Result as ArbitraryResult, Unstructured};
6+
use everscale_types::arbitrary::OrdinaryCell;
7+
use everscale_types::cell::CellBuilder;
8+
use libfuzzer_sys::{fuzz_target, Corpus};
9+
use num_bigint::{BigInt, Sign};
10+
use tycho_vm::{CustomSmcInfo, GasParams, RcStackValue, SafeRc, Stack, Tuple, VmState};
11+
12+
const MAX_BIGINT_BYTES: usize = 32; // Max bytes for BigInt generation (32 bytes = 256 bits)
13+
const MAX_TUPLE_ELEMENTS: u32 = 16;
14+
15+
#[derive(Debug)]
16+
enum ArbitraryStackValue {
17+
Null,
18+
Int(BigInt),
19+
NaN,
20+
Cell(OrdinaryCell),
21+
EmptyBuilder,
22+
Tuple(Vec<ArbitraryStackValue>),
23+
}
24+
25+
impl<'a> Arbitrary<'a> for ArbitraryStackValue {
26+
fn arbitrary(u: &mut Unstructured<'a>) -> ArbitraryResult<Self> {
27+
match u.int_in_range(0..=5)? {
28+
0 => Ok(ArbitraryStackValue::Null),
29+
1 => {
30+
let sign = match u.int_in_range(0..=2)? {
31+
0 => Sign::Plus,
32+
1 => Sign::Minus,
33+
_ => Sign::NoSign,
34+
};
35+
36+
let num_bytes = u.int_in_range(0..=MAX_BIGINT_BYTES)?;
37+
let bytes_slice: &[u8] = u.bytes(num_bytes)?;
38+
Ok(ArbitraryStackValue::Int(BigInt::from_bytes_be(
39+
sign,
40+
bytes_slice,
41+
)))
42+
}
43+
2 => Ok(ArbitraryStackValue::NaN),
44+
3 => {
45+
let cell: OrdinaryCell = u.arbitrary()?;
46+
Ok(ArbitraryStackValue::Cell(cell))
47+
}
48+
4 => Ok(ArbitraryStackValue::EmptyBuilder),
49+
5 => {
50+
// Tuple (recursive, up to MAX_TUPLE_ELEMENTS)
51+
let mut items = Vec::new();
52+
u.arbitrary_loop(None, Some(MAX_TUPLE_ELEMENTS), |u_inner| {
53+
items.push(u_inner.arbitrary()?);
54+
Ok(ControlFlow::Continue(()))
55+
})?;
56+
Ok(ArbitraryStackValue::Tuple(items))
57+
}
58+
_ => unreachable!(),
59+
}
60+
}
61+
}
62+
63+
fn arbitrary_to_rc(item: ArbitraryStackValue) -> Option<RcStackValue> {
64+
Some(match item {
65+
ArbitraryStackValue::Null => Stack::make_null(),
66+
ArbitraryStackValue::Int(val) => SafeRc::new_dyn_value(val),
67+
ArbitraryStackValue::NaN => Stack::make_nan(),
68+
ArbitraryStackValue::Cell(OrdinaryCell(cell)) => SafeRc::new_dyn_value(cell),
69+
ArbitraryStackValue::EmptyBuilder => SafeRc::new_dyn_value(CellBuilder::new()),
70+
ArbitraryStackValue::Tuple(items) => {
71+
let tuple_items: Tuple = items
72+
.into_iter()
73+
.map(arbitrary_to_rc)
74+
.collect::<Option<Vec<_>>>()?;
75+
SafeRc::new_dyn_value(tuple_items)
76+
}
77+
})
78+
}
79+
80+
const MAX_ITEMS: u32 = 32;
81+
82+
#[derive(Debug)]
83+
struct Input {
84+
code: OrdinaryCell,
85+
initial_stack_items: Vec<ArbitraryStackValue>,
86+
c7_items: Vec<ArbitraryStackValue>,
87+
}
88+
89+
impl<'a> Arbitrary<'a> for Input {
90+
fn arbitrary(u: &mut Unstructured<'a>) -> ArbitraryResult<Self> {
91+
let code: OrdinaryCell = u.arbitrary()?;
92+
93+
let mut initial_stack_items = Vec::new();
94+
u.arbitrary_loop(None, Some(MAX_ITEMS), |u_inner| {
95+
initial_stack_items.push(u_inner.arbitrary()?);
96+
Ok(ControlFlow::Continue(()))
97+
})?;
98+
99+
let mut c7_items = Vec::new();
100+
u.arbitrary_loop(None, Some(MAX_ITEMS), |u_inner| {
101+
c7_items.push(u_inner.arbitrary()?);
102+
Ok(ControlFlow::Continue(()))
103+
})?;
104+
105+
Ok(Input {
106+
code,
107+
initial_stack_items,
108+
c7_items,
109+
})
110+
}
111+
}
112+
113+
fuzz_target!(|input: Input| -> Corpus {
114+
dbg!(&input);
115+
let OrdinaryCell(code) = input.code;
116+
117+
let stack_items: Option<Vec<RcStackValue>> = input
118+
.initial_stack_items
119+
.into_iter()
120+
.map(arbitrary_to_rc)
121+
.collect();
122+
123+
let stack_items = match stack_items {
124+
Some(items) => items,
125+
None => return Corpus::Reject,
126+
};
127+
128+
let c7_rc_items: Option<Vec<RcStackValue>> =
129+
input.c7_items.into_iter().map(arbitrary_to_rc).collect();
130+
let c7_rc_items = match c7_rc_items {
131+
Some(items) => items,
132+
None => return Corpus::Reject,
133+
};
134+
135+
let stack = Stack::with_items(stack_items);
136+
137+
let start = std::time::Instant::now();
138+
let mut vm = VmState::builder()
139+
.with_code(code)
140+
.with_raw_stack(stack.into())
141+
.with_smc_info(CustomSmcInfo {
142+
version: VmState::DEFAULT_VERSION,
143+
c7: SafeRc::new(c7_rc_items),
144+
})
145+
.with_gas(GasParams::getter())
146+
.build();
147+
148+
let _ = vm.run();
149+
150+
let elapsed = start.elapsed().as_millis();
151+
if elapsed > 500 {
152+
panic!("Execution took too long: {} ms", elapsed);
153+
}
154+
155+
Corpus::Keep
156+
});

0 commit comments

Comments
 (0)