diff options
Diffstat (limited to 'js/src/shell/jsrtfuzzing')
-rw-r--r-- | js/src/shell/jsrtfuzzing/jsrtfuzzing-example.js | 39 | ||||
-rw-r--r-- | js/src/shell/jsrtfuzzing/jsrtfuzzing.cpp | 143 | ||||
-rw-r--r-- | js/src/shell/jsrtfuzzing/jsrtfuzzing.h | 28 |
3 files changed, 210 insertions, 0 deletions
diff --git a/js/src/shell/jsrtfuzzing/jsrtfuzzing-example.js b/js/src/shell/jsrtfuzzing/jsrtfuzzing-example.js new file mode 100644 index 0000000000..1fdf8ee2c1 --- /dev/null +++ b/js/src/shell/jsrtfuzzing/jsrtfuzzing-example.js @@ -0,0 +1,39 @@ +/* -*- 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 global will hold the current fuzzing buffer for each iteration. +var fuzzBuf; + +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. + + try { + // This is a very simple UTF-16 string conversion for example purposes only. + let input = String.fromCharCode.apply(null, new Uint16Array(fuzzBuf.buffer)); + + // Pass the input through the JSON code as an example. Note that this + // particular example could probably be implemented more efficiently + // directly in fuzz-tests on a C++ level. This is purely for demonstration + // purposes. + print(JSON.stringify(JSON.parse(input))); + } catch(exc) { + print(exc); + } +} diff --git a/js/src/shell/jsrtfuzzing/jsrtfuzzing.cpp b/js/src/shell/jsrtfuzzing/jsrtfuzzing.cpp new file mode 100644 index 0000000000..b78a527a78 --- /dev/null +++ b/js/src/shell/jsrtfuzzing/jsrtfuzzing.cpp @@ -0,0 +1,143 @@ +/* -*- 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 "shell/jsrtfuzzing/jsrtfuzzing.h" + +#include "mozilla/Assertions.h" // MOZ_CRASH +#include "mozilla/Utf8.h" // mozilla::Utf8Unit + +#include <stdio.h> // fflush, fprintf, fputs + +#include "FuzzerDefs.h" +#include "FuzzingInterface.h" +#include "jsapi.h" // JS_ClearPendingException, JS_IsExceptionPending, JS_SetProperty + +#include "js/CompilationAndEvaluation.h" // JS::Evaluate +#include "js/CompileOptions.h" // JS::CompileOptions +#include "js/ErrorReport.h" // JS::PrintError +#include "js/Exception.h" // JS::StealPendingExceptionStack +#include "js/experimental/TypedData.h" // JS_GetUint8ClampedArrayData, JS_NewUint8ClampedArray +#include "js/RootingAPI.h" // JS::Rooted +#include "js/SourceText.h" // JS::Source{Ownership,Text} +#include "js/Value.h" // JS::Value +#include "shell/jsshell.h" // js::shell::{reportWarnings,PrintStackTrace,sArg{c,v}} +#include "util/Text.h" +#include "vm/Interpreter.h" +#include "vm/TypedArrayObject.h" + +#include "vm/ArrayBufferObject-inl.h" +#include "vm/JSContext-inl.h" + +static JSContext* gCx = nullptr; +static std::string gFuzzModuleName; + +static void CrashOnPendingException() { + if (JS_IsExceptionPending(gCx)) { + JS::ExceptionStack exnStack(gCx); + (void)JS::StealPendingExceptionStack(gCx, &exnStack); + + JS::ErrorReportBuilder report(gCx); + if (!report.init(gCx, exnStack, JS::ErrorReportBuilder::WithSideEffects)) { + fprintf(stderr, "out of memory initializing JS::ErrorReportBuilder\n"); + fflush(stderr); + } else { + JS::PrintError(gCx, stderr, report, js::shell::reportWarnings); + if (!js::shell::PrintStackTrace(gCx, exnStack.stack())) { + fputs("(Unable to print stack trace)\n", stderr); + } + } + + MOZ_CRASH("Unhandled exception from JS runtime!"); + } +} + +#ifdef LIBFUZZER +static void FuzzJSRuntimeAtExit() { JS_ShutDown(); } +#endif + +int js::shell::FuzzJSRuntimeStart(JSContext* cx, int* argc, char*** argv) { + gCx = cx; + gFuzzModuleName = getenv("FUZZER"); + + int ret = FuzzJSRuntimeInit(argc, argv); + if (ret) { + fprintf(stderr, "Fuzzing Interface: Error: Initialize callback failed\n"); + return ret; + } + +#ifdef LIBFUZZER + // This is required because libFuzzer can exit() in various cases + std::atexit(FuzzJSRuntimeAtExit); + fuzzer::FuzzerDriver(&shell::sArgc, &shell::sArgv, FuzzJSRuntimeFuzz); +#elif __AFL_COMPILER + MOZ_CRASH("AFL is unsupported for JS runtime fuzzing integration"); +#endif + return 0; +} + +int js::shell::FuzzJSRuntimeInit(int* argc, char*** argv) { + JS::Rooted<JS::Value> v(gCx); + JS::CompileOptions opts(gCx); + + // Load the fuzzing module specified in the FUZZER environment variable + JS::EvaluateUtf8Path(gCx, opts, gFuzzModuleName.c_str(), &v); + + // Any errors while loading the fuzzing module should be fatal + CrashOnPendingException(); + + return 0; +} + +int js::shell::FuzzJSRuntimeFuzz(const uint8_t* buf, size_t size) { + if (!size) { + return 0; + } + + JS::Rooted<JSObject*> arr(gCx, JS_NewUint8ClampedArray(gCx, size)); + if (!arr) { + MOZ_CRASH("OOM"); + } + + do { + JS::AutoCheckCannotGC nogc; + bool isShared; + uint8_t* data = JS_GetUint8ClampedArrayData(arr, &isShared, nogc); + MOZ_RELEASE_ASSERT(!isShared); + memcpy(data, buf, size); + } while (false); + + JS::RootedValue arrVal(gCx, JS::ObjectValue(*arr)); + if (!JS_SetProperty(gCx, gCx->global(), "fuzzBuf", arrVal)) { + MOZ_CRASH("JS_SetProperty failed"); + } + + JS::Rooted<JS::Value> v(gCx); + JS::CompileOptions opts(gCx); + + static const char data[] = "JSFuzzIterate();"; + + JS::SourceText<mozilla::Utf8Unit> srcBuf; + if (!srcBuf.init(gCx, data, js_strlen(data), JS::SourceOwnership::Borrowed)) { + return 1; + } + + if (!JS::Evaluate(gCx, opts.setFileAndLine(__FILE__, __LINE__), srcBuf, &v) && + !JS_IsExceptionPending(gCx)) { + // A return value of `false` without a pending exception indicates + // a timeout as triggered by the `timeout` shell function. + return 1; + } + + // The fuzzing module is required to handle any exceptions + CrashOnPendingException(); + + int32_t ret = 0; + if (!ToInt32(gCx, v, &ret)) { + MOZ_CRASH("Must return an int32 compatible return value!"); + } + + return ret; +} diff --git a/js/src/shell/jsrtfuzzing/jsrtfuzzing.h b/js/src/shell/jsrtfuzzing/jsrtfuzzing.h new file mode 100644 index 0000000000..9216aa91a1 --- /dev/null +++ b/js/src/shell/jsrtfuzzing/jsrtfuzzing.h @@ -0,0 +1,28 @@ +/* -*- 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/. */ + +// jsrtfuzzing.h - Functionality for JS runtime fuzzing + +#ifndef shell_jsrtfuzzing_h +#define shell_jsrtfuzzing_h + +#include "vm/JSContext.h" + +namespace js { +namespace shell { + +// This is the entry point of the JS runtime fuzzing code from the JS shell +int FuzzJSRuntimeStart(JSContext* cx, int* argc, char*** argv); + +// These are the traditional libFuzzer-style functions for initialization +// and fuzzing iteration. +int FuzzJSRuntimeInit(int* argc, char*** argv); +int FuzzJSRuntimeFuzz(const uint8_t* buf, size_t size); + +} // namespace shell +} // namespace js + +#endif /* shell_jsrtfuzzing_h */ |