Bug 1431090 - Add FuzzingInterface support to JS engine. r=jandem,froydnj draft
authorChristian Holler <choller@mozilla.com>
Wed, 17 Jan 2018 17:05:04 +0100
changeset 723550 b715ac2492115d632fc43f61971ecfb1beb63f31
parent 723487 fe07c557032fd33257eb701190becfaf85ab79d0
child 746889 626626fd7724001b41fdce0aa2ba4801ab07bb26
push id96461
push usercholler@mozilla.com
push dateTue, 23 Jan 2018 14:35:26 +0000
reviewersjandem, froydnj
bugs1431090
milestone59.0a1
Bug 1431090 - Add FuzzingInterface support to JS engine. r=jandem,froydnj This patch adds basic support for the fuzzing interface in the JS engine on top of the last patch. This includes all the necessary code except for actual targets (just an example target skeleton) and also makes sure that the fuzzing code is packaged for the standalone release. MozReview-Commit-ID: D6Tyebz3jZS
config/check_spidermonkey_style.py
js/app.mozbuild
js/src/fuzz-tests/README
js/src/fuzz-tests/moz.build
js/src/fuzz-tests/testExample.cpp
js/src/fuzz-tests/tests.cpp
js/src/fuzz-tests/tests.h
js/src/make-source-package.sh
js/src/moz.build
--- a/config/check_spidermonkey_style.py
+++ b/config/check_spidermonkey_style.py
@@ -64,16 +64,18 @@ included_inclnames_to_ignore = set([
     'double-conversion.h',      # strange MFBT case
     'javascript-trace.h',       # generated in $OBJDIR if HAVE_DTRACE is defined
     'frontend/ReservedWordsGenerated.h', # generated in $OBJDIR
     'gc/StatsPhasesGenerated.h',         # generated in $OBJDIR
     'gc/StatsPhasesGenerated.cpp',       # generated in $OBJDIR
     'jscustomallocator.h',      # provided by embedders;  allowed to be missing
     'js-config.h',              # generated in $OBJDIR
     'fdlibm.h',                 # fdlibm
+    'FuzzerDefs.h',             # included without a path
+    'FuzzingInterface.h',       # included without a path
     'mozmemory.h',              # included without a path
     'pratom.h',                 # NSPR
     'prcvar.h',                 # NSPR
     'prerror.h',                # NSPR
     'prinit.h',                 # NSPR
     'prio.h',                   # NSPR
     'private/pprio.h',          # NSPR
     'prlink.h',                 # NSPR
--- a/js/app.mozbuild
+++ b/js/app.mozbuild
@@ -31,8 +31,13 @@ if CONFIG['USE_ICU']:
         '/config/external/icu',
     ]
 
 if CONFIG['COMPILE_ENVIRONMENT'] and CONFIG['BUILD_CTYPES']:
     DIRS += [
         '/config/external/ffi',
     ]
 
+if CONFIG['JS_STANDALONE'] and CONFIG['FUZZING']:
+    DIRS += [
+        '/tools/fuzzing/',
+    ]
+
new file mode 100644
--- /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
new file mode 100644
--- /dev/null
+++ b/js/src/fuzz-tests/moz.build
@@ -0,0 +1,52 @@
+# -*- 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)
+
+UNIFIED_SOURCES += [
+    'testExample.cpp',
+    'tests.cpp',
+]
+
+DEFINES['EXPORT_JS_API'] = True
+
+LOCAL_INCLUDES += [
+    '!..',
+    '..',
+]
+
+if CONFIG['ENABLE_INTL_API'] and CONFIG['MOZ_ICU_DATA_ARCHIVE']:
+    # The ICU libraries linked into libmozjs will not include the ICU data,
+    # so link it directly.
+    USE_LIBS += ['icudata']
+
+if CONFIG['FUZZING']:
+    USE_LIBS += [
+        'static:fuzzer-registry',
+    ]
+
+if CONFIG['LIBFUZZER']:
+    USE_LIBS += [
+        'static:fuzzer',
+    ]
+
+    # Add trace-pc coverage for libfuzzer
+    CFLAGS += ['-fsanitize-coverage=trace-pc-guard']
+    CXXFLAGS += ['-fsanitize-coverage=trace-pc-guard']
+
+USE_LIBS += [
+    'static:js',
+]
+
+if CONFIG['MOZ_NEEDS_LIBATOMIC']:
+    OS_LIBS += ['atomic']
+
+OS_LIBS += CONFIG['MOZ_ZLIB_LIBS']
+
+if CONFIG['CC_TYPE'] in ('clang', 'gcc'):
+    CXXFLAGS += ['-Wno-shadow', '-Werror=format', '-fno-strict-aliasing']
+
+DEFINES['topsrcdir'] = '%s/js/src' % TOPSRCDIR
new file mode 100644
--- /dev/null
+++ b/js/src/fuzz-tests/testExample.cpp
@@ -0,0 +1,62 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ * vim: set ts=8 sts=4 et sw=4 tw=99:
+ */
+/* 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 "jscntxtinlines.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::GCForReason(gCx, GC_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 */
+);
new file mode 100644
--- /dev/null
+++ b/js/src/fuzz-tests/tests.cpp
@@ -0,0 +1,151 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ * vim: set ts=8 sts=4 et sw=4 tw=99:
+ * 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 "jsalloc.h"
+#include "jscntxt.h"
+#include "js/Initialization.h"
+#include "js/RootingAPI.h"
+
+#ifdef LIBFUZZER
+#include "FuzzerDefs.h"
+#endif
+
+using namespace mozilla;
+
+JS::PersistentRootedObject gGlobal;
+JSContext* gCx = nullptr;
+JSCompartment* gOldCompartment = nullptr;
+
+static const JSClass*
+getGlobalClass()
+{
+    static const JSClassOps cOps = {
+        nullptr, nullptr, nullptr, nullptr,
+        nullptr, nullptr, nullptr, nullptr,
+        nullptr, nullptr,
+        JS_GlobalObjectTraceHook
+    };
+    static const JSClass c = {
+        "global", JSCLASS_GLOBAL_FLAGS,
+        &cOps
+    };
+    return &c;
+}
+
+static JSObject*
+jsfuzz_createGlobal(JSContext* cx, JSPrincipals* principals)
+{
+    /* Create the global object. */
+    JS::RootedObject newGlobal(cx);
+    JS::CompartmentOptions options;
+#ifdef ENABLE_STREAMS
+    options.creationOptions().setStreamsEnabled(true);
+#endif
+    newGlobal = JS_NewGlobalObject(cx, getGlobalClass(), principals, JS::FireOnNewGlobalHook,
+                                   options);
+    if (!newGlobal)
+        return nullptr;
+
+    JSAutoCompartment ac(cx, newGlobal);
+
+    // Populate the global object with the standard globals like Object and
+    // Array.
+    if (!JS_InitStandardClasses(cx, newGlobal))
+        return nullptr;
+
+    return newGlobal;
+}
+
+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;
+    JS_BeginRequest(*cx);
+    global->init(*cx);
+    *global = jsfuzz_createGlobal(*cx, nullptr);
+    if (!*global)
+        return false;
+    JS_EnterCompartment(*cx, *global);
+    return true;
+}
+
+static void
+jsfuzz_uninit(JSContext* cx, JSCompartment* oldCompartment)
+{
+    if (oldCompartment) {
+        JS_LeaveCompartment(cx, oldCompartment);
+        oldCompartment = nullptr;
+    }
+    if (cx) {
+        JS_EndRequest(cx);
+        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 __AFL_COMPILER
+    testingFunc(nullptr, 0);
+#endif
+
+    jsfuzz_uninit(gCx, nullptr);
+
+    JS_ShutDown();
+
+    return 0;
+}
new file mode 100644
--- /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: 4 -*-
+ * vim: set ts=8 sts=4 et sw=4 tw=99:
+ * 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 "jscntxt.h"
+
+#endif /* fuzz_tests_tests_h */
--- a/js/src/make-source-package.sh
+++ b/js/src/make-source-package.sh
@@ -163,16 +163,23 @@ case $cmd in
         ${tgtpath}/mozglue
     ${MKDIR} -p ${tgtpath}/memory
     cp -pPR \
         ${TOPSRCDIR}/memory/moz.build \
         ${TOPSRCDIR}/memory/build \
         ${TOPSRCDIR}/memory/fallible \
         ${TOPSRCDIR}/memory/mozalloc \
         ${tgtpath}/memory
+    ${MKDIR} -p ${tgtpath}/tools/fuzzing
+    cp -pPR \
+        ${TOPSRCDIR}/tools/fuzzing/moz.build \
+        ${TOPSRCDIR}/tools/fuzzing/interface \
+        ${TOPSRCDIR}/tools/fuzzing/registry \
+        ${TOPSRCDIR}/tools/fuzzing/libfuzzer \
+        ${tgtpath}/tools/fuzzing
 
     # remove *.pyc and *.pyo files if any
     find ${tgtpath} -type f -name "*.pyc" -o -name "*.pyo" |xargs rm -f
 
     # Remove non-JS Cargo.toml files (for example, the invalid Cargo.toml files
     # used for some testing).
     find ${tgtpath} -type f -name Cargo.toml | grep -v js | xargs rm -f
 
--- a/js/src/moz.build
+++ b/js/src/moz.build
@@ -45,16 +45,26 @@ with Files('builtin/make_intl_data.py'):
 if CONFIG['JS_BUNDLED_EDITLINE']:
     DIRS += ['editline']
 
 if not CONFIG['JS_DISABLE_SHELL']:
     DIRS += ['shell']
 
 TEST_DIRS += ['jsapi-tests', 'tests', 'gdb']
 
+if CONFIG['FUZZING_INTERFACES']:
+    if CONFIG['LIBFUZZER']:
+        # Add trace-pc coverage for libfuzzer
+        CFLAGS += ['-fsanitize-coverage=trace-pc-guard']
+        CXXFLAGS += ['-fsanitize-coverage=trace-pc-guard']
+
+    TEST_DIRS += [
+        'fuzz-tests',
+    ]
+
 CONFIGURE_SUBST_FILES += [
     'devtools/rootAnalysis/Makefile',
 ]
 CONFIGURE_DEFINE_FILES += [
     'js-confdefs.h',
 ]
 
 if not CONFIG['JS_STANDALONE']: