Skip to content

Commit

Permalink
afl-fuzz fuzzes a single instruction
Browse files Browse the repository at this point in the history
  • Loading branch information
joonazan committed May 25, 2024
1 parent 4cca5c3 commit 172cc0f
Show file tree
Hide file tree
Showing 26 changed files with 520 additions and 27 deletions.
4 changes: 4 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
workspace = { members = ["afl-fuzz"] }
[package]
name = "vm2"
version = "0.1.0"
Expand All @@ -20,3 +21,6 @@ proptest = "1.4"
[[bench]]
name = "nested_near_call"
harness = false

[features]
single_instruction_test = ["arbitrary", "u256/arbitrary"]
2 changes: 2 additions & 0 deletions afl-fuzz/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
in
out
16 changes: 16 additions & 0 deletions afl-fuzz/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
[package]
name = "afl-fuzz"
version = "0.1.0"
edition = "2021"

[dependencies]
afl = "*"
arbitrary = "*"

[dependencies.vm2]
path = ".."
features = ["single_instruction_test"]

[[bin]]
name = "show_testcase"
path = "src/show_testcase.rs"
1 change: 1 addition & 0 deletions afl-fuzz/fuzz.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
cargo afl build --release && cargo afl fuzz -i in -o out ../target/release/afl-fuzz -g 10k
18 changes: 18 additions & 0 deletions afl-fuzz/src/main.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
use arbitrary::Arbitrary;
use vm2::{MockWorld, VirtualMachine};

fn main() {
afl::fuzz!(|data: &[u8]| {
if let Ok(VmAndWorld { mut vm, mut world }) = arbitrary::Unstructured::new(data).arbitrary()
{
let instruction = vm.get_first_instruction();
let result = vm.run_single_instruction(instruction, &mut world);
}
});
}

#[derive(Arbitrary, Debug)]
struct VmAndWorld {
vm: VirtualMachine,
world: MockWorld,
}
28 changes: 28 additions & 0 deletions afl-fuzz/src/show_testcase.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
use std::env;
use std::fs;

use arbitrary::Arbitrary;
use vm2::MockWorld;
use vm2::VirtualMachine;

fn main() {
let filename = env::args()
.nth(1)
.expect("Please provide the test case to show as argument.");

let bytes = fs::read(filename).expect("Failed to read file");

let VmAndWorld { mut vm, mut world } =
arbitrary::Unstructured::new(&bytes).arbitrary().unwrap();

println!("{:?}", vm.state);

let instruction = vm.get_first_instruction();
vm.run_single_instruction(instruction, &mut world);
}

#[derive(Arbitrary, Debug)]
struct VmAndWorld {
vm: VirtualMachine,
world: MockWorld,
}
13 changes: 8 additions & 5 deletions src/addressing_modes.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use crate::{bitset::Bitset, predication::Predicate};
use crate::predication::Predicate;
#[cfg(feature = "arbitrary")]
use arbitrary::{Arbitrary, Unstructured};
use enum_dispatch::enum_dispatch;
Expand All @@ -24,9 +24,12 @@ pub trait Addressable {

fn read_stack(&mut self, slot: u16) -> U256;
fn write_stack(&mut self, slot: u16, value: U256);
fn stack_pointer_flags(&mut self) -> &mut Bitset;
fn stack_pointer(&mut self) -> &mut u16;

fn read_stack_pointer_flag(&mut self, slot: u16) -> bool;
fn set_stack_pointer_flag(&mut self, slot: u16);
fn clear_stack_pointer_flag(&mut self, slot: u16);

fn code_page(&self) -> &[U256];
}

Expand Down Expand Up @@ -272,21 +275,21 @@ impl<T: StackAddressing> Source for T {

fn is_fat_pointer(args: &Arguments, state: &mut impl Addressable) -> bool {
let address = Self::address_for_get(args, state);
state.stack_pointer_flags().get(address)
state.read_stack_pointer_flag(address)
}
}

impl<T: StackAddressing> Destination for T {
fn set(args: &Arguments, state: &mut impl Addressable, value: U256) {
let address = Self::address_for_set(args, state);
state.write_stack(address, value);
state.stack_pointer_flags().clear(address);
state.clear_stack_pointer_flag(address);
}

fn set_fat_ptr(args: &Arguments, state: &mut impl Addressable, value: U256) {
let address = Self::address_for_set(args, state);
state.write_stack(address, value);
state.stack_pointer_flags().set(address);
state.set_stack_pointer_flag(address);
}
}

Expand Down
9 changes: 4 additions & 5 deletions src/callframe.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ pub struct Callframe {
pub gas: u32,
pub stipend: u32,

near_calls: Vec<NearCallFrame>,
pub(crate) near_calls: Vec<NearCallFrame>,

pub(crate) program: Program,

Expand All @@ -48,7 +48,7 @@ pub struct Callframe {
}

#[derive(Clone, PartialEq, Debug)]
struct NearCallFrame {
pub(crate) struct NearCallFrame {
call_instruction: u16,
exception_handler: u16,
previous_frame_sp: u16,
Expand Down Expand Up @@ -134,13 +134,12 @@ impl Callframe {
}

pub(crate) fn pc_to_u16(&self, pc: *const Instruction) -> u16 {
unsafe { pc.offset_from(&self.program.instructions()[0]) as u16 }
unsafe { pc.offset_from(self.program.instruction(0).unwrap()) as u16 }
}

pub(crate) fn pc_from_u16(&self, index: u16) -> Option<*const Instruction> {
self.program
.instructions()
.get(index as usize)
.instruction(index)
.map(|p| p as *const Instruction)
}

Expand Down
2 changes: 1 addition & 1 deletion src/decode.rs
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ fn unimplemented_handler(
Err(ExecutionEnd::Panicked)
}

fn decode(raw: u64, is_bootloader: bool) -> Instruction {
pub(crate) fn decode(raw: u64, is_bootloader: bool) -> Instruction {
let (parsed, _) = EncodingModeProduction::parse_preliminary_variant_and_absolute_number(raw);

let predicate = match parsed.condition {
Expand Down
2 changes: 1 addition & 1 deletion src/instruction.rs
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,6 @@ fn jump_to_beginning_handler(
_: *const Instruction,
_: &mut dyn World,
) -> InstructionResult {
let first_instruction = &vm.state.current_frame.program.instructions()[0];
let first_instruction = vm.state.current_frame.program.instruction(0).unwrap();
Ok(first_instruction)
}
2 changes: 1 addition & 1 deletion src/instruction_handlers/far_call.rs
Original file line number Diff line number Diff line change
Expand Up @@ -123,7 +123,7 @@ fn far_call<const CALLING_MODE: u8, const IS_STATIC: bool>(

vm.state.registers[2] = call_type.into();

Ok(&vm.state.current_frame.program.instructions()[0])
Ok(vm.state.current_frame.program.instruction(0).unwrap())
}

pub(crate) struct FarCallABI {
Expand Down
4 changes: 2 additions & 2 deletions src/instruction_handlers/jump.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,8 @@ fn jump<In: Source>(
_: &mut dyn World,
) -> InstructionResult {
unsafe {
let target = In::get(&(*instruction).arguments, &mut vm.state).low_u32() as u16 as usize;
if let Some(i) = vm.state.current_frame.program.instructions().get(target) {
let target = In::get(&(*instruction).arguments, &mut vm.state).low_u32() as u16;
if let Some(i) = vm.state.current_frame.program.instruction(target) {
instruction = i;
} else {
return Ok(&INVALID_INSTRUCTION);
Expand Down
7 changes: 6 additions & 1 deletion src/instruction_handlers/near_call.rs
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,12 @@ fn near_call(

vm.state.flags = Flags::new(false, false, false);

Ok(&vm.state.current_frame.program.instructions()[destination.low_u32() as usize])
Ok(vm
.state
.current_frame
.program
.instruction(destination.low_u32() as u16)
.unwrap())
}

impl Instruction {
Expand Down
15 changes: 15 additions & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,17 +1,21 @@
pub mod addressing_modes;
#[cfg(feature = "arbitrary")]
mod arbitrary_instruction;
#[cfg(not(feature = "single_instruction_test"))]
mod bitset;
mod callframe;
pub mod decode;
mod decommit;
pub mod fat_pointer;
#[cfg(not(feature = "single_instruction_test"))]
mod heap;
mod instruction;
pub mod instruction_handlers;
mod predication;
#[cfg(not(feature = "single_instruction_test"))]
mod program;
mod rollback;
#[cfg(not(feature = "single_instruction_test"))]
mod stack;
mod state;
pub mod testworld;
Expand All @@ -30,6 +34,17 @@ pub use state::State;
pub use vm::{Settings, VirtualMachine, VmSnapshot as Snapshot};
pub use world_diff::{Event, L2ToL1Log, WorldDiff};

#[cfg(feature = "single_instruction_test")]
mod single_instruction_test;
#[cfg(feature = "single_instruction_test")]
use single_instruction_test::heap;
#[cfg(feature = "single_instruction_test")]
use single_instruction_test::program;
#[cfg(feature = "single_instruction_test")]
use single_instruction_test::stack;
#[cfg(feature = "single_instruction_test")]
pub use single_instruction_test::MockWorld;

pub trait World {
/// This will be called *every* time a contract is called. Caching and decoding is
/// the world implementor's job.
Expand Down
4 changes: 2 additions & 2 deletions src/program.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,8 @@ impl Program {
}
}

pub fn instructions(&self) -> &Arc<[Instruction]> {
&self.instructions
pub fn instruction(&self, n: u16) -> Option<&Instruction> {
self.instructions.get::<usize>(n.into())
}

pub fn code_page(&self) -> &Arc<[U256]> {
Expand Down
34 changes: 34 additions & 0 deletions src/single_instruction_test/callframe.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
use crate::{callframe::Callframe, predication::Flags, WorldDiff};
use arbitrary::Arbitrary;

impl<'a> Arbitrary<'a> for Flags {
fn arbitrary(u: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result<Self> {
Ok(Self::new(u.arbitrary()?, u.arbitrary()?, u.arbitrary()?))
}
}

impl<'a> Arbitrary<'a> for Callframe {
fn arbitrary(u: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result<Self> {
Ok(Self {
address: u.arbitrary()?,
code_address: u.arbitrary()?,
caller: u.arbitrary()?,
exception_handler: u.arbitrary()?,
context_u128: u.arbitrary()?,
is_static: u.arbitrary()?,
stack: u.arbitrary()?,
sp: u.arbitrary()?,
gas: u.arbitrary()?,
stipend: u.arbitrary()?,
near_calls: vec![], // TODO
program: u.arbitrary()?,
heap: u.arbitrary()?,
aux_heap: u.arbitrary()?,
heap_size: u.arbitrary()?,
aux_heap_size: u.arbitrary()?,
calldata_heap: u.arbitrary()?,
heaps_i_am_keeping_alive: vec![], // TODO
world_before_this_frame: WorldDiff::default().snapshot(), // TODO
})
}
}
69 changes: 69 additions & 0 deletions src/single_instruction_test/heap.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
use super::mock_array::MockRead;
use arbitrary::Arbitrary;
use std::ops::{Index, IndexMut};

//#[derive(Debug, Clone)]
type Heap = Vec<u8>;

#[derive(Debug, Clone)]
pub struct Heaps {
read: MockRead<HeapId, Heap>,
}

impl<'a> Arbitrary<'a> for Heaps {
fn arbitrary(u: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result<Self> {
Ok(Self {
read: MockRead::new(vec![u.arbitrary()?; 1]),
})
}
}

pub(crate) const CALLDATA_HEAP: HeapId = HeapId(1);
pub const FIRST_HEAP: HeapId = HeapId(2);
pub(crate) const FIRST_AUX_HEAP: HeapId = HeapId(3);

impl Heaps {
pub(crate) fn new(_: Vec<u8>) -> Self {
unimplemented!("Should use arbitrary heap, not fresh heap in testing.")
}

pub(crate) fn allocate(&mut self) -> HeapId {
todo!()
}

pub(crate) fn deallocate(&mut self, _: HeapId) {}
}

impl Index<HeapId> for Heaps {
type Output = Heap;

fn index(&self, index: HeapId) -> &Self::Output {
&self.read.get(index)
}
}

impl IndexMut<HeapId> for Heaps {
fn index_mut(&mut self, index: HeapId) -> &mut Self::Output {
self.read.get_mut(index)
}
}

impl PartialEq for Heaps {
fn eq(&self, _: &Self) -> bool {
false
}
}

#[derive(Copy, Clone, PartialEq, Debug, Arbitrary)]
pub struct HeapId(u32);

impl HeapId {
/// Only for dealing with external data structures, never use internally.
pub fn from_u32_unchecked(value: u32) -> Self {
Self(value)
}

pub fn to_u32(self) -> u32 {
self.0
}
}
Loading

0 comments on commit 172cc0f

Please sign in to comment.