Skip to content

Commit

Permalink
add docs + module loading
Browse files Browse the repository at this point in the history
  • Loading branch information
addisoncrump committed Aug 28, 2022
1 parent 176e317 commit a033c3b
Show file tree
Hide file tree
Showing 10 changed files with 221 additions and 75 deletions.
5 changes: 4 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -48,4 +48,7 @@ __pycache__
*atomic_file_testfile*
**/libxml2
**/corpus_discovered
**/libxml2-*.tar.gz
**/libxml2-*.tar.gz

# JS dependency files
node_modules/
7 changes: 4 additions & 3 deletions fuzzers/baby_fuzzer_v8/js/target.js
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
export default function(data) {
if (data.length > 1 && data[0] === 'A'.charCodeAt(0)) {
let array = new Uint8Array(data);
if (array.length > 1 && array[0] === 'A'.charCodeAt(0)) {
console.log("howdy")
if (data.length > 2 && data[1] === 'B'.charCodeAt(0)) {
if (array.length > 2 && array[1] === 'B'.charCodeAt(0)) {
console.log("kachowdy")
if (data.length > 2 && data[2] === 'C'.charCodeAt(0)) {
if (array.length > 2 && array[2] === 'C'.charCodeAt(0)) {
throw "crash!";
}
}
Expand Down
8 changes: 5 additions & 3 deletions libafl_v8/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,11 @@ edition = "2021"

[dependencies]
ahash = { version = "0.7", features = ["compile-time-rng"] }
# fetch deno directly because of git submodules
deno_core = { git = "https://github.com/denoland/deno.git", tag = "v1.25.0" }
deno_runtime = { git = "https://github.com/denoland/deno.git", tag = "v1.25.0" }
deno_ast = { version = "0.17.0", features = ["transpiling"] }
# fetch deno directly because of git deps; cannot use local submodule due to workspacing
deno_core = { git = "https://github.com/denoland/deno.git", tag = "v1.24.3" }
deno_runtime = { git = "https://github.com/denoland/deno.git", tag = "v1.24.3" }
import_map = "0.12.1"
libafl = { path = "../libafl", version = "0.8.1" }
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
Expand Down
20 changes: 20 additions & 0 deletions libafl_v8/LICENSE-DENO
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
MIT License

Copyright 2018-2022 the Deno authors

Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
the Software, and to permit persons to whom the Software is furnished to do so,
subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
41 changes: 13 additions & 28 deletions libafl_v8/src/executors.rs
Original file line number Diff line number Diff line change
@@ -1,33 +1,30 @@
//! Executors for JavaScript targets
//!
//! Currently, the only provided executor is `V8Executor`, but additional executors for other
//! environments may be added later for different JavaScript contexts.
use core::{
fmt,
fmt::{Debug, Formatter},
marker::PhantomData,
};
use std::{
borrow::{Borrow, BorrowMut},
cell::RefCell,
iter,
rc::Rc,
sync::Arc,
};
use std::{iter, sync::Arc};

use deno_core::{v8, JsRuntime, ModuleId, ModuleSpecifier};
use deno_core::{v8, ModuleId, ModuleSpecifier};
use deno_runtime::worker::MainWorker;
use libafl::{
executors::{Executor, ExitKind, HasObservers},
inputs::{BytesInput, HasBytesVec, Input},
inputs::Input,
observers::ObserversTuple,
state::State,
Error,
};
use tokio::{runtime, runtime::Runtime};
use v8::{
ArrayBuffer, Context, ContextScope, Function, HandleScope, Local, Script, TryCatch, Uint8Array,
Value,
};
use tokio::runtime::Runtime;
use v8::{Function, HandleScope, Local, TryCatch};

use crate::{v8::Global, Mutex};
use crate::{values::IntoJSValue, Mutex};

/// Executor which executes JavaScript using Deno and V8.
pub struct V8Executor<'rt, EM, I, OT, S, Z>
where
I: Input + IntoJSValue,
Expand Down Expand Up @@ -168,6 +165,7 @@ where
}
}

#[allow(dead_code)]
fn js_err_to_libafl(scope: &mut TryCatch<HandleScope>) -> Option<Error> {
if !scope.has_caught() {
None
Expand Down Expand Up @@ -229,16 +227,3 @@ fn js_err_to_libafl(scope: &mut TryCatch<HandleScope>) -> Option<Error> {
)))
}
}

pub trait IntoJSValue {
fn to_js_value<'s>(&self, scope: &mut HandleScope<'s>) -> Result<Local<'s, Value>, Error>;
}

impl IntoJSValue for BytesInput {
fn to_js_value<'s>(&self, scope: &mut HandleScope<'s>) -> Result<Local<'s, Value>, Error> {
let store = ArrayBuffer::new_backing_store_from_vec(Vec::from(self.bytes())).make_shared();
let buffer = ArrayBuffer::with_backing_store(scope, &store);
let array = Uint8Array::new(scope, buffer, 0, self.bytes().len()).unwrap();
Ok(array.into())
}
}
6 changes: 6 additions & 0 deletions libafl_v8/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
//! libafl executors, observers, and other necessary components for fuzzing JavaScript targets.
// lints directly from main libafl
#![allow(incomplete_features)]
// For `type_eq`
Expand Down Expand Up @@ -71,13 +73,17 @@ missing_docs,
#![allow(clippy::borrow_deref_ref)]

pub mod executors;
pub mod loader;
pub mod observers;
pub mod values;

pub use deno_core::{self, v8};
pub use deno_runtime;
pub use executors::*;
pub use loader::*;
pub use observers::*;
pub use tokio::{runtime, sync::Mutex};
pub use values::*;

pub(crate) fn forbid_deserialization<T>() -> T {
unimplemented!(
Expand Down
119 changes: 119 additions & 0 deletions libafl_v8/src/loader.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
//! Loader stubs to be used to resolve JavaScript dependencies
use std::{fs::read_to_string, path::Path, pin::Pin};

use deno_ast::{MediaType, ParseParams, SourceTextInfo};
use deno_core::{
anyhow::bail, futures::FutureExt, url::Url, ModuleLoader, ModuleSource, ModuleSourceFuture,
ModuleSpecifier, ModuleType,
};
use import_map::ImportMapWithDiagnostics;
use libafl::Error;

/// Loader which loads dependencies in vendored deno environments.
#[derive(Debug)]
pub struct VendoredLoader {
import_map: ImportMapWithDiagnostics,
}

impl VendoredLoader {
/// Create a new vendored loader with a path to the vendor/import_map.json.
pub fn new<P: AsRef<Path>>(map_path: P) -> Result<Self, Error> {
let json = read_to_string(map_path.as_ref())?;
let url = match Url::from_file_path(map_path.as_ref()) {
Ok(u) => u,
Err(_) => {
return Err(Error::illegal_argument(format!(
"Path was not found or was not absolute: {}",
map_path.as_ref().to_str().unwrap()
)))
}
};
if let Ok(import_map) = import_map::parse_from_json(&url, &json) {
Ok(Self { import_map })
} else {
Err(Error::illegal_state(
"Couldn't parse the provided import map",
))
}
}

fn read_file_specifier(module_specifier: ModuleSpecifier) -> Pin<Box<ModuleSourceFuture>> {
if let Ok(path) = module_specifier.to_file_path() {
async move {
// Section surrounded by SNIP below is taken from: https://github.com/denoland/deno/blob/94d369ebc65a55bd9fbf378a765c8ed88a4efe2c/core/examples/ts_module_loader.rs#L51-L88
// License text available at: ../LICENSE-DENO
// Copyright 2018-2022 the Deno authors. All rights reserved. MIT license.
// ---- SNIP ----
let media_type = MediaType::from(&path);
let (module_type, should_transpile) = match MediaType::from(&path) {
MediaType::JavaScript | MediaType::Mjs | MediaType::Cjs => {
(ModuleType::JavaScript, false)
}
MediaType::Jsx => (ModuleType::JavaScript, true),
MediaType::TypeScript
| MediaType::Mts
| MediaType::Cts
| MediaType::Dts
| MediaType::Dmts
| MediaType::Dcts
| MediaType::Tsx => (ModuleType::JavaScript, true),
MediaType::Json => (ModuleType::Json, false),
_ => bail!("Unknown extension {:?}", path.extension()),
};

let code = read_to_string(&path)?;
let code = if should_transpile {
let parsed = deno_ast::parse_module(ParseParams {
specifier: module_specifier.to_string(),
text_info: SourceTextInfo::from_string(code),
media_type,
capture_tokens: false,
scope_analysis: false,
maybe_syntax: None,
})?;
parsed.transpile(&Default::default())?.text
} else {
code
};
let module = ModuleSource {
code: code.into_bytes().into_boxed_slice(),
module_type,
module_url_specified: module_specifier.to_string(),
module_url_found: module_specifier.to_string(),
};
Ok(module)
// ---- SNIP ----
}
.boxed_local()
} else {
unimplemented!("File URL couldn't be parsed")
}
}
}

impl ModuleLoader for VendoredLoader {
fn resolve(
&self,
specifier: &str,
referrer: &str,
_is_main: bool,
) -> Result<ModuleSpecifier, deno_core::anyhow::Error> {
let referrer = deno_core::resolve_url_or_path(referrer)?;
Ok(self.import_map.import_map.resolve(specifier, &referrer)?)
}

fn load(
&self,
module_specifier: &ModuleSpecifier,
_maybe_referrer: Option<ModuleSpecifier>,
_is_dyn_import: bool,
) -> Pin<Box<ModuleSourceFuture>> {
let module_specifier = module_specifier.clone();
if module_specifier.scheme() == "file" {
Self::read_file_specifier(module_specifier)
} else {
unimplemented!("Not attempting to resolve non-file module specifiers")
}
}
}
34 changes: 9 additions & 25 deletions libafl_v8/src/observers/json_types.rs
Original file line number Diff line number Diff line change
@@ -1,28 +1,12 @@
// Taken from: https://github.com/denoland/deno/blob/e96933bc163fd81a276cbc169b17f76724a5ac33/cli/tools/coverage/json_types.rs
//
// Full license text:
//
// MIT License
//
// Copyright 2018-2022 the Deno authors
//
// Permission is hereby granted, free of charge, to any person obtaining a copy of
// this software and associated documentation files (the "Software"), to deal in
// the Software without restriction, including without limitation the rights to
// use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
// the Software, and to permit persons to whom the Software is furnished to do so,
// subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
//
//! Structs which are used to interact with the Inspector API
//!
//! Source is unmodified from original. Refer to: https://chromedevtools.github.io/devtools-protocol/
//!
//! Taken from: https://github.com/denoland/deno/blob/e96933bc163fd81a276cbc169b17f76724a5ac33/cli/tools/coverage/json_types.rs
#![allow(missing_docs)]

// License text available at: ../../LICENSE-DENO
// Copyright 2018-2022 the Deno authors. All rights reserved. MIT license.

use serde::{Deserialize, Serialize};
Expand Down
34 changes: 19 additions & 15 deletions libafl_v8/src/observers/mod.rs
Original file line number Diff line number Diff line change
@@ -1,24 +1,20 @@
//! Observers for JavaScript targets
mod json_types;

use core::{
fmt::{Debug, Formatter},
slice::Iter,
};
use std::{
borrow::BorrowMut,
cell::RefCell,
collections::{hash_map::Entry, HashMap},
hash::{Hash, Hasher},
rc::Rc,
sync::{Arc, Mutex as StdMutex},
sync::Arc,
};

use ahash::{AHashMap, AHasher};
use deno_core::{
futures::TryFutureExt, serde_json::Value, JsRuntime, JsRuntimeInspector, LocalInspectorSession,
};
use ahash::AHasher;
use deno_core::LocalInspectorSession;
use deno_runtime::worker::MainWorker;
use json_types::CoverageRange;
pub use json_types::{StartPreciseCoverageParameters, TakePreciseCoverageReturnObject};
use libafl::{
bolts::{AsIter, AsMutSlice, HasLen},
Expand All @@ -27,11 +23,8 @@ use libafl::{
prelude::Named,
Error,
};
use serde::{de::DeserializeOwned, Deserialize, Deserializer, Serialize, Serializer};
use tokio::{
runtime::Runtime,
sync::{oneshot::channel, Mutex},
};
use serde::{Deserialize, Serialize};
use tokio::{runtime::Runtime, sync::Mutex};

use super::forbid_deserialization;

Expand Down Expand Up @@ -123,6 +116,7 @@ impl JSCoverageMapper {
}
}

/// Observer which inspects JavaScript coverage at either a block or function level
#[derive(Serialize, Deserialize)]
pub struct JSMapObserver<'rt> {
initial: u8,
Expand All @@ -140,6 +134,9 @@ pub struct JSMapObserver<'rt> {
}

impl<'rt> JSMapObserver<'rt> {
/// Create the observer with the provided name to use the provided asynchronous runtime and JS
/// worker to push inspector data. If you don't know what kind of coverage you want, use this
/// constructor.
pub fn new(
name: &str,
rt: &'rt Runtime,
Expand All @@ -157,6 +154,8 @@ impl<'rt> JSMapObserver<'rt> {
)
}

/// Create the observer with the provided name to use the provided asynchronous runtime, JS
/// worker to push inspector data, and the parameters with which coverage is collected.
pub fn new_with_parameters(
name: &str,
rt: &'rt Runtime,
Expand Down Expand Up @@ -237,7 +236,12 @@ impl<'rt, I, S> Observer<I, S> for JSMapObserver<'rt> {
Ok(())
}

fn post_exec(&mut self, _state: &mut S, _input: &I, exit_kind: &ExitKind) -> Result<(), Error> {
fn post_exec(
&mut self,
_state: &mut S,
_input: &I,
_exit_kind: &ExitKind,
) -> Result<(), Error> {
let session = self.inspector.clone();
let copy = self.worker.clone();
let coverage = self.rt.block_on(async {
Expand Down
Loading

0 comments on commit a033c3b

Please sign in to comment.