diff --git a/js/src/fuzz-tests/moz.build b/js/src/fuzz-tests/moz.build index ef1737fe83d3..a78c2bc8ffaf 100644 --- a/js/src/fuzz-tests/moz.build +++ b/js/src/fuzz-tests/moz.build @@ -12,7 +12,6 @@ UNIFIED_SOURCES += [ 'testExample.cpp', 'tests.cpp', 'testStructuredCloneReader.cpp', - 'testWasm.cpp', ] if CONFIG['JS_BUILD_BINAST']: diff --git a/js/src/fuzz-tests/testWasm.cpp b/js/src/fuzz-tests/testWasm.cpp deleted file mode 100644 index 637ebb8943cf..000000000000 --- a/js/src/fuzz-tests/testWasm.cpp +++ /dev/null @@ -1,432 +0,0 @@ -/* 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/TypedArrayObject.h" - -#include "wasm/WasmCompile.h" -#include "wasm/WasmCraneliftCompile.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 int testWasmInit(int* argc, char*** argv) { - if (!wasm::HasSupport(gCx)) { - MOZ_CRASH("Failed to initialize wasm support"); - } - - return 0; -} - -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 -static bool assignImportKind(const Import& import, HandleObject obj, - HandleObject lastExportsObj, - JS::Handle lastExportIds, - size_t* currentExportId, size_t exportsLength, - HandleValue defaultValue) { - 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()) { - if (!JS_SetProperty(gCx, obj, import.field.get(), propVal)) { - return false; - } - - assigned = true; - break; - } - } - if (!assigned) { - if (!JS_SetProperty(gCx, obj, import.field.get(), defaultValue)) { - return false; - } - } - return true; -} - -static int testWasmFuzz(const uint8_t* buf, size_t size) { - auto gcGuard = mozilla::MakeScopeExit([&] { - JS::PrepareForFullGC(gCx); - JS::NonIncrementalGC(gCx, GC_NORMAL, JS::GCReason::API); - }); - - 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 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(); - - unsigned char moduleLen = buf[currentIndex]; - currentIndex++; - - if (size - currentIndex < moduleLen) { - moduleLen = size - currentIndex; - } - - if (moduleLen < MINIMUM_MODULE_SIZE) { - continue; - } - - if (currentIndex == 1) { - // 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 = (uint8_t)buf[currentIndex]; - - bool enableWasmBaseline = ((optByte & 0xF0) == (1 << 7)); - bool enableWasmIon = IonCanCompile() && ((optByte & 0xF0) == (1 << 6)); - bool enableWasmCranelift = false; -#ifdef ENABLE_WASM_CRANELIFT - enableWasmCranelift = - CraneliftCanCompile() && ((optByte & 0xF0) == (1 << 5)); -#endif - bool enableWasmAwaitTier2 = (IonCanCompile() -#ifdef ENABLE_WASM_CRANELIFT - || CraneliftCanCompile() -#endif - ) && - ((optByte & 0xF) == (1 << 3)); - - if (!enableWasmBaseline && !enableWasmIon && !enableWasmCranelift) { - // If nothing is selected explicitly, select Ion to test - // more platform specific JIT code. However, on some platforms, - // e.g. ARM64, we do not have Ion available, so we need to switch - // to baseline instead. - if (IonCanCompile()) { - enableWasmIon = true; - } else { - enableWasmBaseline = true; - } - } - - // TODO: Cranelift is not stable for fuzzing, defer to baseline - if (enableWasmCranelift) { - enableWasmCranelift = false; - enableWasmBaseline = true; - } - - if (enableWasmAwaitTier2) { - // Tier 2 needs Baseline + {Ion,Cranelift} - enableWasmBaseline = true; - - if (!enableWasmIon && !enableWasmCranelift) { - enableWasmIon = true; - } - } - - JS::ContextOptionsRef(gCx) - .setWasmBaseline(enableWasmBaseline) - .setWasmIon(enableWasmIon) - .setTestWasmAwaitTier2(enableWasmAwaitTier2) -#ifdef ENABLE_WASM_CRANELIFT - .setWasmCranelift(enableWasmCranelift) -#endif - ; - } - - // Expected header for a valid WebAssembly module - uint32_t magic_header = 0x6d736100; - uint32_t magic_version = 0x1; - - // We just skip over the first 8 bytes now because we fill them - // with `magic_header` and `magic_version` anyway. - currentIndex += 8; - moduleLen -= 8; - - RootedWasmInstanceObject instanceObj(gCx); - - MutableBytes bytecode = gCx->new_(); - 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; - SharedCompileArgs compileArgs = - CompileArgs::build(gCx, std::move(scriptedCaller)); - if (!compileArgs) { - return 0; - } - - UniqueChars error; - UniqueCharsVector warnings; - SharedModule module = - CompileBuffer(*compileArgs, *bytecode, &error, &warnings); - if (!module) { - 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; - - for (const Import& import : importVec) { - // First try to get the namespace object, create one if this is the - // first time. - RootedValue v(gCx); - if (!JS_GetProperty(gCx, importObj, import.module.get(), &v) || - !v.isObject()) { - // Insert empty object at importObj[import.module.get()] - RootedObject plainObj(gCx, JS_NewPlainObject(gCx)); - - if (!plainObj) { - return 0; - } - - RootedValue plainVal(gCx, ObjectValue(*plainObj)); - if (!JS_SetProperty(gCx, importObj, import.module.get(), 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_GetProperty(gCx, importObj, import.module.get(), &v) || - !v.isObject()) { - return 0; - } - } - - RootedObject obj(gCx, &v.toObject()); - bool found = false; - if (JS_HasProperty(gCx, obj, import.field.get(), &found) && !found) { - // Insert i-th export object that fits the type requirement - // at `v[import.field.get()]`. - - switch (import.kind) { - case DefinitionKind::Function: - if (!assignImportKind( - import, obj, lastExportsObj, lastExportIds, - ¤tFunctionExportId, exportsLength, - emptyFunctionValue)) { - return 0; - } - break; - - case DefinitionKind::Table: - // TODO: Pass a dummy defaultValue - if (!assignImportKind( - import, obj, lastExportsObj, lastExportIds, - ¤tTableExportId, exportsLength, nullValue)) { - return 0; - } - break; - - case DefinitionKind::Memory: - // TODO: Pass a dummy defaultValue - if (!assignImportKind( - import, obj, lastExportsObj, lastExportIds, - ¤tMemoryExportId, exportsLength, nullValue)) { - return 0; - } - break; - - case DefinitionKind::Global: - // TODO: Pass a dummy defaultValue - if (!assignImportKind( - import, obj, lastExportsObj, lastExportIds, - ¤tGlobalExportId, exportsLength, nullValue)) { - return 0; - } - break; - } - } - } - - Rooted 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 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()) { - RootedFunction func(gCx, &propObj->as()); - - if (!callExportedFunc(func, &lastReturnVal)) { - return 0; - } - } - - if (propObj->is()) { - Rooted tableObj(gCx, - &propObj->as()); - size_t tableLen = tableObj->table().length(); - - RootedValue tableGetVal(gCx); - if (!JS_GetProperty(gCx, tableObj, "get", &tableGetVal)) { - return 0; - } - RootedFunction tableGet(gCx, - &tableGetVal.toObject().as()); - - 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()); - - if (!callExportedFunc(callee, &lastReturnVal)) { - return 0; - } - } - } - - if (propObj->is()) { - Rooted memory(gCx, - &propObj->as()); - 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]; - } - } - - if (propObj->is()) { - Rooted global(gCx, - &propObj->as()); - if (global->type() != ValType::I64) { - lastReturnVal = global->value(gCx); - } - } - } - } - } - - return 0; -} - -MOZ_FUZZING_INTERFACE_RAW(testWasmInit, testWasmFuzz, Wasm); diff --git a/js/src/wasm/WasmJS.cpp b/js/src/wasm/WasmJS.cpp index 7e364eec6a5b..327b0ce8a60f 100644 --- a/js/src/wasm/WasmJS.cpp +++ b/js/src/wasm/WasmJS.cpp @@ -240,7 +240,7 @@ static bool GetProperty(JSContext* cx, HandleObject obj, const char* chars, return GetProperty(cx, obj, obj, id, v); } -bool js::wasm::GetImports(JSContext* cx, const Module& module, +static bool GetImports(JSContext* cx, const Module& module, HandleObject importObj, ImportValues* imports) { if (!module.imports().empty() && !importObj) { return ThrowBadImportArg(cx); diff --git a/js/src/wasm/WasmJS.h b/js/src/wasm/WasmJS.h index 98dd43e02781..fa7ab84e5523 100644 --- a/js/src/wasm/WasmJS.h +++ b/js/src/wasm/WasmJS.h @@ -75,14 +75,6 @@ MOZ_MUST_USE bool Eval(JSContext* cx, Handle code, HandleObject importObj, MutableHandleWasmInstanceObject instanceObj); -// Extracts the various imports from the given import object into the given -// ImportValues structure while checking the imports against the given module. -// The resulting structure can be passed to WasmModule::instantiate. - -struct ImportValues; -MOZ_MUST_USE bool GetImports(JSContext* cx, const Module& module, - HandleObject importObj, ImportValues* imports); - // For testing cross-process (de)serialization, this pair of functions are // responsible for, in the child process, compiling the given wasm bytecode // to a wasm::Module that is serialized into the given byte array, and, in diff --git a/js/src/wasm/moz.build b/js/src/wasm/moz.build index 5e3385993475..b9acc0e2342e 100644 --- a/js/src/wasm/moz.build +++ b/js/src/wasm/moz.build @@ -45,7 +45,3 @@ UNIFIED_SOURCES += [ 'WasmTypes.cpp', 'WasmValidate.cpp' ] - -# Make sure all WebAssembly code is built with libfuzzer -# coverage instrumentation in FUZZING mode. -include('/tools/fuzzing/libfuzzer-config.mozbuild')