// Copyright 2017 the V8 project authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #include #include #include #include #include "include/v8.h" #include "src/isolate.h" #include "src/objects-inl.h" #include "src/objects.h" #include "src/ostreams.h" #include "src/wasm/wasm-interpreter.h" #include "src/wasm/wasm-module-builder.h" #include "src/wasm/wasm-module.h" #include "test/common/wasm/test-signatures.h" #include "test/common/wasm/wasm-module-runner.h" #include "test/fuzzer/fuzzer-support.h" #include "test/fuzzer/wasm-fuzzer-common.h" typedef uint8_t byte; namespace v8 { namespace internal { namespace wasm { namespace fuzzer { namespace { class DataRange { const uint8_t* data_; size_t size_; public: DataRange(const uint8_t* data, size_t size) : data_(data), size_(size) {} size_t size() const { return size_; } std::pair split(uint32_t index) const { return std::make_pair(DataRange(data_, index), DataRange(data_ + index, size() - index)); } std::pair split() { uint16_t index = get(); if (size() > 0) { index = index % size(); } else { index = 0; } return split(index); } template T get() { if (size() == 0) { return T(); } else { // We want to support the case where we have less than sizeof(T) bytes // remaining in the slice. For example, if we emit an i32 constant, it's // okay if we don't have a full four bytes available, we'll just use what // we have. We aren't concerned about endianness because we are generating // arbitrary expressions. const size_t num_bytes = std::min(sizeof(T), size()); T result = T(); memcpy(&result, data_, num_bytes); data_ += num_bytes; size_ -= num_bytes; return result; } } }; class WasmGenerator { template void op(DataRange data) { Generate(data); builder_->Emit(Op); } template void block(DataRange data) { blocks_.push_back(T); builder_->EmitWithU8( kExprBlock, static_cast(WasmOpcodes::ValueTypeCodeFor(T))); Generate(data); builder_->Emit(kExprEnd); blocks_.pop_back(); } template void block_br(DataRange data) { blocks_.push_back(T); builder_->EmitWithU8( kExprBlock, static_cast(WasmOpcodes::ValueTypeCodeFor(T))); const uint32_t target_block = data.get() % blocks_.size(); const ValueType break_type = blocks_[target_block]; Generate(break_type, data); builder_->EmitWithI32V( kExprBr, static_cast(blocks_.size()) - 1 - target_block); builder_->Emit(kExprEnd); blocks_.pop_back(); } // TODO(eholk): make this function constexpr once gcc supports it static uint8_t max_alignment(WasmOpcode memop) { switch (memop) { case kExprI64LoadMem: case kExprF64LoadMem: case kExprI64StoreMem: case kExprF64StoreMem: return 3; case kExprI32LoadMem: case kExprI64LoadMem32S: case kExprI64LoadMem32U: case kExprF32LoadMem: case kExprI32StoreMem: case kExprI64StoreMem32: case kExprF32StoreMem: return 2; case kExprI32LoadMem16S: case kExprI32LoadMem16U: case kExprI64LoadMem16S: case kExprI64LoadMem16U: case kExprI32StoreMem16: case kExprI64StoreMem16: return 1; case kExprI32LoadMem8S: case kExprI32LoadMem8U: case kExprI64LoadMem8S: case kExprI64LoadMem8U: case kExprI32StoreMem8: case kExprI64StoreMem8: return 0; default: return 0; } } template void memop(DataRange data) { const uint8_t align = data.get() % (max_alignment(memory_op) + 1); const uint32_t offset = data.get(); // Generate the index and the arguments, if any. Generate(data); builder_->Emit(memory_op); builder_->EmitU32V(align); builder_->EmitU32V(offset); } template void sequence(DataRange data) { Generate(data); } void current_memory(DataRange data) { builder_->EmitWithU8(kExprMemorySize, 0); } void grow_memory(DataRange data); using generate_fn = void (WasmGenerator::*const)(DataRange); template void GenerateOneOf(generate_fn (&alternates)[N], DataRange data) { static_assert(N < std::numeric_limits::max(), "Too many alternates. Replace with a bigger type if needed."); const auto which = data.get(); generate_fn alternate = alternates[which % N]; (this->*alternate)(data); } struct GeneratorRecursionScope { explicit GeneratorRecursionScope(WasmGenerator* gen) : gen(gen) { ++gen->recursion_depth; DCHECK_LE(gen->recursion_depth, kMaxRecursionDepth); } ~GeneratorRecursionScope() { DCHECK_GT(gen->recursion_depth, 0); --gen->recursion_depth; } WasmGenerator* gen; }; public: explicit WasmGenerator(WasmFunctionBuilder* fn) : builder_(fn) {} void Generate(ValueType type, DataRange data); template void Generate(DataRange data); template void Generate(DataRange data) { const auto parts = data.split(); Generate(parts.first); Generate(parts.second); } private: WasmFunctionBuilder* builder_; std::vector blocks_; uint32_t recursion_depth = 0; static constexpr uint32_t kMaxRecursionDepth = 64; bool recursion_limit_reached() { return recursion_depth >= kMaxRecursionDepth; } }; template <> void WasmGenerator::Generate(DataRange data) { GeneratorRecursionScope rec_scope(this); if (recursion_limit_reached() || data.size() == 0) return; constexpr generate_fn alternates[] = { &WasmGenerator::block, &WasmGenerator::block_br, &WasmGenerator::memop, &WasmGenerator::memop, &WasmGenerator::memop, &WasmGenerator::memop, &WasmGenerator::memop, &WasmGenerator::memop, &WasmGenerator::memop, &WasmGenerator::memop, &WasmGenerator::memop, }; GenerateOneOf(alternates, data); } template <> void WasmGenerator::Generate(DataRange data) { GeneratorRecursionScope rec_scope(this); if (recursion_limit_reached() || data.size() <= sizeof(uint32_t)) { builder_->EmitI32Const(data.get()); return; } constexpr generate_fn alternates[] = { &WasmGenerator::sequence, &WasmGenerator::op, &WasmGenerator::op, &WasmGenerator::op, &WasmGenerator::op, &WasmGenerator::op, &WasmGenerator::op, &WasmGenerator::op, &WasmGenerator::op, &WasmGenerator::op, &WasmGenerator::op, &WasmGenerator::op, &WasmGenerator::op, &WasmGenerator::op, &WasmGenerator::op, &WasmGenerator::op, &WasmGenerator::op, &WasmGenerator::op, &WasmGenerator::op, &WasmGenerator::op, &WasmGenerator::op, &WasmGenerator::op, &WasmGenerator::op, &WasmGenerator::op, &WasmGenerator::op, &WasmGenerator::op, &WasmGenerator::op, &WasmGenerator::op, &WasmGenerator::op, &WasmGenerator::op, &WasmGenerator::op, &WasmGenerator::op, &WasmGenerator::op, &WasmGenerator::op, &WasmGenerator::op, &WasmGenerator::op, &WasmGenerator::op, &WasmGenerator::op, &WasmGenerator::op, &WasmGenerator::op, &WasmGenerator::op, &WasmGenerator::op, &WasmGenerator::op, &WasmGenerator::op, &WasmGenerator::op, &WasmGenerator::op, &WasmGenerator::op, &WasmGenerator::block, &WasmGenerator::block_br, &WasmGenerator::memop, &WasmGenerator::memop, &WasmGenerator::memop, &WasmGenerator::memop, &WasmGenerator::memop, &WasmGenerator::current_memory, &WasmGenerator::grow_memory}; GenerateOneOf(alternates, data); } template <> void WasmGenerator::Generate(DataRange data) { GeneratorRecursionScope rec_scope(this); if (recursion_limit_reached() || data.size() <= sizeof(uint64_t)) { builder_->EmitI64Const(data.get()); return; } constexpr generate_fn alternates[] = { &WasmGenerator::sequence, &WasmGenerator::op, &WasmGenerator::op, &WasmGenerator::op, &WasmGenerator::op, &WasmGenerator::op, &WasmGenerator::op, &WasmGenerator::op, &WasmGenerator::op, &WasmGenerator::op, &WasmGenerator::op, &WasmGenerator::op, &WasmGenerator::op, &WasmGenerator::op, &WasmGenerator::op, &WasmGenerator::op, &WasmGenerator::op, &WasmGenerator::op, &WasmGenerator::op, &WasmGenerator::block, &WasmGenerator::block_br, &WasmGenerator::memop, &WasmGenerator::memop, &WasmGenerator::memop, &WasmGenerator::memop, &WasmGenerator::memop, &WasmGenerator::memop, &WasmGenerator::memop}; GenerateOneOf(alternates, data); } template <> void WasmGenerator::Generate(DataRange data) { GeneratorRecursionScope rec_scope(this); if (recursion_limit_reached() || data.size() <= sizeof(float)) { builder_->EmitF32Const(data.get()); return; } constexpr generate_fn alternates[] = { &WasmGenerator::sequence, &WasmGenerator::op, &WasmGenerator::op, &WasmGenerator::op, &WasmGenerator::block, &WasmGenerator::block_br, &WasmGenerator::memop}; GenerateOneOf(alternates, data); } template <> void WasmGenerator::Generate(DataRange data) { GeneratorRecursionScope rec_scope(this); if (recursion_limit_reached() || data.size() <= sizeof(double)) { builder_->EmitF64Const(data.get()); return; } constexpr generate_fn alternates[] = { &WasmGenerator::sequence, &WasmGenerator::op, &WasmGenerator::op, &WasmGenerator::op, &WasmGenerator::block, &WasmGenerator::block_br, &WasmGenerator::memop}; GenerateOneOf(alternates, data); } void WasmGenerator::grow_memory(DataRange data) { Generate(data); builder_->EmitWithU8(kExprGrowMemory, 0); } void WasmGenerator::Generate(ValueType type, DataRange data) { switch (type) { case kWasmStmt: return Generate(data); case kWasmI32: return Generate(data); case kWasmI64: return Generate(data); case kWasmF32: return Generate(data); case kWasmF64: return Generate(data); default: UNREACHABLE(); } } } // namespace class WasmCompileFuzzer : public WasmExecutionFuzzer { bool GenerateModule( Isolate* isolate, Zone* zone, const uint8_t* data, size_t size, ZoneBuffer& buffer, int32_t& num_args, std::unique_ptr& interpreter_args, std::unique_ptr[]>& compiler_args) override { TestSignatures sigs; WasmModuleBuilder builder(zone); WasmFunctionBuilder* f = builder.AddFunction(sigs.i_iii()); WasmGenerator gen(f); gen.Generate(DataRange(data, static_cast(size))); uint8_t end_opcode = kExprEnd; f->EmitCode(&end_opcode, 1); builder.AddExport(CStrVector("main"), f); builder.SetMaxMemorySize(32); builder.WriteTo(buffer); num_args = 3; interpreter_args.reset( new WasmValue[3]{WasmValue(1), WasmValue(2), WasmValue(3)}); compiler_args.reset(new Handle[3]{ handle(Smi::FromInt(1), isolate), handle(Smi::FromInt(1), isolate), handle(Smi::FromInt(1), isolate)}); return true; } }; extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) { constexpr bool require_valid = true; return WasmCompileFuzzer().FuzzWasmModule(data, size, require_valid); } } // namespace fuzzer } // namespace wasm } // namespace internal } // namespace v8