diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/tools/execution-results.h | 50 | ||||
-rw-r--r-- | src/tools/fuzzing.h | 7 | ||||
-rw-r--r-- | src/tools/fuzzing/fuzzing.cpp | 84 |
3 files changed, 136 insertions, 5 deletions
diff --git a/src/tools/execution-results.h b/src/tools/execution-results.h index 78cc5af1f..25d0c0772 100644 --- a/src/tools/execution-results.h +++ b/src/tools/execution-results.h @@ -40,10 +40,15 @@ private: // The name of the table exported by the name 'table.' Imports access it. Name exportedTable; + Module& wasm; + + // The ModuleRunner and this ExternalInterface end up needing links both ways, + // so we cannot init this in the constructor. + ModuleRunner* instance = nullptr; public: LoggingExternalInterface(Loggings& loggings, Module& wasm) - : loggings(loggings) { + : loggings(loggings), wasm(wasm) { for (auto& exp : wasm.exports) { if (exp->kind == ExternalKind::Table && exp->name == "table") { exportedTable = exp->value; @@ -99,6 +104,18 @@ public: } tableStore(exportedTable, index, arguments[1]); return {}; + } else if (import->base == "call-export") { + callExport(arguments[0].geti32()); + // Return nothing. If we wanted to return a value we'd need to have + // multiple such functions, one for each signature. + return {}; + } else if (import->base == "call-export-catch") { + try { + callExport(arguments[0].geti32()); + return {Literal(int32_t(0))}; + } catch (const WasmException& e) { + return {Literal(int32_t(1))}; + } } else { WASM_UNREACHABLE("unknown fuzzer import"); } @@ -127,6 +144,35 @@ public: auto payload = std::make_shared<ExnData>("__private", Literals{}); throwException(WasmException{Literal(payload)}); } + + Literals callExport(Index index) { + if (index >= wasm.exports.size()) { + // No export. + throwEmptyException(); + } + auto& exp = wasm.exports[index]; + if (exp->kind != ExternalKind::Function) { + // No callable export. + throwEmptyException(); + } + auto* func = wasm.getFunction(exp->value); + + // TODO JS traps on some types on the boundary, which we should behave the + // same on. For now, this is not needed because the fuzzer will prune all + // non-JS-compatible exports anyhow. + + // Send default values as arguments, or trap if we need anything else. + Literals arguments; + for (const auto& param : func->getParams()) { + if (!param.isDefaultable()) { + throwEmptyException(); + } + arguments.push_back(Literal::makeZero(param)); + } + return instance->callFunction(func->name, arguments); + } + + void setModuleRunner(ModuleRunner* instance_) { instance = instance_; } }; // gets execution results from a wasm module. this is useful for fuzzing @@ -148,6 +194,7 @@ struct ExecutionResults { LoggingExternalInterface interface(loggings, wasm); try { ModuleRunner instance(wasm, &interface); + interface.setModuleRunner(&instance); // execute all exported methods (that are therefore preserved through // opts) for (auto& exp : wasm.exports) { @@ -298,6 +345,7 @@ struct ExecutionResults { LoggingExternalInterface interface(loggings, wasm); try { ModuleRunner instance(wasm, &interface); + interface.setModuleRunner(&instance); return run(func, wasm, instance); } catch (const TrapException&) { // May throw in instance creation (init of offsets). diff --git a/src/tools/fuzzing.h b/src/tools/fuzzing.h index 6f73feca9..3e8ec5b97 100644 --- a/src/tools/fuzzing.h +++ b/src/tools/fuzzing.h @@ -104,10 +104,11 @@ private: Name funcrefTableName; std::unordered_map<Type, Name> logImportNames; - Name throwImportName; Name tableGetImportName; Name tableSetImportName; + Name callExportImportName; + Name callExportCatchImportName; std::unordered_map<Type, std::vector<Name>> globalsByType; std::unordered_map<Type, std::vector<Name>> mutableGlobalsByType; @@ -225,9 +226,8 @@ private: void finalizeTable(); void prepareHangLimitSupport(); void addHangLimitSupport(); - // Imports that we call to log out values. void addImportLoggingSupport(); - // An import that we call to throw an exception from outside. + void addImportCallingSupport(); void addImportThrowingSupport(); void addImportTableSupport(); void addHashMemorySupport(); @@ -238,6 +238,7 @@ private: Expression* makeImportThrowing(Type type); Expression* makeImportTableGet(); Expression* makeImportTableSet(Type type); + Expression* makeImportCallExport(Type type); Expression* makeMemoryHashLogging(); // Function creation diff --git a/src/tools/fuzzing/fuzzing.cpp b/src/tools/fuzzing/fuzzing.cpp index 1329e886e..f4e21d870 100644 --- a/src/tools/fuzzing/fuzzing.cpp +++ b/src/tools/fuzzing/fuzzing.cpp @@ -182,6 +182,7 @@ void TranslateToFuzzReader::build() { addImportTableSupport(); } addImportLoggingSupport(); + addImportCallingSupport(); modifyInitialFunctions(); // keep adding functions until we run out of input while (!random.finished()) { @@ -635,6 +636,49 @@ void TranslateToFuzzReader::addImportLoggingSupport() { } } +void TranslateToFuzzReader::addImportCallingSupport() { + // Only add these some of the time, as they inhibit some fuzzing (things like + // wasm-ctor-eval and wasm-merge are sensitive to the wasm being able to call + // its own exports, and to care about the indexes of the exports): + // + // 0 - none + // 1 - call-export + // 2 - call-export-catch + // 3 - call-export & call-export-catch + // 4 - none + // 5 - none + // + auto choice = upTo(6); + if (choice >= 4) { + return; + } + + if (choice & 1) { + // Given an export index, call it from JS. + callExportImportName = Names::getValidFunctionName(wasm, "call-export"); + auto func = std::make_unique<Function>(); + func->name = callExportImportName; + func->module = "fuzzing-support"; + func->base = "call-export"; + func->type = Signature({Type::i32}, Type::none); + wasm.addFunction(std::move(func)); + } + + if (choice & 2) { + // Given an export index, call it from JS and catch all exceptions. Return + // whether we caught. Exceptions are common (if the index is invalid, in + // particular), so a variant that catches is useful to avoid halting. + callExportCatchImportName = + Names::getValidFunctionName(wasm, "call-export-catch"); + auto func = std::make_unique<Function>(); + func->name = callExportCatchImportName; + func->module = "fuzzing-support"; + func->base = "call-export-catch"; + func->type = Signature(Type::i32, Type::i32); + wasm.addFunction(std::move(func)); + } +} + void TranslateToFuzzReader::addImportThrowingSupport() { // Throw some kind of exception from JS. // TODO: Send an index, which is which exported wasm Tag we should throw, or @@ -820,6 +864,38 @@ Expression* TranslateToFuzzReader::makeImportTableSet(Type type) { Type::none); } +Expression* TranslateToFuzzReader::makeImportCallExport(Type type) { + // The none-returning variant just does the call. The i32-returning one + // catches any errors and returns 1 when it saw an error. Based on the + // variant, pick which to call, and the maximum index to call. + Name target; + Index maxIndex = wasm.exports.size(); + if (type == Type::none) { + target = callExportImportName; + } else if (type == Type::i32) { + target = callExportCatchImportName; + // This never traps, so we can be less careful, but we do still want to + // avoid trapping a lot as executing code is more interesting. (Note that + // even though we double here, the risk is not that great: we are still + // adding functions as we go, so the first half of functions/exports can + // double here and still end up in bounds by the time we've added them all.) + maxIndex = (maxIndex + 1) * 2; + } else { + WASM_UNREACHABLE("bad import.call"); + } + // We must have set up the target function. + assert(target); + + // Most of the time, call a valid export index in the range we picked, but + // sometimes allow anything at all. + auto* index = make(Type::i32); + if (!allowOOB || !oneIn(10)) { + index = builder.makeBinary( + RemUInt32, index, builder.makeConst(int32_t(maxIndex))); + } + return builder.makeCall(target, {index}, type); +} + Expression* TranslateToFuzzReader::makeMemoryHashLogging() { auto* hash = builder.makeCall(std::string("hashMemory"), {}, Type::i32); return builder.makeCall(logImportNames[Type::i32], {hash}, Type::none); @@ -1511,6 +1587,9 @@ Expression* TranslateToFuzzReader::_makeConcrete(Type type) { options.add(FeatureSet::Atomics, &Self::makeAtomic); } if (type == Type::i32) { + if (callExportCatchImportName) { + options.add(FeatureSet::MVP, &Self::makeImportCallExport); + } options.add(FeatureSet::ReferenceTypes, &Self::makeRefIsNull); options.add(FeatureSet::ReferenceTypes | FeatureSet::GC, &Self::makeRefEq, @@ -1590,6 +1669,9 @@ Expression* TranslateToFuzzReader::_makenone() { if (tableSetImportName) { options.add(FeatureSet::ReferenceTypes, &Self::makeImportTableSet); } + if (callExportImportName) { + options.add(FeatureSet::MVP, &Self::makeImportCallExport); + } return (this->*pick(options))(Type::none); } @@ -2023,7 +2105,7 @@ Expression* TranslateToFuzzReader::makeCallIndirect(Type type) { return makeTrivial(type); } } - // with high probability, make sure the type is valid otherwise, most are + // with high probability, make sure the type is valid - otherwise, most are // going to trap auto addressType = wasm.getTable(funcrefTableName)->addressType; Expression* target; |