From f3e4280df1c7c0f091b32626d1f4e4060defd712 Mon Sep 17 00:00:00 2001 From: Christian Holler Date: Tue, 2 Jun 2020 13:50:23 +0000 Subject: [PATCH] Bug 1596691 - Add a JSParser target to fuzz-tests. r=jandem Differential Revision: https://phabricator.services.mozilla.com/D77006 --- .eslintignore | 3 + js/src/fuzz-tests/parsing-evaluate.js | 83 ++++++++++++++++++++ js/src/fuzz-tests/util/sanitize.js | 104 ++++++++++++++++++++++++++ 3 files changed, 190 insertions(+) create mode 100644 js/src/fuzz-tests/parsing-evaluate.js create mode 100644 js/src/fuzz-tests/util/sanitize.js diff --git a/.eslintignore b/.eslintignore index e0be1073894c..a68b47cc92e9 100644 --- a/.eslintignore +++ b/.eslintignore @@ -143,6 +143,9 @@ js/src/jsapi-tests/binast/ js/src/tests/ js/src/Y.js +# Fuzzing code for testing only, targeting the JS shell +js/src/fuzz-tests/ + # Uses `#filter substitution` mobile/android/app/mobile.js mobile/android/app/geckoview-prefs.js diff --git a/js/src/fuzz-tests/parsing-evaluate.js b/js/src/fuzz-tests/parsing-evaluate.js new file mode 100644 index 000000000000..1714e051a9c8 --- /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/util/sanitize.js b/js/src/fuzz-tests/util/sanitize.js new file mode 100644 index 000000000000..59d43a42bd74 --- /dev/null +++ b/js/src/fuzz-tests/util/sanitize.js @@ -0,0 +1,104 @@ +/* -*- 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() {}, + offThreadCompileScript: function() {}, + runOffThreadScript: function() {}, + offThreadCompileModule: function() {}, + finishOffThreadModule: function() {}, + offThreadDecodeScript: function() {}, + runOffThreadDecodedScript: function() {}, + addPromiseReactions: function() {}, + ignoreUnhandledRejections: function() {}, + enableTrackAllocations: function() {}, + disableTrackAllocations: function() {}, + startTraceLogger: function() {}, + stopTraceLogger: function() {}, + setTestFilenameValidationCallback: function() {}, + }; + + for (let lfFunc in lfFuncs) { + g[lfFunc] = lfFuncs[lfFunc]; + } + + return g; +}