Line data Source code
1 : // Copyright 2016 the V8 project authors. All rights reserved.
2 : // Use of this source code is governed by a BSD-style license that can be
3 : // found in the LICENSE file.
4 :
5 : #include "test/fuzzer/wasm-fuzzer-common.h"
6 :
7 : #include <ctime>
8 :
9 : #include "include/v8.h"
10 : #include "src/isolate.h"
11 : #include "src/objects-inl.h"
12 : #include "src/ostreams.h"
13 : #include "src/wasm/wasm-engine.h"
14 : #include "src/wasm/wasm-module-builder.h"
15 : #include "src/wasm/wasm-module.h"
16 : #include "src/wasm/wasm-objects-inl.h"
17 : #include "src/zone/accounting-allocator.h"
18 : #include "src/zone/zone.h"
19 : #include "test/common/wasm/flag-utils.h"
20 : #include "test/common/wasm/wasm-module-runner.h"
21 : #include "test/fuzzer/fuzzer-support.h"
22 :
23 : namespace v8 {
24 : namespace internal {
25 : namespace wasm {
26 : namespace fuzzer {
27 :
28 2 : void InterpretAndExecuteModule(i::Isolate* isolate,
29 : Handle<WasmModuleObject> module_object) {
30 : // We do not instantiate the module if there is a start function, because a
31 : // start function can contain an infinite loop which we cannot handle.
32 6 : if (module_object->module()->start_function_index >= 0) return;
33 :
34 : ErrorThrower thrower(isolate, "WebAssembly Instantiation");
35 : MaybeHandle<WasmInstanceObject> maybe_instance;
36 : Handle<WasmInstanceObject> instance;
37 :
38 : // Try to instantiate and interpret the module_object.
39 : maybe_instance = isolate->wasm_engine()->SyncInstantiate(
40 : isolate, &thrower, module_object,
41 : Handle<JSReceiver>::null(), // imports
42 4 : MaybeHandle<JSArrayBuffer>()); // memory
43 2 : if (!maybe_instance.ToHandle(&instance)) {
44 1 : isolate->clear_pending_exception();
45 1 : thrower.Reset(); // Ignore errors.
46 1 : return;
47 : }
48 1 : if (!testing::InterpretWasmModuleForTesting(isolate, instance, "main", 0,
49 1 : nullptr)) {
50 1 : isolate->clear_pending_exception();
51 1 : return;
52 : }
53 :
54 : // Try to instantiate and execute the module_object.
55 : maybe_instance = isolate->wasm_engine()->SyncInstantiate(
56 : isolate, &thrower, module_object,
57 : Handle<JSReceiver>::null(), // imports
58 0 : MaybeHandle<JSArrayBuffer>()); // memory
59 0 : if (!maybe_instance.ToHandle(&instance)) {
60 0 : isolate->clear_pending_exception();
61 0 : thrower.Reset(); // Ignore errors.
62 0 : return;
63 : }
64 0 : if (testing::RunWasmModuleForTesting(isolate, instance, 0, nullptr) < 0) {
65 0 : isolate->clear_pending_exception();
66 0 : return;
67 0 : }
68 : }
69 :
70 : namespace {
71 : struct PrintSig {
72 : const size_t num;
73 : const std::function<ValueType(size_t)> getter;
74 : };
75 0 : PrintSig PrintParameters(const FunctionSig* sig) {
76 0 : return {sig->parameter_count(), [=](size_t i) { return sig->GetParam(i); }};
77 : }
78 0 : PrintSig PrintReturns(const FunctionSig* sig) {
79 0 : return {sig->return_count(), [=](size_t i) { return sig->GetReturn(i); }};
80 : }
81 0 : const char* ValueTypeToConstantName(ValueType type) {
82 0 : switch (type) {
83 : case kWasmI32:
84 : return "kWasmI32";
85 : case kWasmI64:
86 0 : return "kWasmI64";
87 : case kWasmF32:
88 0 : return "kWasmF32";
89 : case kWasmF64:
90 0 : return "kWasmF64";
91 : default:
92 0 : UNREACHABLE();
93 : }
94 : }
95 0 : std::ostream& operator<<(std::ostream& os, const PrintSig& print) {
96 0 : os << "[";
97 0 : for (size_t i = 0; i < print.num; ++i) {
98 0 : os << (i == 0 ? "" : ", ") << ValueTypeToConstantName(print.getter(i));
99 : }
100 0 : return os << "]";
101 : }
102 :
103 : struct PrintName {
104 : WasmName name;
105 : PrintName(ModuleWireBytes wire_bytes, WireBytesRef ref)
106 0 : : name(wire_bytes.GetNameOrNull(ref)) {}
107 : };
108 : std::ostream& operator<<(std::ostream& os, const PrintName& name) {
109 0 : return os.write(name.name.start(), name.name.size());
110 : }
111 : } // namespace
112 :
113 0 : void GenerateTestCase(Isolate* isolate, ModuleWireBytes wire_bytes,
114 : bool compiles) {
115 : constexpr bool kVerifyFunctions = false;
116 0 : auto enabled_features = i::wasm::WasmFeaturesFromIsolate(isolate);
117 : ModuleResult module_res = DecodeWasmModule(
118 : enabled_features, wire_bytes.start(), wire_bytes.end(), kVerifyFunctions,
119 0 : ModuleOrigin::kWasmOrigin, isolate->counters(), isolate->allocator());
120 0 : CHECK(module_res.ok());
121 0 : WasmModule* module = module_res.value().get();
122 0 : CHECK_NOT_NULL(module);
123 :
124 0 : StdoutStream os;
125 :
126 0 : tzset();
127 0 : time_t current_time = time(nullptr);
128 : struct tm current_localtime;
129 : #ifdef V8_OS_WIN
130 : localtime_s(¤t_localtime, ¤t_time);
131 : #else
132 0 : localtime_r(¤t_time, ¤t_localtime);
133 : #endif
134 0 : int year = 1900 + current_localtime.tm_year;
135 :
136 0 : os << "// Copyright " << year
137 : << " the V8 project authors. All rights reserved.\n"
138 : "// Use of this source code is governed by a BSD-style license that "
139 : "can be\n"
140 : "// found in the LICENSE file.\n"
141 : "\n"
142 : "load('test/mjsunit/wasm/wasm-module-builder.js');\n"
143 : "\n"
144 : "(function() {\n"
145 0 : " const builder = new WasmModuleBuilder();\n";
146 :
147 0 : if (module->has_memory) {
148 0 : os << " builder.addMemory(" << module->initial_pages;
149 0 : if (module->has_maximum_pages) {
150 0 : os << ", " << module->maximum_pages;
151 : } else {
152 0 : os << ", undefined";
153 : }
154 0 : os << ", " << (module->mem_export ? "true" : "false");
155 0 : if (module->has_shared_memory) {
156 0 : os << ", true";
157 : }
158 0 : os << ");\n";
159 : }
160 :
161 0 : for (WasmGlobal& glob : module->globals) {
162 0 : os << " builder.addGlobal(" << ValueTypeToConstantName(glob.type) << ", "
163 0 : << glob.mutability << ");\n";
164 : }
165 :
166 0 : for (const FunctionSig* sig : module->signatures) {
167 0 : os << " builder.addType(makeSig(" << PrintParameters(sig) << ", "
168 0 : << PrintReturns(sig) << "));\n";
169 : }
170 :
171 0 : Zone tmp_zone(isolate->allocator(), ZONE_NAME);
172 :
173 : // There currently cannot be more than one table.
174 : DCHECK_GE(1, module->tables.size());
175 0 : for (const WasmTable& table : module->tables) {
176 0 : os << " builder.setTableBounds(" << table.initial_size << ", ";
177 0 : if (table.has_maximum_size) {
178 0 : os << table.maximum_size << ");\n";
179 : } else {
180 0 : os << "undefined);\n";
181 : }
182 : }
183 0 : for (const WasmElemSegment& elem_segment : module->elem_segments) {
184 0 : os << " builder.addElementSegment(";
185 0 : switch (elem_segment.offset.kind) {
186 : case WasmInitExpr::kGlobalIndex:
187 0 : os << elem_segment.offset.val.global_index << ", true";
188 0 : break;
189 : case WasmInitExpr::kI32Const:
190 0 : os << elem_segment.offset.val.i32_const << ", false";
191 0 : break;
192 : default:
193 0 : UNREACHABLE();
194 : }
195 0 : os << ", " << PrintCollection(elem_segment.entries) << ");\n";
196 : }
197 :
198 0 : for (const WasmFunction& func : module->functions) {
199 : Vector<const uint8_t> func_code = wire_bytes.GetFunctionBytes(&func);
200 0 : os << " // Generate function " << (func.func_index + 1) << " (out of "
201 0 : << module->functions.size() << ").\n";
202 :
203 : // Add function.
204 0 : os << " builder.addFunction(undefined, " << func.sig_index
205 0 : << " /* sig */)\n";
206 :
207 : // Add locals.
208 : BodyLocalDecls decls(&tmp_zone);
209 : DecodeLocalDecls(enabled_features, &decls, func_code.start(),
210 0 : func_code.end());
211 0 : if (!decls.type_list.empty()) {
212 0 : os << " ";
213 0 : for (size_t pos = 0, count = 1, locals = decls.type_list.size();
214 : pos < locals; pos += count, count = 1) {
215 0 : ValueType type = decls.type_list[pos];
216 0 : while (pos + count < locals && decls.type_list[pos + count] == type)
217 0 : ++count;
218 0 : os << ".addLocals({" << ValueTypes::TypeName(type)
219 0 : << "_count: " << count << "})";
220 : }
221 0 : os << "\n";
222 : }
223 :
224 : // Add body.
225 0 : os << " .addBodyWithEnd([\n";
226 :
227 : FunctionBody func_body(func.sig, func.code.offset(), func_code.start(),
228 0 : func_code.end());
229 0 : PrintRawWasmCode(isolate->allocator(), func_body, module, kOmitLocals);
230 0 : os << " ]);\n";
231 : }
232 :
233 0 : for (WasmExport& exp : module->export_table) {
234 0 : if (exp.kind != kExternalFunction) continue;
235 0 : os << " builder.addExport('" << PrintName(wire_bytes, exp.name) << "', "
236 0 : << exp.index << ");\n";
237 : }
238 :
239 0 : if (compiles) {
240 : os << " const instance = builder.instantiate();\n"
241 0 : " print(instance.exports.main(1, 2, 3));\n";
242 : } else {
243 : os << " assertThrows(function() { builder.instantiate(); }, "
244 0 : "WebAssembly.CompileError);\n";
245 : }
246 0 : os << "})();\n";
247 0 : }
248 :
249 2 : void WasmExecutionFuzzer::FuzzWasmModule(Vector<const uint8_t> data,
250 : bool require_valid) {
251 : // Strictly enforce the input size limit. Note that setting "max_len" on the
252 : // fuzzer target is not enough, since different fuzzers are used and not all
253 : // respect that limit.
254 3 : if (data.size() > max_input_size()) return;
255 :
256 2 : v8_fuzzer::FuzzerSupport* support = v8_fuzzer::FuzzerSupport::Get();
257 : v8::Isolate* isolate = support->GetIsolate();
258 : i::Isolate* i_isolate = reinterpret_cast<Isolate*>(isolate);
259 :
260 : // Clear any pending exceptions from a prior run.
261 2 : i_isolate->clear_pending_exception();
262 :
263 : v8::Isolate::Scope isolate_scope(isolate);
264 3 : v8::HandleScope handle_scope(isolate);
265 2 : v8::Context::Scope context_scope(support->GetContext());
266 3 : v8::TryCatch try_catch(isolate);
267 : HandleScope scope(i_isolate);
268 :
269 3 : AccountingAllocator allocator;
270 3 : Zone zone(&allocator, ZONE_NAME);
271 :
272 : ZoneBuffer buffer(&zone);
273 2 : int32_t num_args = 0;
274 2 : std::unique_ptr<WasmValue[]> interpreter_args;
275 2 : std::unique_ptr<Handle<Object>[]> compiler_args;
276 : // The first byte builds the bitmask to control which function will be
277 : // compiled with Turbofan and which one with Liftoff.
278 2 : uint8_t tier_mask = data.is_empty() ? 0 : data[0];
279 2 : if (!data.is_empty()) data += 1;
280 2 : if (!GenerateModule(i_isolate, &zone, data, buffer, num_args,
281 2 : interpreter_args, compiler_args)) {
282 : return;
283 : }
284 :
285 2 : testing::SetupIsolateForWasmModule(i_isolate);
286 :
287 1 : ErrorThrower interpreter_thrower(i_isolate, "Interpreter");
288 2 : ModuleWireBytes wire_bytes(buffer.begin(), buffer.end());
289 :
290 : // Compile with Turbofan here. Liftoff will be tested later.
291 2 : auto enabled_features = i::wasm::WasmFeaturesFromIsolate(i_isolate);
292 : MaybeHandle<WasmModuleObject> compiled_module;
293 : {
294 : // Explicitly enable Liftoff, disable tiering and set the tier_mask. This
295 : // way, we deterministically test a combination of Liftoff and Turbofan.
296 : FlagScope<bool> liftoff(&FLAG_liftoff, true);
297 : FlagScope<bool> no_tier_up(&FLAG_wasm_tier_up, false);
298 2 : FlagScope<int> tier_mask_scope(&FLAG_wasm_tier_mask_for_testing, tier_mask);
299 : compiled_module = i_isolate->wasm_engine()->SyncCompile(
300 2 : i_isolate, enabled_features, &interpreter_thrower, wire_bytes);
301 : }
302 2 : bool compiles = !compiled_module.is_null();
303 :
304 2 : if (FLAG_wasm_fuzzer_gen_test) {
305 0 : GenerateTestCase(i_isolate, wire_bytes, compiles);
306 : }
307 :
308 : bool validates = i_isolate->wasm_engine()->SyncValidate(
309 2 : i_isolate, enabled_features, wire_bytes);
310 :
311 2 : CHECK_EQ(compiles, validates);
312 2 : CHECK_IMPLIES(require_valid, validates);
313 :
314 3 : if (!compiles) return;
315 :
316 : MaybeHandle<WasmInstanceObject> interpreter_instance =
317 : i_isolate->wasm_engine()->SyncInstantiate(
318 : i_isolate, &interpreter_thrower, compiled_module.ToHandleChecked(),
319 2 : MaybeHandle<JSReceiver>(), MaybeHandle<JSArrayBuffer>());
320 :
321 : // Ignore instantiation failure.
322 1 : if (interpreter_thrower.error()) return;
323 :
324 : testing::WasmInterpretationResult interpreter_result =
325 : testing::InterpretWasmModule(i_isolate,
326 : interpreter_instance.ToHandleChecked(), 0,
327 1 : interpreter_args.get());
328 :
329 : // Do not execute the generated code if the interpreter did not finished after
330 : // a bounded number of steps.
331 1 : if (interpreter_result.stopped()) return;
332 :
333 : // The WebAssembly spec allows the sign bit of NaN to be non-deterministic.
334 : // This sign bit can make the difference between an infinite loop and
335 : // terminating code. With possible non-determinism we cannot guarantee that
336 : // the generated code will not go into an infinite loop and cause a timeout in
337 : // Clusterfuzz. Therefore we do not execute the generated code if the result
338 : // may be non-deterministic.
339 1 : if (interpreter_result.possible_nondeterminism()) return;
340 :
341 : int32_t result_compiled;
342 : {
343 : ErrorThrower compiler_thrower(i_isolate, "Compile");
344 : MaybeHandle<WasmInstanceObject> compiled_instance =
345 : i_isolate->wasm_engine()->SyncInstantiate(
346 : i_isolate, &compiler_thrower, compiled_module.ToHandleChecked(),
347 2 : MaybeHandle<JSReceiver>(), MaybeHandle<JSArrayBuffer>());
348 :
349 : DCHECK(!compiler_thrower.error());
350 : result_compiled = testing::CallWasmFunctionForTesting(
351 : i_isolate, compiled_instance.ToHandleChecked(), &compiler_thrower,
352 2 : "main", num_args, compiler_args.get());
353 : }
354 :
355 1 : if (interpreter_result.trapped() != i_isolate->has_pending_exception()) {
356 0 : const char* exception_text[] = {"no exception", "exception"};
357 0 : FATAL("interpreter: %s; compiled: %s",
358 : exception_text[interpreter_result.trapped()],
359 0 : exception_text[i_isolate->has_pending_exception()]);
360 : }
361 :
362 1 : if (!interpreter_result.trapped()) {
363 1 : CHECK_EQ(interpreter_result.result(), result_compiled);
364 : }
365 :
366 : // Cleanup any pending exception.
367 1 : i_isolate->clear_pending_exception();
368 : }
369 :
370 : } // namespace fuzzer
371 : } // namespace wasm
372 : } // namespace internal
373 18 : } // namespace v8
|