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