tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

testWasm.cpp (18806B)


      1 /* This Source Code Form is subject to the terms of the Mozilla Public
      2 * License, v. 2.0. If a copy of the MPL was not distributed with this
      3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
      4 
      5 #include "mozilla/ScopeExit.h"
      6 
      7 #include "jsapi.h"
      8 #include "jspubtd.h"
      9 
     10 #include "fuzz-tests/tests.h"
     11 #include "js/CallAndConstruct.h"
     12 #include "js/Prefs.h"
     13 #include "js/PropertyAndElement.h"  // JS_Enumerate, JS_GetProperty, JS_GetPropertyById, JS_HasProperty, JS_SetProperty
     14 #include "vm/GlobalObject.h"
     15 #include "vm/Interpreter.h"
     16 #include "vm/TypedArrayObject.h"
     17 
     18 #include "wasm/WasmCompile.h"
     19 #include "wasm/WasmFeatures.h"
     20 #include "wasm/WasmIonCompile.h"
     21 #include "wasm/WasmJS.h"
     22 #include "wasm/WasmTable.h"
     23 
     24 #include "vm/ArrayBufferObject-inl.h"
     25 #include "vm/JSContext-inl.h"
     26 
     27 using namespace js;
     28 using namespace js::wasm;
     29 
     30 // These are defined and pre-initialized by the harness (in tests.cpp).
     31 extern JS::PersistentRootedObject gGlobal;
     32 extern JSContext* gCx;
     33 
     34 static bool gIsWasmSmith = false;
     35 extern "C" {
     36 size_t gluesmith(uint8_t* data, size_t size, uint8_t* out, size_t maxsize);
     37 }
     38 
     39 // Filter and set only "always" preferences. "startup" preferences are
     40 // not allowed to be set after JS_Init.
     41 struct PrefsSetters {
     42 #define JS_PREF_SETTER(NAME, CPP_NAME, TYPE, SETTER_NAME, IS_STARTUP) \
     43  template <typename T>                                               \
     44  static void set_##CPP_NAME(T value) {                               \
     45    if constexpr (!IS_STARTUP) {                                      \
     46      JS::Prefs::SETTER_NAME(value);                                  \
     47    }                                                                 \
     48  }
     49  FOR_EACH_JS_PREF(JS_PREF_SETTER)
     50 #undef JS_PREF_SETTER
     51 };
     52 
     53 static int testWasmInit(int* argc, char*** argv) {
     54  if (!wasm::HasSupport(gCx)) {
     55    MOZ_CRASH("Wasm is not supported");
     56  }
     57 
     58 #define WASM_FEATURE(NAME, LOWER_NAME, COMPILE_PRED, COMPILER_PRED, FLAG_PRED, \
     59                     FLAG_FORCE_ON, FLAG_FUZZ_ON, PREF)                        \
     60  PrefsSetters::set_wasm_##PREF(FLAG_FUZZ_ON);
     61  JS_FOR_WASM_FEATURES(WASM_FEATURE)
     62 #undef WASM_FEATURE
     63 
     64  if (!GlobalObject::getOrCreateConstructor(gCx, JSProto_WebAssembly)) {
     65    MOZ_CRASH("Failed to initialize wasm engine");
     66  }
     67 
     68  return 0;
     69 }
     70 
     71 static int testWasmSmithInit(int* argc, char*** argv) {
     72  gIsWasmSmith = true;
     73  return testWasmInit(argc, argv);
     74 }
     75 
     76 static bool emptyNativeFunction(JSContext* cx, unsigned argc, Value* vp) {
     77  CallArgs args = CallArgsFromVp(argc, vp);
     78  args.rval().setUndefined();
     79  return true;
     80 }
     81 
     82 static bool callExportedFunc(HandleFunction func,
     83                             MutableHandleValue lastReturnVal) {
     84  // TODO: We can specify a thisVal here.
     85  RootedValue thisVal(gCx, UndefinedValue());
     86  JS::RootedValueVector args(gCx);
     87 
     88  if (!lastReturnVal.isNull() && !lastReturnVal.isUndefined() &&
     89      !args.append(lastReturnVal)) {
     90    return false;
     91  }
     92 
     93  RootedValue returnVal(gCx);
     94  if (!Call(gCx, thisVal, func, args, &returnVal)) {
     95    gCx->clearPendingException();
     96  } else {
     97    lastReturnVal.set(returnVal);
     98  }
     99 
    100  return true;
    101 }
    102 
    103 template <typename T>
    104 static bool assignImportKind(const Import& import, HandleObject obj,
    105                             HandleObject lastExportsObj,
    106                             JS::Handle<JS::IdVector> lastExportIds,
    107                             size_t* currentExportId, size_t exportsLength,
    108                             HandleValue defaultValue) {
    109  RootedId fieldName(gCx);
    110  if (!import.field.toPropertyKey(gCx, &fieldName)) {
    111    return false;
    112  }
    113  bool assigned = false;
    114  while (*currentExportId < exportsLength) {
    115    RootedValue propVal(gCx);
    116    if (!JS_GetPropertyById(gCx, lastExportsObj,
    117                            lastExportIds[*currentExportId], &propVal)) {
    118      return false;
    119    }
    120 
    121    (*currentExportId)++;
    122 
    123    if (propVal.isObject() && propVal.toObject().is<T>()) {
    124      if (!JS_SetPropertyById(gCx, obj, fieldName, propVal)) {
    125        return false;
    126      }
    127 
    128      assigned = true;
    129      break;
    130    }
    131  }
    132  if (!assigned) {
    133    if (!JS_SetPropertyById(gCx, obj, fieldName, defaultValue)) {
    134      return false;
    135    }
    136  }
    137  return true;
    138 }
    139 
    140 static bool FuzzerBuildId(JS::BuildIdCharVector* buildId) {
    141  const char buildid[] = "testWasmFuzz";
    142  return buildId->append(buildid, sizeof(buildid));
    143 }
    144 
    145 static int testWasmFuzz(const uint8_t* buf, size_t size) {
    146  auto gcGuard = mozilla::MakeScopeExit([&] {
    147    JS::PrepareForFullGC(gCx);
    148    JS::NonIncrementalGC(gCx, JS::GCOptions::Normal, JS::GCReason::API);
    149  });
    150 
    151  JS::SetProcessBuildIdOp(FuzzerBuildId);
    152 
    153  const size_t MINIMUM_MODULE_SIZE = 8;
    154 
    155  // The smallest valid wasm module is 8 bytes and we need 1 byte for size
    156  if (size < MINIMUM_MODULE_SIZE + 1) return 0;
    157 
    158  size_t currentIndex = 0;
    159 
    160  // Store the last non-empty exports object and its enumerated Ids here
    161  RootedObject lastExportsObj(gCx);
    162  JS::Rooted<JS::IdVector> lastExportIds(gCx, JS::IdVector(gCx));
    163 
    164  // Store the last return value so we can pass it in as an argument during
    165  // the next call (which can be on another module as well).
    166  RootedValue lastReturnVal(gCx);
    167 
    168  while (size - currentIndex >= MINIMUM_MODULE_SIZE + 1) {
    169    // Ensure we have no lingering exceptions from previous modules
    170    gCx->clearPendingException();
    171 
    172    uint16_t moduleLen;
    173    if (gIsWasmSmith) {
    174      // Jump over the optByte. Unlike with the regular format, for
    175      // wasm-smith we are fixing this and use byte 0 as opt-byte.
    176      // Eventually this will also be changed for the regular format.
    177      if (!currentIndex) {
    178        currentIndex++;
    179      }
    180 
    181      // Caller ensures the structural soundness of the input here
    182      moduleLen = *((uint16_t*)&buf[currentIndex]);
    183      currentIndex += 2;
    184    } else {
    185      moduleLen = buf[currentIndex];
    186      currentIndex++;
    187    }
    188 
    189    if (size - currentIndex < moduleLen) {
    190      moduleLen = size - currentIndex;
    191    }
    192 
    193    if (moduleLen < MINIMUM_MODULE_SIZE) {
    194      continue;
    195    }
    196 
    197    if (currentIndex == 1 || (gIsWasmSmith && currentIndex == 3)) {
    198      // If this is the first module we are reading, we use the first
    199      // few bytes to tweak some settings. These are fixed anyway and
    200      // overwritten later on.
    201      uint8_t optByte;
    202      if (gIsWasmSmith) {
    203        optByte = (uint8_t)buf[0];
    204      } else {
    205        optByte = (uint8_t)buf[currentIndex];
    206      }
    207 
    208      // Note that IonPlatformSupport() does not take into account whether
    209      // the compiler supports particular features that may have been enabled.
    210      bool enableWasmBaseline = ((optByte & 0xF0) == (1 << 7));
    211      bool enableWasmOptimizing =
    212          IonPlatformSupport() && ((optByte & 0xF0) == (1 << 6));
    213      bool enableWasmAwaitTier2 =
    214          (IonPlatformSupport()) && ((optByte & 0xF) == (1 << 3));
    215 
    216      if (!enableWasmBaseline && !enableWasmOptimizing) {
    217        // If nothing is selected explicitly, enable an optimizing compiler to
    218        // test more platform specific JIT code. However, on some platforms,
    219        // e.g. ARM64 on Windows, we do not have Ion available, so we need to
    220        // switch to baseline instead.
    221        if (IonPlatformSupport()) {
    222          enableWasmOptimizing = true;
    223        } else {
    224          enableWasmBaseline = true;
    225        }
    226      }
    227 
    228      if (enableWasmAwaitTier2) {
    229        // Tier 2 needs Baseline + Optimizing
    230        enableWasmBaseline = true;
    231 
    232        if (!enableWasmOptimizing) {
    233          enableWasmOptimizing = true;
    234        }
    235      }
    236 
    237      JS::ContextOptionsRef(gCx)
    238          .setWasmBaseline(enableWasmBaseline)
    239          .setWasmIon(enableWasmOptimizing)
    240          .setTestWasmAwaitTier2(enableWasmAwaitTier2);
    241    }
    242 
    243    // Expected header for a valid WebAssembly module
    244    uint32_t magic_header = 0x6d736100;
    245    uint32_t magic_version = 0x1;
    246 
    247    if (gIsWasmSmith) {
    248      // When using wasm-smith, magic values should already be there.
    249      // Checking this to make sure the data passed is sane.
    250      MOZ_RELEASE_ASSERT(*(uint32_t*)(&buf[currentIndex]) == magic_header,
    251                         "Magic header mismatch!");
    252      MOZ_RELEASE_ASSERT(*(uint32_t*)(&buf[currentIndex + 4]) == magic_version,
    253                         "Magic version mismatch!");
    254    }
    255 
    256    // We just skip over the first 8 bytes now because we fill them
    257    // with `magic_header` and `magic_version` anyway.
    258    currentIndex += 8;
    259    moduleLen -= 8;
    260 
    261    Rooted<WasmInstanceObject*> instanceObj(gCx);
    262 
    263    MutableBytes bytecode = gCx->new_<ShareableBytes>();
    264    if (!bytecode || !bytecode->append((uint8_t*)&magic_header, 4) ||
    265        !bytecode->append((uint8_t*)&magic_version, 4) ||
    266        !bytecode->append(&buf[currentIndex], moduleLen)) {
    267      return 0;
    268    }
    269    BytecodeSource bytecodeSource(bytecode->begin(), bytecode->length());
    270 
    271    currentIndex += moduleLen;
    272 
    273    ScriptedCaller scriptedCaller;
    274    FeatureOptions options;
    275    SharedCompileArgs compileArgs =
    276        CompileArgs::buildAndReport(gCx, std::move(scriptedCaller), options);
    277    if (!compileArgs) {
    278      return 0;
    279    }
    280 
    281    UniqueChars error;
    282    UniqueCharsVector warnings;
    283    SharedModule module =
    284        CompileBuffer(*compileArgs, BytecodeBufferOrSource(bytecodeSource),
    285                      &error, &warnings);
    286    if (!module) {
    287      // We should always have a valid module if we are using wasm-smith. Check
    288      // that no error is reported, signalling an OOM.
    289      MOZ_RELEASE_ASSERT(!gIsWasmSmith || !error);
    290      continue;
    291    }
    292 
    293    // At this point we have a valid module and we should try to ensure
    294    // that its import requirements are met for instantiation.
    295    const ImportVector& importVec = module->moduleMeta().imports;
    296 
    297    // Empty native function used to fill in function import slots if we
    298    // run out of functions exported by other modules.
    299    JS::RootedFunction emptyFunction(gCx);
    300    emptyFunction =
    301        JS_NewFunction(gCx, emptyNativeFunction, 0, 0, "emptyFunction");
    302 
    303    if (!emptyFunction) {
    304      return 0;
    305    }
    306 
    307    RootedValue emptyFunctionValue(gCx, ObjectValue(*emptyFunction));
    308    RootedValue nullValue(gCx, NullValue());
    309 
    310    RootedObject importObj(gCx, JS_NewPlainObject(gCx));
    311 
    312    if (!importObj) {
    313      return 0;
    314    }
    315 
    316    size_t exportsLength = lastExportIds.length();
    317    size_t currentFunctionExportId = 0;
    318    size_t currentTableExportId = 0;
    319    size_t currentMemoryExportId = 0;
    320    size_t currentGlobalExportId = 0;
    321    size_t currentTagExportId = 0;
    322 
    323    for (const Import& import : importVec) {
    324      RootedId moduleName(gCx);
    325      if (!import.module.toPropertyKey(gCx, &moduleName)) {
    326        return false;
    327      }
    328      RootedId fieldName(gCx);
    329      if (!import.field.toPropertyKey(gCx, &fieldName)) {
    330        return false;
    331      }
    332 
    333      // First try to get the namespace object, create one if this is the
    334      // first time.
    335      RootedValue v(gCx);
    336      if (!JS_GetPropertyById(gCx, importObj, moduleName, &v) ||
    337          !v.isObject()) {
    338        // Insert empty object at importObj[moduleName]
    339        RootedObject plainObj(gCx, JS_NewPlainObject(gCx));
    340 
    341        if (!plainObj) {
    342          return 0;
    343        }
    344 
    345        RootedValue plainVal(gCx, ObjectValue(*plainObj));
    346        if (!JS_SetPropertyById(gCx, importObj, moduleName, plainVal)) {
    347          return 0;
    348        }
    349 
    350        // Get the object we just inserted, store in v, ensure it is an
    351        // object (no proxies or other magic at work).
    352        if (!JS_GetPropertyById(gCx, importObj, moduleName, &v) ||
    353            !v.isObject()) {
    354          return 0;
    355        }
    356      }
    357 
    358      RootedObject obj(gCx, &v.toObject());
    359      bool found = false;
    360      if (JS_HasPropertyById(gCx, obj, fieldName, &found) && !found) {
    361        // Insert i-th export object that fits the type requirement
    362        // at `v[fieldName]`.
    363 
    364        switch (import.kind) {
    365          case DefinitionKind::Function:
    366            if (!assignImportKind<JSFunction>(
    367                    import, obj, lastExportsObj, lastExportIds,
    368                    &currentFunctionExportId, exportsLength,
    369                    emptyFunctionValue)) {
    370              return 0;
    371            }
    372            break;
    373 
    374          case DefinitionKind::Table:
    375            // TODO: Pass a dummy defaultValue
    376            if (!assignImportKind<WasmTableObject>(
    377                    import, obj, lastExportsObj, lastExportIds,
    378                    &currentTableExportId, exportsLength, nullValue)) {
    379              return 0;
    380            }
    381            break;
    382 
    383          case DefinitionKind::Memory:
    384            // TODO: Pass a dummy defaultValue
    385            if (!assignImportKind<WasmMemoryObject>(
    386                    import, obj, lastExportsObj, lastExportIds,
    387                    &currentMemoryExportId, exportsLength, nullValue)) {
    388              return 0;
    389            }
    390            break;
    391 
    392          case DefinitionKind::Global:
    393            // TODO: Pass a dummy defaultValue
    394            if (!assignImportKind<WasmGlobalObject>(
    395                    import, obj, lastExportsObj, lastExportIds,
    396                    &currentGlobalExportId, exportsLength, nullValue)) {
    397              return 0;
    398            }
    399            break;
    400 
    401          case DefinitionKind::Tag:
    402            // TODO: Pass a dummy defaultValue
    403            if (!assignImportKind<WasmTagObject>(
    404                    import, obj, lastExportsObj, lastExportIds,
    405                    &currentTagExportId, exportsLength, nullValue)) {
    406              return 0;
    407            }
    408            break;
    409        }
    410      }
    411    }
    412 
    413    Rooted<ImportValues> imports(gCx);
    414    if (!GetImports(gCx, *module, importObj, imports.address())) {
    415      continue;
    416    }
    417 
    418    if (!module->instantiate(gCx, imports.get(), nullptr, &instanceObj)) {
    419      continue;
    420    }
    421 
    422    // At this module we have a valid WebAssembly module instance.
    423 
    424    RootedObject exportsObj(gCx, &instanceObj->exportsObj());
    425    JS::Rooted<JS::IdVector> exportIds(gCx, JS::IdVector(gCx));
    426    if (!JS_Enumerate(gCx, exportsObj, &exportIds)) {
    427      continue;
    428    }
    429 
    430    if (!exportIds.length()) {
    431      continue;
    432    }
    433 
    434    // Store the last exports for re-use later
    435    lastExportsObj = exportsObj;
    436    lastExportIds.get() = std::move(exportIds.get());
    437 
    438    for (size_t i = 0; i < lastExportIds.length(); i++) {
    439      RootedValue propVal(gCx);
    440      if (!JS_GetPropertyById(gCx, exportsObj, lastExportIds[i], &propVal)) {
    441        return 0;
    442      }
    443 
    444      if (propVal.isObject()) {
    445        RootedObject propObj(gCx, &propVal.toObject());
    446 
    447        if (propObj->is<JSFunction>()) {
    448          RootedFunction func(gCx, &propObj->as<JSFunction>());
    449 
    450          if (!callExportedFunc(func, &lastReturnVal)) {
    451            return 0;
    452          }
    453        }
    454 
    455        if (propObj->is<WasmTableObject>()) {
    456          Rooted<WasmTableObject*> tableObj(gCx,
    457                                            &propObj->as<WasmTableObject>());
    458          size_t tableLen = tableObj->table().length();
    459 
    460          RootedValue tableGetVal(gCx);
    461          if (!JS_GetProperty(gCx, tableObj, "get", &tableGetVal)) {
    462            return 0;
    463          }
    464          RootedFunction tableGet(gCx,
    465                                  &tableGetVal.toObject().as<JSFunction>());
    466 
    467          for (size_t i = 0; i < tableLen; i++) {
    468            JS::RootedValueVector tableGetArgs(gCx);
    469            if (!tableGetArgs.append(NumberValue(uint32_t(i)))) {
    470              return 0;
    471            }
    472 
    473            RootedValue readFuncValue(gCx);
    474            if (!Call(gCx, tableObj, tableGet, tableGetArgs, &readFuncValue)) {
    475              return 0;
    476            }
    477 
    478            if (readFuncValue.isNull()) {
    479              continue;
    480            }
    481 
    482            RootedFunction callee(gCx,
    483                                  &readFuncValue.toObject().as<JSFunction>());
    484 
    485            if (!callExportedFunc(callee, &lastReturnVal)) {
    486              return 0;
    487            }
    488          }
    489        }
    490 
    491        if (propObj->is<WasmMemoryObject>()) {
    492          Rooted<WasmMemoryObject*> memory(gCx,
    493                                           &propObj->as<WasmMemoryObject>());
    494          size_t byteLen = memory->volatileMemoryLength();
    495          if (byteLen) {
    496            // Read the bounds of the buffer to ensure it is valid.
    497            // AddressSanitizer would detect any out-of-bounds here.
    498            uint8_t* rawMemory = memory->buffer().dataPointerEither().unwrap();
    499            volatile uint8_t rawMemByte = 0;
    500            rawMemByte += rawMemory[0];
    501            rawMemByte += rawMemory[byteLen - 1];
    502            (void)rawMemByte;
    503          }
    504        }
    505 
    506        if (propObj->is<WasmGlobalObject>()) {
    507          Rooted<WasmGlobalObject*> global(gCx,
    508                                           &propObj->as<WasmGlobalObject>());
    509          if (global->type() != ValType::I64) {
    510            global->val().get().toJSValue(gCx, &lastReturnVal);
    511          }
    512        }
    513      }
    514    }
    515  }
    516 
    517  return 0;
    518 }
    519 
    520 static int testWasmSmithFuzz(const uint8_t* buf, size_t size) {
    521  // Define maximum sizes for the input to wasm-smith as well
    522  // as the resulting modules. The input to output size factor
    523  // of wasm-smith is somewhat variable but a factor of 4 seems
    524  // to roughly work out. The logic below also assumes that these
    525  // are powers of 2.
    526  const size_t maxInputSize = 1024;
    527  const size_t maxModuleSize = 4096;
    528 
    529  size_t maxModules = size / maxInputSize + 1;
    530 
    531  // We need 1 leading byte for options and 2 bytes for size per module
    532  uint8_t* out =
    533      new uint8_t[1 + maxModules * (maxModuleSize + sizeof(uint16_t))];
    534 
    535  auto deleteGuard = mozilla::MakeScopeExit([&] { delete[] out; });
    536 
    537  // Copy the opt-byte.
    538  out[0] = buf[0];
    539 
    540  size_t outIndex = 1;
    541  size_t currentIndex = 1;
    542 
    543  while (currentIndex < size) {
    544    size_t remaining = size - currentIndex;
    545 
    546    // We need to have at least a size and some byte to read.
    547    if (remaining <= sizeof(uint16_t)) {
    548      break;
    549    }
    550 
    551    // Determine size of the next input, limited to `maxInputSize`.
    552    uint16_t inSize =
    553        (*((uint16_t*)&buf[currentIndex]) & (maxInputSize - 1)) + 1;
    554    remaining -= sizeof(uint16_t);
    555    currentIndex += sizeof(uint16_t);
    556 
    557    // Cap to remaining bytes.
    558    inSize = remaining >= inSize ? inSize : remaining;
    559 
    560    size_t outSize =
    561        gluesmith((uint8_t*)&buf[currentIndex], inSize,
    562                  out + outIndex + sizeof(uint16_t), maxModuleSize);
    563 
    564    if (!outSize) {
    565      break;
    566    }
    567 
    568    currentIndex += inSize;
    569 
    570    // Write the size of the resulting module to our output buffer.
    571    *(uint16_t*)(&out[outIndex]) = (uint16_t)outSize;
    572    outIndex += sizeof(uint16_t) + outSize;
    573  }
    574 
    575  // If we lack at least one module, don't do anything.
    576  if (outIndex == 1) {
    577    return 0;
    578  }
    579 
    580  return testWasmFuzz(out, outIndex);
    581 }
    582 
    583 MOZ_FUZZING_INTERFACE_RAW(testWasmInit, testWasmFuzz, Wasm);
    584 MOZ_FUZZING_INTERFACE_RAW(testWasmSmithInit, testWasmSmithFuzz, WasmSmith);