From 828a4c4089882ec2d2b264207e433d8d7dd55997 Mon Sep 17 00:00:00 2001 From: Addison Crump Date: Sun, 6 Nov 2022 19:01:33 +0100 Subject: [PATCH] make a more stable exit strategy for fuzzing with loops --- boa_engine/src/error.rs | 23 +++++++++++++++++++++++ boa_engine/src/vm/mod.rs | 4 ++-- fuzz/fuzz_targets/vm-implied.rs | 28 ++++++++++++++++++---------- 3 files changed, 43 insertions(+), 12 deletions(-) diff --git a/boa_engine/src/error.rs b/boa_engine/src/error.rs index 00e124da31f..115a52f6672 100644 --- a/boa_engine/src/error.rs +++ b/boa_engine/src/error.rs @@ -503,6 +503,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 = "fuzz")] + pub fn no_instructions_remain() -> Self { + Self::new( + JsNativeErrorKind::NoInstructionsRemain, + Box::default(), + None, + ) + } + /// Sets the message of this error. /// /// # Examples @@ -619,6 +630,12 @@ impl JsNativeError { } JsNativeErrorKind::Type => (constructors.type_error().prototype(), ErrorKind::Type), JsNativeErrorKind::Uri => (constructors.uri_error().prototype(), ErrorKind::Uri), + #[cfg(feature = "fuzz")] + JsNativeErrorKind::NoInstructionsRemain => { + // we can propagate out from try/catch since the catch block will also perform some + // operation + (constructors.error().prototype(), ErrorKind::Error) + } }; let o = JsObject::from_proto_and_data(prototype, ObjectData::error(tag)); @@ -747,6 +764,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 = "fuzz")] + NoInstructionsRemain, } impl std::fmt::Display for JsNativeErrorKind { @@ -760,6 +781,8 @@ impl std::fmt::Display for JsNativeErrorKind { JsNativeErrorKind::Syntax => "SyntaxError", JsNativeErrorKind::Type => "TypeError", JsNativeErrorKind::Uri => "UriError", + #[cfg(feature = "fuzz")] + JsNativeErrorKind::NoInstructionsRemain => "NoInstructionsRemain", } .fmt(f) } diff --git a/boa_engine/src/vm/mod.rs b/boa_engine/src/vm/mod.rs index b529c9cb3c7..de7350a3bba 100644 --- a/boa_engine/src/vm/mod.rs +++ b/boa_engine/src/vm/mod.rs @@ -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; @@ -114,7 +114,7 @@ impl Context { fn execute_instruction(&mut self) -> JsResult { #[cfg(feature = "fuzz")] if self.insns_remaining == 0 { - return Ok(ShouldExit::True); + return Err(JsError::from_native(JsNativeError::no_instructions_remain())); } else { self.insns_remaining -= 1; } diff --git a/fuzz/fuzz_targets/vm-implied.rs b/fuzz/fuzz_targets/vm-implied.rs index cdbdf66a558..c42f8ca9146 100644 --- a/fuzz/fuzz_targets/vm-implied.rs +++ b/fuzz/fuzz_targets/vm-implied.rs @@ -3,22 +3,30 @@ mod common; use crate::common::FuzzSource; -use boa_engine::{Context, JsResult, JsValue}; +use boa_engine::{Context, JsNativeErrorKind}; use libfuzzer_sys::fuzz_target; use libfuzzer_sys::Corpus; -fn do_fuzz(original: FuzzSource) -> JsResult { +fn do_fuzz(original: FuzzSource) -> Corpus { let mut ctx = Context::builder() .interner(original.interner) .insns_remaining(1 << 16) .build(); - ctx.eval(&original.source) + match ctx.eval(&original.source) { + Ok(_) => Corpus::Keep, + Err(e) => match e.try_native(&mut ctx) { + Ok(e) => { + if matches!(e.kind, JsNativeErrorKind::NoInstructionsRemain) { + // most interesting loops will initially cause an infinite loop + // this is okay as long as it is explicitly handled + Corpus::Keep + } else { + Corpus::Reject + } + } + Err(_) => Corpus::Reject, + }, + } } -fuzz_target!(|original: FuzzSource| -> Corpus { - if do_fuzz(original).is_ok() { - Corpus::Keep - } else { - Corpus::Reject - } -}); +fuzz_target!(|original: FuzzSource| -> Corpus { do_fuzz(original) });