jsrtfuzzing.cpp (4940B)
1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- 2 * vim: set ts=8 sts=2 et sw=2 tw=80: 3 * This Source Code Form is subject to the terms of the Mozilla Public 4 * License, v. 2.0. If a copy of the MPL was not distributed with this 5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 6 7 #include "shell/jsrtfuzzing/jsrtfuzzing.h" 8 9 #include "mozilla/Assertions.h" // MOZ_CRASH 10 #include "mozilla/Utf8.h" // mozilla::Utf8Unit 11 12 #include <stdio.h> // fflush, fprintf, fputs 13 14 #ifdef LIBFUZZER 15 # include "FuzzerDefs.h" 16 #endif 17 #ifdef AFLFUZZ 18 # include "FuzzingInterface.h" 19 #endif 20 21 #include "jsapi.h" // JS_ClearPendingException, JS_IsExceptionPending 22 23 #include "js/CompilationAndEvaluation.h" // JS::Evaluate 24 #include "js/CompileOptions.h" // JS::CompileOptions 25 #include "js/Conversions.h" // JS::ToInt32 26 #include "js/ErrorReport.h" // JS::PrintError 27 #include "js/Exception.h" // JS::StealPendingExceptionStack 28 #include "js/experimental/TypedData.h" // JS_GetUint8ClampedArrayData, JS_NewUint8ClampedArray 29 #include "js/PropertyAndElement.h" // JS_SetProperty 30 #include "js/RootingAPI.h" // JS::Rooted 31 #include "js/SourceText.h" // JS::Source{Ownership,Text} 32 #include "js/Value.h" // JS::Value 33 #include "shell/jsshell.h" // js::shell::{reportWarnings,PrintStackTrace,sArg{c,v}} 34 #include "util/Text.h" 35 #include "vm/Interpreter.h" 36 #include "vm/TypedArrayObject.h" 37 38 #include "vm/ArrayBufferObject-inl.h" 39 #include "vm/JSContext-inl.h" 40 41 static JSContext* gCx = nullptr; 42 MOZ_RUNINIT static std::string gFuzzModuleName; 43 44 static void CrashOnPendingException() { 45 if (JS_IsExceptionPending(gCx)) { 46 JS::ExceptionStack exnStack(gCx); 47 (void)JS::StealPendingExceptionStack(gCx, &exnStack); 48 49 JS::ErrorReportBuilder report(gCx); 50 if (!report.init(gCx, exnStack, JS::ErrorReportBuilder::WithSideEffects)) { 51 fprintf(stderr, "out of memory initializing JS::ErrorReportBuilder\n"); 52 fflush(stderr); 53 } else { 54 JS::PrintError(stderr, report, js::shell::reportWarnings); 55 if (!js::shell::PrintStackTrace(gCx, exnStack.stack())) { 56 fputs("(Unable to print stack trace)\n", stderr); 57 } 58 } 59 60 MOZ_CRASH("Unhandled exception from JS runtime!"); 61 } 62 } 63 64 int js::shell::FuzzJSRuntimeStart(JSContext* cx, int* argc, char*** argv) { 65 gCx = cx; 66 gFuzzModuleName = getenv("FUZZER"); 67 68 int ret = FuzzJSRuntimeInit(argc, argv); 69 if (ret) { 70 fprintf(stderr, "Fuzzing Interface: Error: Initialize callback failed\n"); 71 return ret; 72 } 73 74 #ifdef LIBFUZZER 75 fuzzer::FuzzerDriver(&shell::sArgc, &shell::sArgv, FuzzJSRuntimeFuzz); 76 #elif AFLFUZZ 77 afl_interface_raw(FuzzJSRuntimeFuzz); 78 #endif 79 return 0; 80 } 81 82 int js::shell::FuzzJSRuntimeInit(int* argc, char*** argv) { 83 JS::Rooted<JS::Value> v(gCx); 84 JS::CompileOptions opts(gCx); 85 86 // Load the fuzzing module specified in the FUZZER environment variable 87 JS::EvaluateUtf8Path(gCx, opts, gFuzzModuleName.c_str(), &v); 88 89 // Any errors while loading the fuzzing module should be fatal 90 CrashOnPendingException(); 91 92 return 0; 93 } 94 95 int js::shell::FuzzJSRuntimeFuzz(const uint8_t* buf, size_t size) { 96 if (!size) { 97 return 0; 98 } 99 100 JS::Rooted<JSObject*> arr(gCx, JS_NewUint8ClampedArray(gCx, size)); 101 if (!arr) { 102 MOZ_CRASH("OOM"); 103 } 104 105 do { 106 JS::AutoCheckCannotGC nogc; 107 bool isShared; 108 uint8_t* data = JS_GetUint8ClampedArrayData(arr, &isShared, nogc); 109 MOZ_RELEASE_ASSERT(!isShared); 110 memcpy(data, buf, size); 111 } while (false); 112 113 JS::RootedValue arrVal(gCx, JS::ObjectValue(*arr)); 114 if (!JS_SetProperty(gCx, gCx->global(), "fuzzBuf", arrVal)) { 115 MOZ_CRASH("JS_SetProperty failed"); 116 } 117 118 JS::Rooted<JS::Value> v(gCx); 119 JS::CompileOptions opts(gCx); 120 121 static const char data[] = "JSFuzzIterate();"; 122 123 JS::SourceText<mozilla::Utf8Unit> srcBuf; 124 if (!srcBuf.init(gCx, data, js_strlen(data), JS::SourceOwnership::Borrowed)) { 125 return 1; 126 } 127 128 if (!JS::Evaluate(gCx, opts.setFileAndLine(__FILE__, __LINE__), srcBuf, &v) && 129 !JS_IsExceptionPending(gCx)) { 130 // A return value of `false` without a pending exception indicates 131 // a timeout as triggered by the `timeout` shell function. 132 return 1; 133 } 134 135 if (gCx->isThrowingOutOfMemory()) { 136 // If the target is throwing out of memory, try to recover and indicate 137 // to the fuzzer that we don't want to keep this sample as it usually 138 // slows down execution. 139 gCx->recoverFromOutOfMemory(); 140 return 1; 141 } 142 143 // Also make sure to reset this flag, as the fuzzing implementation might 144 // use it to discard differential test results in the next run. 145 gCx->runtime()->hadOutOfMemory = false; 146 147 // The fuzzing module is required to handle any exceptions 148 CrashOnPendingException(); 149 150 int32_t ret = 0; 151 if (!JS::ToInt32(gCx, v, &ret)) { 152 MOZ_CRASH("Must return an int32 compatible return value!"); 153 } 154 155 return ret; 156 }