summaryrefslogtreecommitdiffstats
path: root/js/src/fuzz-tests
diff options
context:
space:
mode:
Diffstat (limited to 'js/src/fuzz-tests')
-rw-r--r--js/src/fuzz-tests/README31
-rw-r--r--js/src/fuzz-tests/differential-parsing.js148
-rw-r--r--js/src/fuzz-tests/gluesmith/Cargo.toml10
-rw-r--r--js/src/fuzz-tests/gluesmith/moz.build15
-rw-r--r--js/src/fuzz-tests/gluesmith/src/lib.rs75
-rw-r--r--js/src/fuzz-tests/moz.build45
-rw-r--r--js/src/fuzz-tests/parsing-evaluate.js83
-rw-r--r--js/src/fuzz-tests/testExample.cpp61
-rw-r--r--js/src/fuzz-tests/testRegExp.cpp112
-rw-r--r--js/src/fuzz-tests/testStructuredCloneReader.cpp84
-rw-r--r--js/src/fuzz-tests/testWasm.cpp557
-rw-r--r--js/src/fuzz-tests/tests.cpp121
-rw-r--r--js/src/fuzz-tests/tests.h19
-rw-r--r--js/src/fuzz-tests/util/sanitize.js100
14 files changed, 1461 insertions, 0 deletions
diff --git a/js/src/fuzz-tests/README b/js/src/fuzz-tests/README
new file mode 100644
index 0000000000..2c98c1ffa2
--- /dev/null
+++ b/js/src/fuzz-tests/README
@@ -0,0 +1,31 @@
+# JS Fuzzing Interface
+
+This directory contains fuzzing targets that implement the unified fuzzing
+interface to be used with libFuzzer or AFL.
+
+## Building the fuzzing targets
+
+To include this directory in your JS build, you need to build with Clang
+and the --enable-fuzzing flag enabled. The build system will automatically
+detect if you are building with afl-clang-fast for AFL or regular Clang
+for libFuzzer.
+
+## Running a fuzzing target
+
+To run a particular target with libFuzzer, use:
+
+ cd $OBJDIR/dist/bin
+ FUZZER=YourTargetName ./fuzz-tests
+
+To run with AFL, use something like
+
+ cd $OBJDIR/dist/bin
+ FUZZER=YourTargetName MOZ_FUZZ_TESTFILE=input \
+ afl-fuzz <regular AFL options> -f input ./fuzz-tests
+
+
+## Writing a fuzzing target
+
+1. Check testExample.cpp for a target skeleton with comments.
+
+2. Add your own .cpp file to UNIFIED_SOURCES in moz.build
diff --git a/js/src/fuzz-tests/differential-parsing.js b/js/src/fuzz-tests/differential-parsing.js
new file mode 100644
index 0000000000..1a61175721
--- /dev/null
+++ b/js/src/fuzz-tests/differential-parsing.js
@@ -0,0 +1,148 @@
+/* -*- Mode: javascript; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// This file is used to detect and find cases where the Visage's parser is
+// accepting more inputs than SpiderMonkey's parser.
+//
+// 1. Find new cases:
+//
+// To find new cases, we have to build with libFuzzer. The JS Shell can easily
+// be built with libFuzzer by adding --enable-fuzzing to the configure script
+// command line.
+//
+// Create a directory, and copy all test cases from the JS shell to this
+// directory:
+//
+// $ mkdir fuzzer-input
+// $ cd fuzzer-input
+// $ find ../ -name \*.js -print0 | xargs -I '{}' -0 -n1 cp '{}' $(pwd)
+//
+// Once the JS Shell is built, set the FUZZER environment variable to this
+// script location.
+//
+// $ FUZZER="./fuzz-tests/differential-parsing.js" build.dir/dist/bin/js -- \
+// -use_value_profile=1 -print_pcs=1 -timeout=5 -max_len=32 -only_ascii=1 \
+// ./fuzzer-input
+//
+// 2. Test a crashing test case:
+//
+// Once a new crashing test case is found, this script can be used to
+// reproduce the crashing conditions.
+//
+// To do so, you need a JS Shell and to load this script and use the testFile
+// function with the location of the crashing file.
+//
+// $ build.dir/dist/bin/js
+// js> load("./fuzz-tests/differential-parsing.js");
+// js> testFile("./crash-42");
+// Parse Script C++: fail
+// Parse Module C++: fail
+// Parse Script Rust: succeed
+// Parse Module Rust: fail
+// Hit MOZ_CRASH(Rust accept more than C++)
+//
+
+/* global crash, os, parse, timeout */
+
+// This global will hold the current fuzzing buffer for each iteration.
+var fuzzBuf;
+
+function timed(sec, f) {
+ // If the function `f` takes more than 3 seconds, then the evaluation ends
+ // prematurely and returns in libFuzzer handler without considering this
+ // test case as interesting.
+ timeout(sec, function() {
+ return false;
+ });
+ f();
+
+ // Remove the timeout handler, to not kill future executions.
+ timeout(-1);
+}
+
+var parseScriptCpp = { module: false, smoosh: false };
+var parseScriptRust = { module: false, smoosh: true };
+var parseModuleRust = { module: true, smoosh: true };
+var parseModuleCpp = { module: true, smoosh: false };
+function test(code, verbose = false) {
+ var isScriptCpp = false,
+ isModuleCpp = false,
+ isScriptRust = false,
+ isModuleRust = false;
+ try {
+ parse(code, parseScriptCpp);
+ isScriptCpp = true;
+ if (verbose) {
+ console.log("Parse Script C++: succeed");
+ }
+ } catch (exc) {
+ if (verbose) {
+ console.log("Parse Script C++: fail");
+ }
+ }
+ try {
+ parse(code, parseModuleCpp);
+ isModuleCpp = true;
+ if (verbose) {
+ console.log("Parse Module C++: succeed");
+ }
+ } catch (exc) {
+ if (verbose) {
+ console.log("Parse Module C++: fail");
+ }
+ }
+ try {
+ parse(code, parseScriptRust);
+ isScriptRust = true;
+ if (verbose) {
+ console.log("Parse Script Rust: succeed");
+ }
+ } catch (exc) {
+ if (verbose) {
+ console.log("Parse Script Rust: fail");
+ }
+ }
+ try {
+ parse(code, parseModuleRust);
+ isModuleRust = true;
+ if (verbose) {
+ console.log("Parse Module Rust: succeed");
+ }
+ } catch (exc) {
+ if (verbose) {
+ console.log("Parse Module Rust: fail");
+ }
+ }
+ if ((isScriptRust && !isScriptCpp) || (isModuleRust && !isModuleCpp)) {
+ crash("Rust accept more than C++");
+ }
+}
+
+function JSFuzzIterate() {
+ // This function is called per iteration. You must ensure that:
+ //
+ // 1) Each of your actions/decisions is only based on fuzzBuf,
+ // in particular not on Math.random(), Date/Time or other
+ // external inputs.
+ //
+ // 2) Your actions should be deterministic. The same fuzzBuf
+ // should always lead to the same set of actions/decisions.
+ //
+ // 3) You can modify the global where needed, but ensure that
+ // each iteration is isolated from one another by cleaning
+ // any modifications to the global after each iteration.
+ // In particular, iterations must not depend on or influence
+ // each other in any way (see also 1)).
+ //
+ // 4) You must catch all exceptions.
+ let code = String.fromCharCode(...fuzzBuf);
+ timed(3, _ => test(code));
+ return 0;
+}
+
+function testFile(file) {
+ let content = os.file.readFile(file);
+ test(content, true);
+}
diff --git a/js/src/fuzz-tests/gluesmith/Cargo.toml b/js/src/fuzz-tests/gluesmith/Cargo.toml
new file mode 100644
index 0000000000..42c8cec4f2
--- /dev/null
+++ b/js/src/fuzz-tests/gluesmith/Cargo.toml
@@ -0,0 +1,10 @@
+[package]
+name = "gluesmith"
+version = "0.1.0"
+authors = ["Christian Holler"]
+license = "MPL-2.0"
+
+[dependencies]
+wasm-smith = "0.12.5"
+arbitrary = { version = "1.0.0", features = ["derive"] }
+libc = "0.2"
diff --git a/js/src/fuzz-tests/gluesmith/moz.build b/js/src/fuzz-tests/gluesmith/moz.build
new file mode 100644
index 0000000000..d75c4c18ba
--- /dev/null
+++ b/js/src/fuzz-tests/gluesmith/moz.build
@@ -0,0 +1,15 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+FINAL_LIBRARY = "js"
+
+# Includes should be relative to parent path
+LOCAL_INCLUDES += ["!../..", "../.."]
+
+include("../../js-config.mozbuild")
+include("../../js-cxxflags.mozbuild")
+
+DIRS += ["../../rust"]
diff --git a/js/src/fuzz-tests/gluesmith/src/lib.rs b/js/src/fuzz-tests/gluesmith/src/lib.rs
new file mode 100644
index 0000000000..2efb678a03
--- /dev/null
+++ b/js/src/fuzz-tests/gluesmith/src/lib.rs
@@ -0,0 +1,75 @@
+/* Copyright 2021 Mozilla Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+extern crate arbitrary;
+extern crate wasm_smith;
+
+use arbitrary::{Arbitrary, Unstructured};
+use wasm_smith::{Config, Module};
+
+use std::ptr;
+
+// A custom configuration to enable all experimental features that we have
+// some support for.
+#[derive(Arbitrary, Debug)]
+struct SpiderMonkeyConfig;
+
+impl Config for SpiderMonkeyConfig {
+ fn bulk_memory_enabled(&self) -> bool {
+ true
+ }
+ fn reference_types_enabled(&self) -> bool {
+ true
+ }
+ fn exceptions_enabled(&self) -> bool {
+ true
+ }
+ fn memory64_enabled(&self) -> bool {
+ true
+ }
+ fn simd_enabled(&self) -> bool {
+ true
+ }
+}
+
+#[no_mangle]
+pub unsafe extern "C" fn gluesmith(
+ data: *mut u8,
+ len: usize,
+ out: *mut u8,
+ maxlen: usize,
+) -> usize {
+ let buf: &[u8] = std::slice::from_raw_parts(data, len);
+
+ let mut u = Unstructured::new(buf);
+
+ let module = match Module::new(SpiderMonkeyConfig {}, &mut u) {
+ Ok(m) => m,
+ Err(_e) => return 0,
+ };
+
+ let wasm_bytes = module.to_bytes();
+
+ let src_len = wasm_bytes.len();
+
+ if src_len > maxlen {
+ return 0;
+ }
+
+ let src_ptr = wasm_bytes.as_ptr();
+ ptr::copy_nonoverlapping(src_ptr, out, src_len);
+
+ return src_len;
+}
diff --git a/js/src/fuzz-tests/moz.build b/js/src/fuzz-tests/moz.build
new file mode 100644
index 0000000000..ccec998f0b
--- /dev/null
+++ b/js/src/fuzz-tests/moz.build
@@ -0,0 +1,45 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+GeckoProgram("fuzz-tests", linkage=None)
+
+include("../js-cxxflags.mozbuild")
+include("../js-standalone.mozbuild")
+
+UNIFIED_SOURCES += [
+ "testExample.cpp",
+ "testRegExp.cpp",
+ "tests.cpp",
+ "testStructuredCloneReader.cpp",
+ "testWasm.cpp",
+]
+
+DEFINES["EXPORT_JS_API"] = True
+
+LOCAL_INCLUDES += [
+ "!..",
+ "..",
+]
+
+if CONFIG["FUZZING"]:
+ USE_LIBS += [
+ "static:fuzzer-registry",
+ ]
+
+ # Add libFuzzer configuration directives
+ include("/tools/fuzzing/libfuzzer-config.mozbuild")
+
+if CONFIG["FUZZING_INTERFACES"]:
+ USE_LIBS += [
+ "static:fuzzer",
+ ]
+
+USE_LIBS += [
+ "static:js",
+ "static:jsrust",
+]
+
+DEFINES["topsrcdir"] = "%s/js/src" % TOPSRCDIR
diff --git a/js/src/fuzz-tests/parsing-evaluate.js b/js/src/fuzz-tests/parsing-evaluate.js
new file mode 100644
index 0000000000..1714e051a9
--- /dev/null
+++ b/js/src/fuzz-tests/parsing-evaluate.js
@@ -0,0 +1,83 @@
+/* -*- Mode: javascript; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// This fuzzing target aims to stress the SpiderMonkey parser. However, for
+// this purpose, it does *not* use `parse()` because some past bugs in the
+// parser could only be triggered in the runtime later. Instead, we use
+// the `evaluate` function which parses and runs the code. This brings in
+// other problems like timeouts and permanent side-effects. We try to minimize
+// the amount of permanent side-effects from running the code by running it
+// in a fresh global for each iteration. We also use a special function
+// called `sanitizeGlobal` to remove any harmful shell functions from the
+// global prior to running. Many of these shell functions would otherwise
+// have permanent side-effects of some sort or be disruptive to testing like
+// increasing the amount of timeouts or leak memory. Finally, the target also
+// tries to catch timeouts locally and signal back any timeouts by returning 1
+// from the iteration function.
+
+// This global will hold the current fuzzing buffer for each iteration.
+var fuzzBuf;
+
+loadRelativeToScript("util/sanitize.js");
+
+deterministicgc(true);
+
+// Set a default value for timeouts to 1 second, but allow this to
+// be set on the command line as well using -e fuzzTimeout=VAL.
+if (typeof fuzzTimeout === "undefined") {
+ fuzzTimeout = 1;
+}
+
+function JSFuzzIterate() {
+ try {
+ let code = String.fromCharCode(...fuzzBuf);
+ let result = null;
+
+ // Create a new global and sanitize it such that its potentially permanent
+ // side-effects are reduced to a minimum.
+ let global = newGlobal();
+ sanitizeGlobal(global);
+
+ // Work around memory leaks when the hook is not set
+ evaluate(`
+ setModuleResolveHook(function(module, specifier) {
+ throw "Module '" + specifier + "' not found";
+ });
+ setModuleResolveHook = function() {};
+ `, { global: global, catchTermination: true });
+
+ // Start a timer and set a timeout in addition
+ let lfStart = monotonicNow();
+ timeout(fuzzTimeout, function() { return false; });
+
+ try {
+ result = evaluate(code, { global: global, catchTermination: true });
+ } catch(exc) {
+ print(exc);
+ }
+
+ timeout(-1);
+ let lfStop = monotonicNow();
+
+ // Reset some things that could have been altered by the code we ran
+ gczeal(0);
+ schedulegc(0);
+ setGCCallback({ action: "majorGC" });
+ clearSavedFrames();
+
+ // If we either ended terminating the script, or we took longer than
+ // the timeout set (but timeout didn't kick in), then we return 1 to
+ // signal libFuzzer that the sample just be abandoned.
+ if (result === "terminated" || (lfStop - lfStart > (fuzzTimeout * 1000 + 200))) {
+ return 1;
+ }
+
+ return 0;
+ } catch(exc) {
+ print("Caught toplevel exception: " + exc);
+ }
+
+ return 1;
+}
diff --git a/js/src/fuzz-tests/testExample.cpp b/js/src/fuzz-tests/testExample.cpp
new file mode 100644
index 0000000000..91ee827d3d
--- /dev/null
+++ b/js/src/fuzz-tests/testExample.cpp
@@ -0,0 +1,61 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * vim: set ts=8 sts=2 et sw=2 tw=80:
+ */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "mozilla/ScopeExit.h"
+
+#include "jsapi.h"
+
+#include "fuzz-tests/tests.h"
+#include "vm/Interpreter.h"
+
+#include "vm/JSContext-inl.h"
+
+using namespace JS;
+using namespace js;
+
+extern JS::PersistentRootedObject gGlobal;
+extern JSContext* gCx;
+
+static int testExampleInit(int* argc, char*** argv) {
+ /* This function is called once at startup. You can use it to e.g. read
+ environment variables to initialize additional options you might need.
+ Note that `gCx` and `gGlobal` are pre-initialized by the harness.
+ */
+ return 0;
+}
+
+static int testExampleFuzz(const uint8_t* buf, size_t size) {
+ /* If your code directly or indirectly allocates GC memory, then it makes
+ sense to attempt and collect that after every iteration. This should detect
+ GC issues as soon as possible (right after your iteration), rather than
+ later when your code happens to trigger GC coincidentially. You can of
+ course disable this code
+ if it is not required in your use case, which will speed up fuzzing. */
+ auto gcGuard = mozilla::MakeScopeExit([&] {
+ JS::PrepareForFullGC(gCx);
+ JS::NonIncrementalGC(gCx, JS::GCOptions::Normal, JS::GCReason::API);
+ });
+
+ /* Add code here that processes the given buffer.
+ While doing so, you need to follow these rules:
+
+ 1. Do not modify or free the buffer. Make a copy if necessary.
+ 2. This function must always return 0.
+ 3. Do not crash or abort unless the condition constitutes a bug.
+ 4. You may use the `gGlobal` and `gCx` variables, they are pre-initialized.
+ 5. Try to keep the effects of this function contained, such that future
+ calls to this function are not affected. Otherwise you end up with
+ non-reproducible testcases and coverage measurements will be incorrect.
+ */
+
+ return 0;
+}
+
+MOZ_FUZZING_INTERFACE_RAW(testExampleInit, /* init function */
+ testExampleFuzz, /* fuzzing function */
+ Example /* module name */
+);
diff --git a/js/src/fuzz-tests/testRegExp.cpp b/js/src/fuzz-tests/testRegExp.cpp
new file mode 100644
index 0000000000..5c71eda873
--- /dev/null
+++ b/js/src/fuzz-tests/testRegExp.cpp
@@ -0,0 +1,112 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * vim: set ts=8 sts=2 et sw=2 tw=80:
+ */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "mozilla/ScopeExit.h"
+
+#include "jsapi.h"
+
+#include "fuzz-tests/tests.h"
+#include "irregexp/RegExpAPI.h"
+#include "vm/Interpreter.h"
+#include "vm/JSAtom.h"
+#include "vm/MatchPairs.h"
+
+#include "vm/JSContext-inl.h"
+
+using namespace JS;
+using namespace js;
+
+extern JS::PersistentRootedObject gGlobal;
+extern JSContext* gCx;
+
+static int testRegExpInit(int* argc, char*** argv) { return 0; }
+
+static int testRegExpFuzz(const uint8_t* buf, size_t size) {
+ auto gcGuard = mozilla::MakeScopeExit([&] {
+ JS::PrepareForFullGC(gCx);
+ JS::NonIncrementalGC(gCx, JS::GCOptions::Normal, JS::GCReason::API);
+ });
+
+ const uint32_t HEADER_LEN = 2;
+ if (size <= HEADER_LEN) {
+ return 0;
+ }
+
+ uint8_t rawFlags = buf[0];
+ int32_t patternLength = buf[1];
+
+ const uint32_t startIndex = 0;
+
+ RegExpFlags flags(rawFlags & RegExpFlag::AllFlags);
+
+ int32_t inputLength = size - HEADER_LEN - patternLength;
+
+ const char* patternChars = reinterpret_cast<const char*>(buf + HEADER_LEN);
+
+ const char* inputChars;
+ if (inputLength < 0) {
+ patternLength = size - HEADER_LEN;
+
+ bool useUnicodeInput = (buf[1] & 1) == 0;
+ inputChars = useUnicodeInput ? "Привет мир" : "Hello\nworld!";
+ inputLength = strlen(inputChars);
+ } else {
+ inputChars = patternChars + patternLength;
+ }
+
+ Rooted<JSAtom*> pattern(gCx,
+ AtomizeUTF8Chars(gCx, patternChars, patternLength));
+ if (!pattern) {
+ ReportOutOfMemory(gCx);
+ return 0;
+ }
+ Rooted<JSAtom*> input(gCx, AtomizeUTF8Chars(gCx, inputChars, inputLength));
+ if (!input) {
+ ReportOutOfMemory(gCx);
+ return 0;
+ }
+
+ VectorMatchPairs interpretedMatches;
+ VectorMatchPairs compiledMatches;
+
+ RegExpRunStatus iStatus = irregexp::ExecuteForFuzzing(
+ gCx, pattern, input, flags, startIndex, &interpretedMatches,
+ RegExpShared::CodeKind::Bytecode);
+ if (iStatus == RegExpRunStatus_Error) {
+ if (gCx->isThrowingOverRecursed()) {
+ return 0;
+ }
+ gCx->clearPendingException();
+ }
+ RegExpRunStatus cStatus = irregexp::ExecuteForFuzzing(
+ gCx, pattern, input, flags, startIndex, &compiledMatches,
+ RegExpShared::CodeKind::Jitcode);
+ if (cStatus == RegExpRunStatus_Error) {
+ if (gCx->isThrowingOverRecursed()) {
+ return 0;
+ }
+ gCx->clearPendingException();
+ }
+
+ // Use release asserts to enable fuzzing on non-debug builds.
+ MOZ_RELEASE_ASSERT(iStatus == cStatus);
+ if (iStatus == RegExpRunStatus_Success) {
+ MOZ_RELEASE_ASSERT(interpretedMatches.pairCount() ==
+ compiledMatches.pairCount());
+ for (uint32_t i = 0; i < interpretedMatches.pairCount(); i++) {
+ MOZ_RELEASE_ASSERT(
+ interpretedMatches[i].start == compiledMatches[i].start &&
+ interpretedMatches[i].limit == compiledMatches[i].limit);
+ }
+ }
+ return 0;
+}
+
+MOZ_FUZZING_INTERFACE_RAW(testRegExpInit, /* init function */
+ testRegExpFuzz, /* fuzzing function */
+ RegExp /* module name */
+);
diff --git a/js/src/fuzz-tests/testStructuredCloneReader.cpp b/js/src/fuzz-tests/testStructuredCloneReader.cpp
new file mode 100644
index 0000000000..6b70d8bc52
--- /dev/null
+++ b/js/src/fuzz-tests/testStructuredCloneReader.cpp
@@ -0,0 +1,84 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * vim: set ts=8 sts=2 et sw=2 tw=80:
+ */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "mozilla/ScopeExit.h"
+
+#include "jsapi.h"
+
+#include "fuzz-tests/tests.h"
+#include "js/StructuredClone.h"
+#include "vm/Interpreter.h"
+
+#include "vm/JSContext-inl.h"
+
+using namespace js;
+
+// These are defined and pre-initialized by the harness (in tests.cpp).
+extern JS::PersistentRootedObject gGlobal;
+extern JSContext* gCx;
+
+static int testStructuredCloneReaderInit(int* argc, char*** argv) { return 0; }
+
+static int testStructuredCloneReaderFuzz(const uint8_t* buf, size_t size) {
+ auto gcGuard = mozilla::MakeScopeExit([&] {
+ JS::PrepareForFullGC(gCx);
+ JS::NonIncrementalGC(gCx, JS::GCOptions::Normal, JS::GCReason::API);
+ });
+
+ if (!size) return 0;
+
+ // Make sure to pad the buffer to a multiple of kSegmentAlignment
+ const size_t kSegmentAlignment = 8;
+ size_t buf_size = RoundUp(size, kSegmentAlignment);
+
+ JS::StructuredCloneScope scope = JS::StructuredCloneScope::DifferentProcess;
+
+ auto clonebuf = MakeUnique<JSStructuredCloneData>(scope);
+ if (!clonebuf || !clonebuf->Init(buf_size)) {
+ ReportOutOfMemory(gCx);
+ return 0;
+ }
+
+ // Copy buffer then pad with zeroes.
+ if (!clonebuf->AppendBytes((const char*)buf, size)) {
+ ReportOutOfMemory(gCx);
+ return 0;
+ }
+ char padding[kSegmentAlignment] = {0};
+ if (!clonebuf->AppendBytes(padding, buf_size - size)) {
+ ReportOutOfMemory(gCx);
+ return 0;
+ }
+
+ JS::CloneDataPolicy policy;
+ RootedValue deserialized(gCx);
+ if (!JS_ReadStructuredClone(gCx, *clonebuf, JS_STRUCTURED_CLONE_VERSION,
+ scope, &deserialized, policy, nullptr, nullptr)) {
+ return 0;
+ }
+
+ /* If we succeeded in deserializing, we should try to reserialize the data.
+ This has two main advantages:
+
+ 1) It tests parts of the serializer as well.
+ 2) The deserialized data is actually used, making it more likely to detect
+ further memory-related problems.
+
+ Tests show that this also doesn't cause a serious performance penalty.
+ */
+ mozilla::Maybe<JSAutoStructuredCloneBuffer> clonebufOut;
+
+ clonebufOut.emplace(scope, nullptr, nullptr);
+ if (!clonebufOut->write(gCx, deserialized, UndefinedHandleValue, policy)) {
+ return 0;
+ }
+
+ return 0;
+}
+
+MOZ_FUZZING_INTERFACE_RAW(testStructuredCloneReaderInit,
+ testStructuredCloneReaderFuzz, StructuredCloneReader);
diff --git a/js/src/fuzz-tests/testWasm.cpp b/js/src/fuzz-tests/testWasm.cpp
new file mode 100644
index 0000000000..25c3ea09e3
--- /dev/null
+++ b/js/src/fuzz-tests/testWasm.cpp
@@ -0,0 +1,557 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "mozilla/ScopeExit.h"
+
+#include "jsapi.h"
+#include "jspubtd.h"
+
+#include "fuzz-tests/tests.h"
+#include "js/CallAndConstruct.h"
+#include "js/PropertyAndElement.h" // JS_Enumerate, JS_GetProperty, JS_GetPropertyById, JS_HasProperty, JS_SetProperty
+#include "vm/GlobalObject.h"
+#include "vm/Interpreter.h"
+#include "vm/TypedArrayObject.h"
+
+#include "wasm/WasmCompile.h"
+#include "wasm/WasmIonCompile.h"
+#include "wasm/WasmJS.h"
+#include "wasm/WasmTable.h"
+
+#include "vm/ArrayBufferObject-inl.h"
+#include "vm/JSContext-inl.h"
+
+using namespace js;
+using namespace js::wasm;
+
+// These are defined and pre-initialized by the harness (in tests.cpp).
+extern JS::PersistentRootedObject gGlobal;
+extern JSContext* gCx;
+
+static bool gIsWasmSmith = false;
+extern "C" {
+size_t gluesmith(uint8_t* data, size_t size, uint8_t* out, size_t maxsize);
+}
+
+static int testWasmInit(int* argc, char*** argv) {
+ if (!wasm::HasSupport(gCx) ||
+ !GlobalObject::getOrCreateConstructor(gCx, JSProto_WebAssembly)) {
+ MOZ_CRASH("Failed to initialize wasm support");
+ }
+
+ return 0;
+}
+
+static int testWasmSmithInit(int* argc, char*** argv) {
+ gIsWasmSmith = true;
+ return testWasmInit(argc, argv);
+}
+
+static bool emptyNativeFunction(JSContext* cx, unsigned argc, Value* vp) {
+ CallArgs args = CallArgsFromVp(argc, vp);
+ args.rval().setUndefined();
+ return true;
+}
+
+static bool callExportedFunc(HandleFunction func,
+ MutableHandleValue lastReturnVal) {
+ // TODO: We can specify a thisVal here.
+ RootedValue thisVal(gCx, UndefinedValue());
+ JS::RootedValueVector args(gCx);
+
+ if (!lastReturnVal.isNull() && !lastReturnVal.isUndefined() &&
+ !args.append(lastReturnVal)) {
+ return false;
+ }
+
+ RootedValue returnVal(gCx);
+ if (!Call(gCx, thisVal, func, args, &returnVal)) {
+ gCx->clearPendingException();
+ } else {
+ lastReturnVal.set(returnVal);
+ }
+
+ return true;
+}
+
+template <typename T>
+static bool assignImportKind(const Import& import, HandleObject obj,
+ HandleObject lastExportsObj,
+ JS::Handle<JS::IdVector> lastExportIds,
+ size_t* currentExportId, size_t exportsLength,
+ HandleValue defaultValue) {
+ RootedId fieldName(gCx);
+ if (!import.field.toPropertyKey(gCx, &fieldName)) {
+ return false;
+ }
+ bool assigned = false;
+ while (*currentExportId < exportsLength) {
+ RootedValue propVal(gCx);
+ if (!JS_GetPropertyById(gCx, lastExportsObj,
+ lastExportIds[*currentExportId], &propVal)) {
+ return false;
+ }
+
+ (*currentExportId)++;
+
+ if (propVal.isObject() && propVal.toObject().is<T>()) {
+ if (!JS_SetPropertyById(gCx, obj, fieldName, propVal)) {
+ return false;
+ }
+
+ assigned = true;
+ break;
+ }
+ }
+ if (!assigned) {
+ if (!JS_SetPropertyById(gCx, obj, fieldName, defaultValue)) {
+ return false;
+ }
+ }
+ return true;
+}
+
+static bool FuzzerBuildId(JS::BuildIdCharVector* buildId) {
+ const char buildid[] = "testWasmFuzz";
+ return buildId->append(buildid, sizeof(buildid));
+}
+
+static int testWasmFuzz(const uint8_t* buf, size_t size) {
+ auto gcGuard = mozilla::MakeScopeExit([&] {
+ JS::PrepareForFullGC(gCx);
+ JS::NonIncrementalGC(gCx, JS::GCOptions::Normal, JS::GCReason::API);
+ });
+
+ JS::SetProcessBuildIdOp(FuzzerBuildId);
+
+ const size_t MINIMUM_MODULE_SIZE = 8;
+
+ // The smallest valid wasm module is 8 bytes and we need 1 byte for size
+ if (size < MINIMUM_MODULE_SIZE + 1) return 0;
+
+ size_t currentIndex = 0;
+
+ // Store the last non-empty exports object and its enumerated Ids here
+ RootedObject lastExportsObj(gCx);
+ JS::Rooted<JS::IdVector> lastExportIds(gCx, JS::IdVector(gCx));
+
+ // Store the last return value so we can pass it in as an argument during
+ // the next call (which can be on another module as well).
+ RootedValue lastReturnVal(gCx);
+
+ while (size - currentIndex >= MINIMUM_MODULE_SIZE + 1) {
+ // Ensure we have no lingering exceptions from previous modules
+ gCx->clearPendingException();
+
+ uint16_t moduleLen;
+ if (gIsWasmSmith) {
+ // Jump over the optByte. Unlike with the regular format, for
+ // wasm-smith we are fixing this and use byte 0 as opt-byte.
+ // Eventually this will also be changed for the regular format.
+ if (!currentIndex) {
+ currentIndex++;
+ }
+
+ // Caller ensures the structural soundness of the input here
+ moduleLen = *((uint16_t*)&buf[currentIndex]);
+ currentIndex += 2;
+ } else {
+ moduleLen = buf[currentIndex];
+ currentIndex++;
+ }
+
+ if (size - currentIndex < moduleLen) {
+ moduleLen = size - currentIndex;
+ }
+
+ if (moduleLen < MINIMUM_MODULE_SIZE) {
+ continue;
+ }
+
+ if (currentIndex == 1 || (gIsWasmSmith && currentIndex == 3)) {
+ // If this is the first module we are reading, we use the first
+ // few bytes to tweak some settings. These are fixed anyway and
+ // overwritten later on.
+ uint8_t optByte;
+ if (gIsWasmSmith) {
+ optByte = (uint8_t)buf[0];
+ } else {
+ optByte = (uint8_t)buf[currentIndex];
+ }
+
+ // Note that IonPlatformSupport() does not take into account whether
+ // the compiler supports particular features that may have been enabled.
+ bool enableWasmBaseline = ((optByte & 0xF0) == (1 << 7));
+ bool enableWasmOptimizing =
+ IonPlatformSupport() && ((optByte & 0xF0) == (1 << 6));
+ bool enableWasmAwaitTier2 =
+ (IonPlatformSupport()) && ((optByte & 0xF) == (1 << 3));
+
+ if (!enableWasmBaseline && !enableWasmOptimizing) {
+ // If nothing is selected explicitly, enable an optimizing compiler to
+ // test more platform specific JIT code. However, on some platforms,
+ // e.g. ARM64 on Windows, we do not have Ion available, so we need to
+ // switch to baseline instead.
+ if (IonPlatformSupport()) {
+ enableWasmOptimizing = true;
+ } else {
+ enableWasmBaseline = true;
+ }
+ }
+
+ if (enableWasmAwaitTier2) {
+ // Tier 2 needs Baseline + Optimizing
+ enableWasmBaseline = true;
+
+ if (!enableWasmOptimizing) {
+ enableWasmOptimizing = true;
+ }
+ }
+
+ JS::ContextOptionsRef(gCx)
+ .setWasmBaseline(enableWasmBaseline)
+ .setWasmIon(enableWasmOptimizing)
+ .setTestWasmAwaitTier2(enableWasmAwaitTier2);
+ }
+
+ // Expected header for a valid WebAssembly module
+ uint32_t magic_header = 0x6d736100;
+ uint32_t magic_version = 0x1;
+
+ if (gIsWasmSmith) {
+ // When using wasm-smith, magic values should already be there.
+ // Checking this to make sure the data passed is sane.
+ MOZ_RELEASE_ASSERT(*(uint32_t*)(&buf[currentIndex]) == magic_header,
+ "Magic header mismatch!");
+ MOZ_RELEASE_ASSERT(*(uint32_t*)(&buf[currentIndex + 4]) == magic_version,
+ "Magic version mismatch!");
+ }
+
+ // We just skip over the first 8 bytes now because we fill them
+ // with `magic_header` and `magic_version` anyway.
+ currentIndex += 8;
+ moduleLen -= 8;
+
+ Rooted<WasmInstanceObject*> instanceObj(gCx);
+
+ MutableBytes bytecode = gCx->new_<ShareableBytes>();
+ if (!bytecode || !bytecode->append((uint8_t*)&magic_header, 4) ||
+ !bytecode->append((uint8_t*)&magic_version, 4) ||
+ !bytecode->append(&buf[currentIndex], moduleLen)) {
+ return 0;
+ }
+
+ currentIndex += moduleLen;
+
+ ScriptedCaller scriptedCaller;
+ FeatureOptions options;
+ SharedCompileArgs compileArgs =
+ CompileArgs::buildAndReport(gCx, std::move(scriptedCaller), options);
+ if (!compileArgs) {
+ return 0;
+ }
+
+ UniqueChars error;
+ UniqueCharsVector warnings;
+ SharedModule module =
+ CompileBuffer(*compileArgs, *bytecode, &error, &warnings);
+ if (!module) {
+ // We should always have a valid module if we are using wasm-smith. Check
+ // that no error is reported, signalling an OOM.
+ MOZ_RELEASE_ASSERT(!gIsWasmSmith || !error);
+ continue;
+ }
+
+ // At this point we have a valid module and we should try to ensure
+ // that its import requirements are met for instantiation.
+ const ImportVector& importVec = module->imports();
+
+ // Empty native function used to fill in function import slots if we
+ // run out of functions exported by other modules.
+ JS::RootedFunction emptyFunction(gCx);
+ emptyFunction =
+ JS_NewFunction(gCx, emptyNativeFunction, 0, 0, "emptyFunction");
+
+ if (!emptyFunction) {
+ return 0;
+ }
+
+ RootedValue emptyFunctionValue(gCx, ObjectValue(*emptyFunction));
+ RootedValue nullValue(gCx, NullValue());
+
+ RootedObject importObj(gCx, JS_NewPlainObject(gCx));
+
+ if (!importObj) {
+ return 0;
+ }
+
+ size_t exportsLength = lastExportIds.length();
+ size_t currentFunctionExportId = 0;
+ size_t currentTableExportId = 0;
+ size_t currentMemoryExportId = 0;
+ size_t currentGlobalExportId = 0;
+ size_t currentTagExportId = 0;
+
+ for (const Import& import : importVec) {
+ RootedId moduleName(gCx);
+ if (!import.module.toPropertyKey(gCx, &moduleName)) {
+ return false;
+ }
+ RootedId fieldName(gCx);
+ if (!import.field.toPropertyKey(gCx, &fieldName)) {
+ return false;
+ }
+
+ // First try to get the namespace object, create one if this is the
+ // first time.
+ RootedValue v(gCx);
+ if (!JS_GetPropertyById(gCx, importObj, moduleName, &v) ||
+ !v.isObject()) {
+ // Insert empty object at importObj[moduleName]
+ RootedObject plainObj(gCx, JS_NewPlainObject(gCx));
+
+ if (!plainObj) {
+ return 0;
+ }
+
+ RootedValue plainVal(gCx, ObjectValue(*plainObj));
+ if (!JS_SetPropertyById(gCx, importObj, moduleName, plainVal)) {
+ return 0;
+ }
+
+ // Get the object we just inserted, store in v, ensure it is an
+ // object (no proxies or other magic at work).
+ if (!JS_GetPropertyById(gCx, importObj, moduleName, &v) ||
+ !v.isObject()) {
+ return 0;
+ }
+ }
+
+ RootedObject obj(gCx, &v.toObject());
+ bool found = false;
+ if (JS_HasPropertyById(gCx, obj, fieldName, &found) && !found) {
+ // Insert i-th export object that fits the type requirement
+ // at `v[fieldName]`.
+
+ switch (import.kind) {
+ case DefinitionKind::Function:
+ if (!assignImportKind<JSFunction>(
+ import, obj, lastExportsObj, lastExportIds,
+ &currentFunctionExportId, exportsLength,
+ emptyFunctionValue)) {
+ return 0;
+ }
+ break;
+
+ case DefinitionKind::Table:
+ // TODO: Pass a dummy defaultValue
+ if (!assignImportKind<WasmTableObject>(
+ import, obj, lastExportsObj, lastExportIds,
+ &currentTableExportId, exportsLength, nullValue)) {
+ return 0;
+ }
+ break;
+
+ case DefinitionKind::Memory:
+ // TODO: Pass a dummy defaultValue
+ if (!assignImportKind<WasmMemoryObject>(
+ import, obj, lastExportsObj, lastExportIds,
+ &currentMemoryExportId, exportsLength, nullValue)) {
+ return 0;
+ }
+ break;
+
+ case DefinitionKind::Global:
+ // TODO: Pass a dummy defaultValue
+ if (!assignImportKind<WasmGlobalObject>(
+ import, obj, lastExportsObj, lastExportIds,
+ &currentGlobalExportId, exportsLength, nullValue)) {
+ return 0;
+ }
+ break;
+
+ case DefinitionKind::Tag:
+ // TODO: Pass a dummy defaultValue
+ if (!assignImportKind<WasmTagObject>(
+ import, obj, lastExportsObj, lastExportIds,
+ &currentTagExportId, exportsLength, nullValue)) {
+ return 0;
+ }
+ break;
+ }
+ }
+ }
+
+ Rooted<ImportValues> imports(gCx);
+ if (!GetImports(gCx, *module, importObj, imports.address())) {
+ continue;
+ }
+
+ if (!module->instantiate(gCx, imports.get(), nullptr, &instanceObj)) {
+ continue;
+ }
+
+ // At this module we have a valid WebAssembly module instance.
+
+ RootedObject exportsObj(gCx, &instanceObj->exportsObj());
+ JS::Rooted<JS::IdVector> exportIds(gCx, JS::IdVector(gCx));
+ if (!JS_Enumerate(gCx, exportsObj, &exportIds)) {
+ continue;
+ }
+
+ if (!exportIds.length()) {
+ continue;
+ }
+
+ // Store the last exports for re-use later
+ lastExportsObj = exportsObj;
+ lastExportIds.get() = std::move(exportIds.get());
+
+ for (size_t i = 0; i < lastExportIds.length(); i++) {
+ RootedValue propVal(gCx);
+ if (!JS_GetPropertyById(gCx, exportsObj, lastExportIds[i], &propVal)) {
+ return 0;
+ }
+
+ if (propVal.isObject()) {
+ RootedObject propObj(gCx, &propVal.toObject());
+
+ if (propObj->is<JSFunction>()) {
+ RootedFunction func(gCx, &propObj->as<JSFunction>());
+
+ if (!callExportedFunc(func, &lastReturnVal)) {
+ return 0;
+ }
+ }
+
+ if (propObj->is<WasmTableObject>()) {
+ Rooted<WasmTableObject*> tableObj(gCx,
+ &propObj->as<WasmTableObject>());
+ size_t tableLen = tableObj->table().length();
+
+ RootedValue tableGetVal(gCx);
+ if (!JS_GetProperty(gCx, tableObj, "get", &tableGetVal)) {
+ return 0;
+ }
+ RootedFunction tableGet(gCx,
+ &tableGetVal.toObject().as<JSFunction>());
+
+ for (size_t i = 0; i < tableLen; i++) {
+ JS::RootedValueVector tableGetArgs(gCx);
+ if (!tableGetArgs.append(NumberValue(uint32_t(i)))) {
+ return 0;
+ }
+
+ RootedValue readFuncValue(gCx);
+ if (!Call(gCx, tableObj, tableGet, tableGetArgs, &readFuncValue)) {
+ return 0;
+ }
+
+ if (readFuncValue.isNull()) {
+ continue;
+ }
+
+ RootedFunction callee(gCx,
+ &readFuncValue.toObject().as<JSFunction>());
+
+ if (!callExportedFunc(callee, &lastReturnVal)) {
+ return 0;
+ }
+ }
+ }
+
+ if (propObj->is<WasmMemoryObject>()) {
+ Rooted<WasmMemoryObject*> memory(gCx,
+ &propObj->as<WasmMemoryObject>());
+ size_t byteLen = memory->volatileMemoryLength();
+ if (byteLen) {
+ // Read the bounds of the buffer to ensure it is valid.
+ // AddressSanitizer would detect any out-of-bounds here.
+ uint8_t* rawMemory = memory->buffer().dataPointerEither().unwrap();
+ volatile uint8_t rawMemByte = 0;
+ rawMemByte += rawMemory[0];
+ rawMemByte += rawMemory[byteLen - 1];
+ (void)rawMemByte;
+ }
+ }
+
+ if (propObj->is<WasmGlobalObject>()) {
+ Rooted<WasmGlobalObject*> global(gCx,
+ &propObj->as<WasmGlobalObject>());
+ if (global->type() != ValType::I64) {
+ global->val().get().toJSValue(gCx, &lastReturnVal);
+ }
+ }
+ }
+ }
+ }
+
+ return 0;
+}
+
+static int testWasmSmithFuzz(const uint8_t* buf, size_t size) {
+ // Define maximum sizes for the input to wasm-smith as well
+ // as the resulting modules. The input to output size factor
+ // of wasm-smith is somewhat variable but a factor of 4 seems
+ // to roughly work out. The logic below also assumes that these
+ // are powers of 2.
+ const size_t maxInputSize = 1024;
+ const size_t maxModuleSize = 4096;
+
+ size_t maxModules = size / maxInputSize + 1;
+
+ // We need 1 leading byte for options and 2 bytes for size per module
+ uint8_t* out =
+ new uint8_t[1 + maxModules * (maxModuleSize + sizeof(uint16_t))];
+
+ auto deleteGuard = mozilla::MakeScopeExit([&] { delete[] out; });
+
+ // Copy the opt-byte.
+ out[0] = buf[0];
+
+ size_t outIndex = 1;
+ size_t currentIndex = 1;
+
+ while (currentIndex < size) {
+ size_t remaining = size - currentIndex;
+
+ // We need to have at least a size and some byte to read.
+ if (remaining <= sizeof(uint16_t)) {
+ break;
+ }
+
+ // Determine size of the next input, limited to `maxInputSize`.
+ uint16_t inSize =
+ (*((uint16_t*)&buf[currentIndex]) & (maxInputSize - 1)) + 1;
+ remaining -= sizeof(uint16_t);
+ currentIndex += sizeof(uint16_t);
+
+ // Cap to remaining bytes.
+ inSize = remaining >= inSize ? inSize : remaining;
+
+ size_t outSize =
+ gluesmith((uint8_t*)&buf[currentIndex], inSize,
+ out + outIndex + sizeof(uint16_t), maxModuleSize);
+
+ if (!outSize) {
+ break;
+ }
+
+ currentIndex += inSize;
+
+ // Write the size of the resulting module to our output buffer.
+ *(uint16_t*)(&out[outIndex]) = (uint16_t)outSize;
+ outIndex += sizeof(uint16_t) + outSize;
+ }
+
+ // If we lack at least one module, don't do anything.
+ if (outIndex == 1) {
+ return 0;
+ }
+
+ return testWasmFuzz(out, outIndex);
+}
+
+MOZ_FUZZING_INTERFACE_RAW(testWasmInit, testWasmFuzz, Wasm);
+MOZ_FUZZING_INTERFACE_RAW(testWasmSmithInit, testWasmSmithFuzz, WasmSmith);
diff --git a/js/src/fuzz-tests/tests.cpp b/js/src/fuzz-tests/tests.cpp
new file mode 100644
index 0000000000..f6002bd739
--- /dev/null
+++ b/js/src/fuzz-tests/tests.cpp
@@ -0,0 +1,121 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * vim: set ts=8 sts=2 et sw=2 tw=80:
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "fuzz-tests/tests.h"
+
+#include <stdio.h>
+
+#include "js/AllocPolicy.h"
+#include "js/GlobalObject.h"
+#include "js/Initialization.h"
+#include "js/RootingAPI.h"
+#include "js/Stack.h"
+#include "vm/JSContext.h"
+
+#ifdef LIBFUZZER
+# include "FuzzerDefs.h"
+#endif
+
+using namespace mozilla;
+
+JS::PersistentRootedObject gGlobal;
+JSContext* gCx = nullptr;
+
+static const JSClass* getGlobalClass() {
+ static const JSClass c = {"global", JSCLASS_GLOBAL_FLAGS,
+ &JS::DefaultGlobalClassOps};
+ return &c;
+}
+
+static JSObject* jsfuzz_createGlobal(JSContext* cx, JSPrincipals* principals) {
+ /* Create the global object. */
+ JS::RealmOptions options;
+ options.creationOptions().setWeakRefsEnabled(
+ JS::WeakRefSpecifier::EnabledWithCleanupSome);
+ return JS_NewGlobalObject(cx, getGlobalClass(), principals,
+ JS::FireOnNewGlobalHook, options);
+}
+
+static bool jsfuzz_init(JSContext** cx, JS::PersistentRootedObject* global) {
+ *cx = JS_NewContext(8L * 1024 * 1024);
+ if (!*cx) {
+ return false;
+ }
+
+ const size_t MAX_STACK_SIZE = 500000;
+
+ JS_SetNativeStackQuota(*cx, MAX_STACK_SIZE);
+
+ js::UseInternalJobQueues(*cx);
+ if (!JS::InitSelfHostedCode(*cx)) {
+ return false;
+ }
+ global->init(*cx);
+ *global = jsfuzz_createGlobal(*cx, nullptr);
+ if (!*global) {
+ return false;
+ }
+ JS::EnterRealm(*cx, *global);
+ return true;
+}
+
+static void jsfuzz_uninit(JSContext* cx) {
+ if (cx) {
+ JS::LeaveRealm(cx, nullptr);
+ JS_DestroyContext(cx);
+ cx = nullptr;
+ }
+}
+
+int main(int argc, char* argv[]) {
+ if (!JS_Init()) {
+ fprintf(stderr, "Error: Call to jsfuzz_init() failed\n");
+ return 1;
+ }
+
+ if (!jsfuzz_init(&gCx, &gGlobal)) {
+ fprintf(stderr, "Error: Call to jsfuzz_init() failed\n");
+ return 1;
+ }
+
+ const char* fuzzerEnv = getenv("FUZZER");
+ if (!fuzzerEnv) {
+ fprintf(stderr,
+ "Must specify fuzzing target in FUZZER environment variable\n");
+ return 1;
+ }
+
+ std::string moduleNameStr(getenv("FUZZER"));
+
+ FuzzerFunctions funcs =
+ FuzzerRegistry::getInstance().getModuleFunctions(moduleNameStr);
+ FuzzerInitFunc initFunc = funcs.first;
+ FuzzerTestingFunc testingFunc = funcs.second;
+ if (initFunc) {
+ int ret = initFunc(&argc, &argv);
+ if (ret) {
+ fprintf(stderr, "Fuzzing Interface: Error: Initialize callback failed\n");
+ return ret;
+ }
+ }
+
+ if (!testingFunc) {
+ fprintf(stderr, "Fuzzing Interface: Error: No testing callback found\n");
+ return 1;
+ }
+
+#ifdef LIBFUZZER
+ fuzzer::FuzzerDriver(&argc, &argv, testingFunc);
+#elif AFLFUZZ
+ testingFunc(nullptr, 0);
+#endif
+
+ jsfuzz_uninit(gCx);
+
+ JS_ShutDown();
+
+ return 0;
+}
diff --git a/js/src/fuzz-tests/tests.h b/js/src/fuzz-tests/tests.h
new file mode 100644
index 0000000000..3935ade748
--- /dev/null
+++ b/js/src/fuzz-tests/tests.h
@@ -0,0 +1,19 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * vim: set ts=8 sts=2 et sw=2 tw=80:
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef fuzz_tests_tests_h
+#define fuzz_tests_tests_h
+
+#include <errno.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "FuzzingInterface.h"
+
+#include "vm/JSContext.h"
+
+#endif /* fuzz_tests_tests_h */
diff --git a/js/src/fuzz-tests/util/sanitize.js b/js/src/fuzz-tests/util/sanitize.js
new file mode 100644
index 0000000000..77c5badc00
--- /dev/null
+++ b/js/src/fuzz-tests/util/sanitize.js
@@ -0,0 +1,100 @@
+/* -*- Mode: javascript; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// This function can be used to "sanitize" a new global for fuzzing in such
+// a way that permanent side-effects, hangs and behavior that could be harmful
+// to libFuzzer targets is reduced to a minimum.
+function sanitizeGlobal(g) {
+ let lfFuncs = {
+ // Noisy functions (output)
+ backtrace: function () { },
+ getBacktrace: function () { },
+ help: function () { },
+ print: function (s) { return s.toString(); },
+ printErr: function (s) { return s.toString(); },
+ putstr: function (s) { return s.toString(); },
+ stackDump: function () { },
+ dumpHeap: function () { },
+ dumpScopeChain: function () { },
+ dumpObjectWrappers: function () { },
+ dumpGCArenaInfo: function () { },
+ printProfilerEvents: function () { },
+
+ // Harmful functions (hangs, timeouts, leaks)
+ getLcovInfo: function () { },
+ readline: function () { },
+ readlineBuf: function () { },
+ timeout: function () { },
+ quit: function () { },
+ interruptIf: function () { },
+ terminate: function () { },
+ invokeInterruptCallback: function () { },
+ setInterruptCallback: function () { },
+ intern: function () { },
+ evalInWorker: function () { },
+ sleep: function () { },
+ cacheEntry: function () { },
+ streamCacheEntry: function () { },
+ createMappedArrayBuffer: function () { },
+ wasmCompileInSeparateProcess: function () { },
+ gcparam: function () { },
+ newGlobal: function () { return g; },
+
+ // Harmful functions (throw)
+ assertEq: function (a, b) { return a.toString() == b.toString(); },
+ throwError: function () { },
+ reportOutOfMemory: function () { },
+ throwOutOfMemory: function () { },
+ reportLargeAllocationFailure: function () { },
+
+ // Functions that need limiting
+ gczeal: function (m, f) { return gczeal(m, 100); },
+ startgc: function (n, o) { startgc(n > 20 ? 20 : n, o); },
+ gcslice: function (n) { gcslice(n > 20 ? 20 : n); },
+
+ // Global side-effects
+ deterministicgc: function () { },
+ fullcompartmentchecks: function () { },
+ setIonCheckGraphCoherency: function () { },
+ enableShellAllocationMetadataBuilder: function () { },
+ setTimeResolution: function () { },
+ options: function () { return "tracejit,methodjit,typeinfer"; },
+ setJitCompilerOption: function () { },
+ clearLastWarning: function () { },
+ enableSingleStepProfiling: function () { },
+ disableSingleStepProfiling: function () { },
+ enableGeckoProfiling: function () { },
+ enableGeckoProfilingWithSlowAssertions: function () { },
+ disableGeckoProfiling: function () { },
+ enqueueJob: function () { },
+ globalOfFirstJobInQueue: function () { },
+ drainJobQueue: function () { },
+ setPromiseRejectionTrackerCallback: function () { },
+ startTimingMutator: function () { },
+ stopTimingMutator: function () { },
+ setModuleLoadHook: function () { },
+ // Left enabled, as it is required for now to avoid leaks
+ //setModuleResolveHook: function() {},
+ setModuleMetadataHook: function () { },
+ setModuleDynamicImportHook: function () { },
+ finishDynamicModuleImport: function () { },
+ abortDynamicModuleImport: function () { },
+ offThreadCompileToStencil: function () { },
+ offThreadCompileModuleToStencil: function () { },
+ offThreadDecodeStencil: function () { },
+ finishOffThreadStencil: function () { },
+ addPromiseReactions: function () { },
+ ignoreUnhandledRejections: function () { },
+ enableTrackAllocations: function () { },
+ disableTrackAllocations: function () { },
+ setTestFilenameValidationCallback: function () { },
+ };
+
+ for (let lfFunc in lfFuncs) {
+ g[lfFunc] = lfFuncs[lfFunc];
+ }
+
+ return g;
+}