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/. */
5 #include "mozilla/ScopeExit.h"
10 #include "fuzz-tests/tests.h"
11 #include "js/CallAndConstruct.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"
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"
24 #include "vm/ArrayBufferObject-inl.h"
25 #include "vm/JSContext-inl.h"
28 using namespace js::wasm
;
30 // These are defined and pre-initialized by the harness (in tests.cpp).
31 extern JS::PersistentRootedObject gGlobal
;
32 extern JSContext
* gCx
;
34 static bool gIsWasmSmith
= false;
36 size_t gluesmith(uint8_t* data
, size_t size
, uint8_t* out
, size_t maxsize
);
39 static int testWasmInit(int* argc
, char*** argv
) {
40 if (!wasm::HasSupport(gCx
)) {
41 MOZ_CRASH("Wasm is not supported");
44 #define WASM_FEATURE(NAME, LOWER_NAME, COMPILE_PRED, COMPILER_PRED, FLAG_PRED, \
45 FLAG_FORCE_ON, FLAG_FUZZ_ON, PREF) \
46 JS::Prefs::setAtStartup_wasm_##PREF(FLAG_FUZZ_ON);
47 JS_FOR_WASM_FEATURES(WASM_FEATURE
)
50 if (!GlobalObject::getOrCreateConstructor(gCx
, JSProto_WebAssembly
)) {
51 MOZ_CRASH("Failed to initialize wasm engine");
57 static int testWasmSmithInit(int* argc
, char*** argv
) {
59 return testWasmInit(argc
, argv
);
62 static bool emptyNativeFunction(JSContext
* cx
, unsigned argc
, Value
* vp
) {
63 CallArgs args
= CallArgsFromVp(argc
, vp
);
64 args
.rval().setUndefined();
68 static bool callExportedFunc(HandleFunction func
,
69 MutableHandleValue lastReturnVal
) {
70 // TODO: We can specify a thisVal here.
71 RootedValue
thisVal(gCx
, UndefinedValue());
72 JS::RootedValueVector
args(gCx
);
74 if (!lastReturnVal
.isNull() && !lastReturnVal
.isUndefined() &&
75 !args
.append(lastReturnVal
)) {
79 RootedValue
returnVal(gCx
);
80 if (!Call(gCx
, thisVal
, func
, args
, &returnVal
)) {
81 gCx
->clearPendingException();
83 lastReturnVal
.set(returnVal
);
90 static bool assignImportKind(const Import
& import
, HandleObject obj
,
91 HandleObject lastExportsObj
,
92 JS::Handle
<JS::IdVector
> lastExportIds
,
93 size_t* currentExportId
, size_t exportsLength
,
94 HandleValue defaultValue
) {
95 RootedId
fieldName(gCx
);
96 if (!import
.field
.toPropertyKey(gCx
, &fieldName
)) {
99 bool assigned
= false;
100 while (*currentExportId
< exportsLength
) {
101 RootedValue
propVal(gCx
);
102 if (!JS_GetPropertyById(gCx
, lastExportsObj
,
103 lastExportIds
[*currentExportId
], &propVal
)) {
107 (*currentExportId
)++;
109 if (propVal
.isObject() && propVal
.toObject().is
<T
>()) {
110 if (!JS_SetPropertyById(gCx
, obj
, fieldName
, propVal
)) {
119 if (!JS_SetPropertyById(gCx
, obj
, fieldName
, defaultValue
)) {
126 static bool FuzzerBuildId(JS::BuildIdCharVector
* buildId
) {
127 const char buildid
[] = "testWasmFuzz";
128 return buildId
->append(buildid
, sizeof(buildid
));
131 static int testWasmFuzz(const uint8_t* buf
, size_t size
) {
132 auto gcGuard
= mozilla::MakeScopeExit([&] {
133 JS::PrepareForFullGC(gCx
);
134 JS::NonIncrementalGC(gCx
, JS::GCOptions::Normal
, JS::GCReason::API
);
137 JS::SetProcessBuildIdOp(FuzzerBuildId
);
139 const size_t MINIMUM_MODULE_SIZE
= 8;
141 // The smallest valid wasm module is 8 bytes and we need 1 byte for size
142 if (size
< MINIMUM_MODULE_SIZE
+ 1) return 0;
144 size_t currentIndex
= 0;
146 // Store the last non-empty exports object and its enumerated Ids here
147 RootedObject
lastExportsObj(gCx
);
148 JS::Rooted
<JS::IdVector
> lastExportIds(gCx
, JS::IdVector(gCx
));
150 // Store the last return value so we can pass it in as an argument during
151 // the next call (which can be on another module as well).
152 RootedValue
lastReturnVal(gCx
);
154 while (size
- currentIndex
>= MINIMUM_MODULE_SIZE
+ 1) {
155 // Ensure we have no lingering exceptions from previous modules
156 gCx
->clearPendingException();
160 // Jump over the optByte. Unlike with the regular format, for
161 // wasm-smith we are fixing this and use byte 0 as opt-byte.
162 // Eventually this will also be changed for the regular format.
167 // Caller ensures the structural soundness of the input here
168 moduleLen
= *((uint16_t*)&buf
[currentIndex
]);
171 moduleLen
= buf
[currentIndex
];
175 if (size
- currentIndex
< moduleLen
) {
176 moduleLen
= size
- currentIndex
;
179 if (moduleLen
< MINIMUM_MODULE_SIZE
) {
183 if (currentIndex
== 1 || (gIsWasmSmith
&& currentIndex
== 3)) {
184 // If this is the first module we are reading, we use the first
185 // few bytes to tweak some settings. These are fixed anyway and
186 // overwritten later on.
189 optByte
= (uint8_t)buf
[0];
191 optByte
= (uint8_t)buf
[currentIndex
];
194 // Note that IonPlatformSupport() does not take into account whether
195 // the compiler supports particular features that may have been enabled.
196 bool enableWasmBaseline
= ((optByte
& 0xF0) == (1 << 7));
197 bool enableWasmOptimizing
=
198 IonPlatformSupport() && ((optByte
& 0xF0) == (1 << 6));
199 bool enableWasmAwaitTier2
=
200 (IonPlatformSupport()) && ((optByte
& 0xF) == (1 << 3));
202 if (!enableWasmBaseline
&& !enableWasmOptimizing
) {
203 // If nothing is selected explicitly, enable an optimizing compiler to
204 // test more platform specific JIT code. However, on some platforms,
205 // e.g. ARM64 on Windows, we do not have Ion available, so we need to
206 // switch to baseline instead.
207 if (IonPlatformSupport()) {
208 enableWasmOptimizing
= true;
210 enableWasmBaseline
= true;
214 if (enableWasmAwaitTier2
) {
215 // Tier 2 needs Baseline + Optimizing
216 enableWasmBaseline
= true;
218 if (!enableWasmOptimizing
) {
219 enableWasmOptimizing
= true;
223 JS::ContextOptionsRef(gCx
)
224 .setWasmBaseline(enableWasmBaseline
)
225 .setWasmIon(enableWasmOptimizing
)
226 .setTestWasmAwaitTier2(enableWasmAwaitTier2
);
229 // Expected header for a valid WebAssembly module
230 uint32_t magic_header
= 0x6d736100;
231 uint32_t magic_version
= 0x1;
234 // When using wasm-smith, magic values should already be there.
235 // Checking this to make sure the data passed is sane.
236 MOZ_RELEASE_ASSERT(*(uint32_t*)(&buf
[currentIndex
]) == magic_header
,
237 "Magic header mismatch!");
238 MOZ_RELEASE_ASSERT(*(uint32_t*)(&buf
[currentIndex
+ 4]) == magic_version
,
239 "Magic version mismatch!");
242 // We just skip over the first 8 bytes now because we fill them
243 // with `magic_header` and `magic_version` anyway.
247 Rooted
<WasmInstanceObject
*> instanceObj(gCx
);
249 MutableBytes bytecode
= gCx
->new_
<ShareableBytes
>();
250 if (!bytecode
|| !bytecode
->append((uint8_t*)&magic_header
, 4) ||
251 !bytecode
->append((uint8_t*)&magic_version
, 4) ||
252 !bytecode
->append(&buf
[currentIndex
], moduleLen
)) {
256 currentIndex
+= moduleLen
;
258 ScriptedCaller scriptedCaller
;
259 FeatureOptions options
;
260 SharedCompileArgs compileArgs
=
261 CompileArgs::buildAndReport(gCx
, std::move(scriptedCaller
), options
);
267 UniqueCharsVector warnings
;
268 SharedModule module
=
269 CompileBuffer(*compileArgs
, *bytecode
, &error
, &warnings
);
271 // We should always have a valid module if we are using wasm-smith. Check
272 // that no error is reported, signalling an OOM.
273 MOZ_RELEASE_ASSERT(!gIsWasmSmith
|| !error
);
277 // At this point we have a valid module and we should try to ensure
278 // that its import requirements are met for instantiation.
279 const ImportVector
& importVec
= module
->imports();
281 // Empty native function used to fill in function import slots if we
282 // run out of functions exported by other modules.
283 JS::RootedFunction
emptyFunction(gCx
);
285 JS_NewFunction(gCx
, emptyNativeFunction
, 0, 0, "emptyFunction");
287 if (!emptyFunction
) {
291 RootedValue
emptyFunctionValue(gCx
, ObjectValue(*emptyFunction
));
292 RootedValue
nullValue(gCx
, NullValue());
294 RootedObject
importObj(gCx
, JS_NewPlainObject(gCx
));
300 size_t exportsLength
= lastExportIds
.length();
301 size_t currentFunctionExportId
= 0;
302 size_t currentTableExportId
= 0;
303 size_t currentMemoryExportId
= 0;
304 size_t currentGlobalExportId
= 0;
305 size_t currentTagExportId
= 0;
307 for (const Import
& import
: importVec
) {
308 RootedId
moduleName(gCx
);
309 if (!import
.module
.toPropertyKey(gCx
, &moduleName
)) {
312 RootedId
fieldName(gCx
);
313 if (!import
.field
.toPropertyKey(gCx
, &fieldName
)) {
317 // First try to get the namespace object, create one if this is the
320 if (!JS_GetPropertyById(gCx
, importObj
, moduleName
, &v
) ||
322 // Insert empty object at importObj[moduleName]
323 RootedObject
plainObj(gCx
, JS_NewPlainObject(gCx
));
329 RootedValue
plainVal(gCx
, ObjectValue(*plainObj
));
330 if (!JS_SetPropertyById(gCx
, importObj
, moduleName
, plainVal
)) {
334 // Get the object we just inserted, store in v, ensure it is an
335 // object (no proxies or other magic at work).
336 if (!JS_GetPropertyById(gCx
, importObj
, moduleName
, &v
) ||
342 RootedObject
obj(gCx
, &v
.toObject());
344 if (JS_HasPropertyById(gCx
, obj
, fieldName
, &found
) && !found
) {
345 // Insert i-th export object that fits the type requirement
346 // at `v[fieldName]`.
348 switch (import
.kind
) {
349 case DefinitionKind::Function
:
350 if (!assignImportKind
<JSFunction
>(
351 import
, obj
, lastExportsObj
, lastExportIds
,
352 ¤tFunctionExportId
, exportsLength
,
353 emptyFunctionValue
)) {
358 case DefinitionKind::Table
:
359 // TODO: Pass a dummy defaultValue
360 if (!assignImportKind
<WasmTableObject
>(
361 import
, obj
, lastExportsObj
, lastExportIds
,
362 ¤tTableExportId
, exportsLength
, nullValue
)) {
367 case DefinitionKind::Memory
:
368 // TODO: Pass a dummy defaultValue
369 if (!assignImportKind
<WasmMemoryObject
>(
370 import
, obj
, lastExportsObj
, lastExportIds
,
371 ¤tMemoryExportId
, exportsLength
, nullValue
)) {
376 case DefinitionKind::Global
:
377 // TODO: Pass a dummy defaultValue
378 if (!assignImportKind
<WasmGlobalObject
>(
379 import
, obj
, lastExportsObj
, lastExportIds
,
380 ¤tGlobalExportId
, exportsLength
, nullValue
)) {
385 case DefinitionKind::Tag
:
386 // TODO: Pass a dummy defaultValue
387 if (!assignImportKind
<WasmTagObject
>(
388 import
, obj
, lastExportsObj
, lastExportIds
,
389 ¤tTagExportId
, exportsLength
, nullValue
)) {
397 Rooted
<ImportValues
> imports(gCx
);
398 if (!GetImports(gCx
, *module
, importObj
, imports
.address())) {
402 if (!module
->instantiate(gCx
, imports
.get(), nullptr, &instanceObj
)) {
406 // At this module we have a valid WebAssembly module instance.
408 RootedObject
exportsObj(gCx
, &instanceObj
->exportsObj());
409 JS::Rooted
<JS::IdVector
> exportIds(gCx
, JS::IdVector(gCx
));
410 if (!JS_Enumerate(gCx
, exportsObj
, &exportIds
)) {
414 if (!exportIds
.length()) {
418 // Store the last exports for re-use later
419 lastExportsObj
= exportsObj
;
420 lastExportIds
.get() = std::move(exportIds
.get());
422 for (size_t i
= 0; i
< lastExportIds
.length(); i
++) {
423 RootedValue
propVal(gCx
);
424 if (!JS_GetPropertyById(gCx
, exportsObj
, lastExportIds
[i
], &propVal
)) {
428 if (propVal
.isObject()) {
429 RootedObject
propObj(gCx
, &propVal
.toObject());
431 if (propObj
->is
<JSFunction
>()) {
432 RootedFunction
func(gCx
, &propObj
->as
<JSFunction
>());
434 if (!callExportedFunc(func
, &lastReturnVal
)) {
439 if (propObj
->is
<WasmTableObject
>()) {
440 Rooted
<WasmTableObject
*> tableObj(gCx
,
441 &propObj
->as
<WasmTableObject
>());
442 size_t tableLen
= tableObj
->table().length();
444 RootedValue
tableGetVal(gCx
);
445 if (!JS_GetProperty(gCx
, tableObj
, "get", &tableGetVal
)) {
448 RootedFunction
tableGet(gCx
,
449 &tableGetVal
.toObject().as
<JSFunction
>());
451 for (size_t i
= 0; i
< tableLen
; i
++) {
452 JS::RootedValueVector
tableGetArgs(gCx
);
453 if (!tableGetArgs
.append(NumberValue(uint32_t(i
)))) {
457 RootedValue
readFuncValue(gCx
);
458 if (!Call(gCx
, tableObj
, tableGet
, tableGetArgs
, &readFuncValue
)) {
462 if (readFuncValue
.isNull()) {
466 RootedFunction
callee(gCx
,
467 &readFuncValue
.toObject().as
<JSFunction
>());
469 if (!callExportedFunc(callee
, &lastReturnVal
)) {
475 if (propObj
->is
<WasmMemoryObject
>()) {
476 Rooted
<WasmMemoryObject
*> memory(gCx
,
477 &propObj
->as
<WasmMemoryObject
>());
478 size_t byteLen
= memory
->volatileMemoryLength();
480 // Read the bounds of the buffer to ensure it is valid.
481 // AddressSanitizer would detect any out-of-bounds here.
482 uint8_t* rawMemory
= memory
->buffer().dataPointerEither().unwrap();
483 volatile uint8_t rawMemByte
= 0;
484 rawMemByte
+= rawMemory
[0];
485 rawMemByte
+= rawMemory
[byteLen
- 1];
490 if (propObj
->is
<WasmGlobalObject
>()) {
491 Rooted
<WasmGlobalObject
*> global(gCx
,
492 &propObj
->as
<WasmGlobalObject
>());
493 if (global
->type() != ValType::I64
) {
494 global
->val().get().toJSValue(gCx
, &lastReturnVal
);
504 static int testWasmSmithFuzz(const uint8_t* buf
, size_t size
) {
505 // Define maximum sizes for the input to wasm-smith as well
506 // as the resulting modules. The input to output size factor
507 // of wasm-smith is somewhat variable but a factor of 4 seems
508 // to roughly work out. The logic below also assumes that these
510 const size_t maxInputSize
= 1024;
511 const size_t maxModuleSize
= 4096;
513 size_t maxModules
= size
/ maxInputSize
+ 1;
515 // We need 1 leading byte for options and 2 bytes for size per module
517 new uint8_t[1 + maxModules
* (maxModuleSize
+ sizeof(uint16_t))];
519 auto deleteGuard
= mozilla::MakeScopeExit([&] { delete[] out
; });
521 // Copy the opt-byte.
525 size_t currentIndex
= 1;
527 while (currentIndex
< size
) {
528 size_t remaining
= size
- currentIndex
;
530 // We need to have at least a size and some byte to read.
531 if (remaining
<= sizeof(uint16_t)) {
535 // Determine size of the next input, limited to `maxInputSize`.
537 (*((uint16_t*)&buf
[currentIndex
]) & (maxInputSize
- 1)) + 1;
538 remaining
-= sizeof(uint16_t);
539 currentIndex
+= sizeof(uint16_t);
541 // Cap to remaining bytes.
542 inSize
= remaining
>= inSize
? inSize
: remaining
;
545 gluesmith((uint8_t*)&buf
[currentIndex
], inSize
,
546 out
+ outIndex
+ sizeof(uint16_t), maxModuleSize
);
552 currentIndex
+= inSize
;
554 // Write the size of the resulting module to our output buffer.
555 *(uint16_t*)(&out
[outIndex
]) = (uint16_t)outSize
;
556 outIndex
+= sizeof(uint16_t) + outSize
;
559 // If we lack at least one module, don't do anything.
564 return testWasmFuzz(out
, outIndex
);
567 MOZ_FUZZING_INTERFACE_RAW(testWasmInit
, testWasmFuzz
, Wasm
);
568 MOZ_FUZZING_INTERFACE_RAW(testWasmSmithInit
, testWasmSmithFuzz
, WasmSmith
);