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 ¤tFunctionExportId, 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 ¤tTableExportId, 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 ¤tMemoryExportId, 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 ¤tGlobalExportId, 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 ¤tTagExportId, 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);