Skip to content

Commit

Permalink
init vm fuzzing
Browse files Browse the repository at this point in the history
  • Loading branch information
addisoncrump committed Nov 6, 2022
1 parent 19e8dd8 commit 4e6e8af
Show file tree
Hide file tree
Showing 6 changed files with 73 additions and 3 deletions.
6 changes: 6 additions & 0 deletions boa_engine/fuzz/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -34,3 +34,9 @@ name = "parser-idempotency"
path = "fuzz_targets/parser-idempotency.rs"
test = false
doc = false

[[bin]]
name = "vm-implied"
path = "fuzz_targets/vm-implied.rs"
test = false
doc = false
4 changes: 2 additions & 2 deletions boa_engine/fuzz/fuzz_targets/common.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ use boa_ast::{
visitor::{VisitWith, VisitorMut},
Expression, StatementList,
};
use boa_engine::context::Context;
use boa_engine::context::{Context, ContextBuilder};
use boa_interner::Sym;
use libfuzzer_sys::arbitrary;
use libfuzzer_sys::arbitrary::{Arbitrary, Unstructured};
Expand All @@ -16,7 +16,7 @@ pub struct FuzzData {

impl<'a> Arbitrary<'a> for FuzzData {
fn arbitrary(u: &mut Unstructured<'a>) -> arbitrary::Result<Self> {
let mut context = Context::default();
let mut context = ContextBuilder::new().insns_remaining(1 << 16).build();
let mut syms_available = Vec::with_capacity(8);
for c in 'a'..='h' {
syms_available.push(context.interner_mut().get_or_intern(&*String::from(c)));
Expand Down
19 changes: 19 additions & 0 deletions boa_engine/fuzz/fuzz_targets/vm-implied.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
#![no_main]

mod common;

use crate::common::FuzzData;
use boa_engine::{JsResult, JsValue};
use boa_interner::ToInternedString;
use libfuzzer_sys::fuzz_target;

fn do_fuzz(mut data: FuzzData) -> JsResult<JsValue> {
// Convert back to source; we may not actually produce valid code, so we need to re-parse it.
let original = data.ast.to_interned_string(data.context.interner());

data.context.eval(&original).into()
}

fuzz_target!(|data: FuzzData| {
let _ = do_fuzz(data);
});
17 changes: 17 additions & 0 deletions boa_engine/src/context/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,10 @@ pub struct Context {
#[cfg(feature = "intl")]
icu: icu::Icu,

/// Number of instructions remaining before a forced exit
#[cfg(feature = "fuzzer-not-safe-for-production")]
pub(crate) insns_remaining: usize,

pub(crate) vm: Vm,

pub(crate) promise_job_queue: VecDeque<JobCallback>,
Expand Down Expand Up @@ -597,6 +601,8 @@ pub struct ContextBuilder {
interner: Option<Interner>,
#[cfg(feature = "intl")]
icu: Option<icu::Icu>,
#[cfg(feature = "fuzzer-not-safe-for-production")]
insns_remaining: usize,
}

impl ContextBuilder {
Expand All @@ -619,6 +625,15 @@ impl ContextBuilder {
Ok(self)
}

/// Specifies the number of instructions remaining to the [`Context`].
///
/// This function is only available if the `fuzzer-not-safe-for-production` feature is enabled.
#[cfg(feature = "fuzzer-not-safe-for-production")]
pub fn insns_remaining(mut self, insns_remaining: usize) -> Self {
self.insns_remaining = insns_remaining;
self
}

/// Creates a new [`ContextBuilder`] with a default empty [`Interner`]
/// and a default [`BoaProvider`] if the `intl` feature is enabled.
pub fn new() -> Self {
Expand Down Expand Up @@ -647,6 +662,8 @@ impl ContextBuilder {
icu::Icu::new(Box::new(icu_testdata::get_provider()))
.expect("Failed to initialize default icu data.")
}),
#[cfg(feature = "fuzzer-not-safe-for-production")]
insns_remaining: self.insns_remaining,
promise_job_queue: VecDeque::new(),
};

Expand Down
21 changes: 21 additions & 0 deletions boa_engine/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -504,6 +504,17 @@ impl JsNativeError {
Self::new(JsNativeErrorKind::Uri, Box::default(), None)
}

/// Creates a new `JsNativeError` that indicates that the context hit its execution limit. This
/// is only used in a fuzzing context.
#[cfg(feature = "fuzzer-not-safe-for-production")]
pub fn no_instructions_remain() -> Self {
Self::new(
JsNativeErrorKind::NoInstructionsRemain,
Box::default(),
None,
)
}

/// Sets the message of this error.
///
/// # Examples
Expand Down Expand Up @@ -620,6 +631,10 @@ impl JsNativeError {
}
JsNativeErrorKind::Type => (constructors.type_error().prototype(), ErrorKind::Type),
JsNativeErrorKind::Uri => (constructors.uri_error().prototype(), ErrorKind::Uri),
#[cfg(feature = "fuzzer-not-safe-for-production")]
JsNativeErrorKind::NoInstructionsRemain => unimplemented!(
"Cannot convert a NoInstructionsRemain error variant to an opaque type."
),
};

let o = JsObject::from_proto_and_data(prototype, ObjectData::error(tag));
Expand Down Expand Up @@ -748,6 +763,10 @@ pub enum JsNativeErrorKind {
/// [e_uri]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/encodeURI
/// [d_uri]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/decodeURI
Uri,
/// Error thrown when no instructions remain. Only used in a fuzzing context; not a valid JS
/// error variant.
#[cfg(feature = "fuzzer-not-safe-for-production")]
NoInstructionsRemain,
}

impl std::fmt::Display for JsNativeErrorKind {
Expand All @@ -761,6 +780,8 @@ impl std::fmt::Display for JsNativeErrorKind {
JsNativeErrorKind::Syntax => "SyntaxError",
JsNativeErrorKind::Type => "TypeError",
JsNativeErrorKind::Uri => "UriError",
#[cfg(feature = "fuzzer-not-safe-for-production")]
JsNativeErrorKind::NoInstructionsRemain => "NoInstructionsRemain",
}
.fmt(f)
}
Expand Down
9 changes: 8 additions & 1 deletion boa_engine/src/vm/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
use crate::{
builtins::async_generator::{AsyncGenerator, AsyncGeneratorState},
vm::{call_frame::CatchAddresses, code_block::Readable},
Context, JsResult, JsValue,
Context, JsError, JsNativeError, JsResult, JsValue,
};
use boa_interner::ToInternedString;
use boa_profiler::Profiler;
Expand Down Expand Up @@ -112,6 +112,13 @@ pub(crate) enum ReturnType {

impl Context {
fn execute_instruction(&mut self) -> JsResult<ShouldExit> {
#[cfg(feature = "fuzzer-not-safe-for-production")]
if self.insns_remaining == 0 {
return Err(JsError::from_native(JsNativeError::no_instructions_remain()));
} else {
self.insns_remaining -= 1;
}

let opcode: Opcode = {
let _timer = Profiler::global().start_event("Opcode retrieval", "vm");
let opcode = self.vm.frame().code.code[self.vm.frame().pc]
Expand Down

0 comments on commit 4e6e8af

Please sign in to comment.