diff --git a/include/proxy-wasm/null_vm.h b/include/proxy-wasm/null_vm.h index d731060a2..a1a2f73d5 100644 --- a/include/proxy-wasm/null_vm.h +++ b/include/proxy-wasm/null_vm.h @@ -44,7 +44,6 @@ struct NullVm : public WasmVm { bool setWord(uint64_t pointer, Word data) override; bool getWord(uint64_t pointer, Word *data) override; size_t getWordSize() override; - std::string_view getCustomSection(std::string_view name) override; std::string_view getPrecompiledSectionName() override; #define _FORWARD_GET_FUNCTION(_T) \ diff --git a/include/proxy-wasm/wasm_vm.h b/include/proxy-wasm/wasm_vm.h index e88aa3784..c794e6f4c 100644 --- a/include/proxy-wasm/wasm_vm.h +++ b/include/proxy-wasm/wasm_vm.h @@ -248,14 +248,6 @@ class WasmVm { */ virtual size_t getWordSize() = 0; - /** - * Get the contents of the custom section with the given name or "" if it does not exist. - * @param name the name of the custom section to get. - * @return the contents of the custom section (if any). The result will be empty if there - * is no such section. - */ - virtual std::string_view getCustomSection(std::string_view name) = 0; - /** * Get the name of the custom section that contains precompiled module. * @return the name of the custom section that contains precompiled module. diff --git a/src/common/bytecode_util.cc b/src/common/bytecode_util.cc new file mode 100644 index 000000000..4b6a8a804 --- /dev/null +++ b/src/common/bytecode_util.cc @@ -0,0 +1,244 @@ +// Copyright 2021 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "src/common/bytecode_util.h" +#include + +namespace proxy_wasm { +namespace common { + +bool BytecodeUtil::checkWasmHeader(std::string_view bytecode) { + // Wasm file header is 8 bytes (magic number + version). + static const uint8_t wasm_magic_number[4] = {0x00, 0x61, 0x73, 0x6d}; + return bytecode.size() < 8 || !::memcmp(bytecode.data(), wasm_magic_number, 4); +} + +bool BytecodeUtil::getAbiVersion(std::string_view bytecode, proxy_wasm::AbiVersion &ret) { + ret = proxy_wasm::AbiVersion::Unknown; + // Check Wasm header. + if (!checkWasmHeader(bytecode)) { + return false; + } + + // Skip the Wasm header. + const char *pos = bytecode.data() + 8; + const char *end = bytecode.data() + bytecode.size(); + while (pos < end) { + if (pos + 1 > end) { + return false; + } + const auto section_type = *pos++; + uint32_t section_len = 0; + if (!parseVarint(pos, end, section_len) || pos + section_len > end) { + return false; + } + if (section_type == 7 /* export section */) { + uint32_t export_vector_size = 0; + if (!parseVarint(pos, end, export_vector_size) || pos + export_vector_size > end) { + return false; + } + // Search thourgh exports. + for (uint32_t i = 0; i < export_vector_size; i++) { + // Parse name of the export. + uint32_t export_name_size = 0; + if (!parseVarint(pos, end, export_name_size) || pos + export_name_size > end) { + return false; + } + const auto name_begin = pos; + pos += export_name_size; + if (pos + 1 > end) { + return false; + } + // Check if it is a function type export + if (*pos++ == 0x00) { + const std::string export_name = {name_begin, export_name_size}; + // Check the name of the function. + if (export_name == "proxy_abi_version_0_1_0") { + ret = AbiVersion::ProxyWasm_0_1_0; + return true; + } else if (export_name == "proxy_abi_version_0_2_0") { + ret = AbiVersion::ProxyWasm_0_2_0; + return true; + } else if (export_name == "proxy_abi_version_0_2_1") { + ret = AbiVersion::ProxyWasm_0_2_1; + return true; + } + } + // Skip export's index. + if (!parseVarint(pos, end, export_name_size)) { + return false; + } + } + return true; + } else { + pos += section_len; + } + } + return true; +} + +bool BytecodeUtil::getCustomSection(std::string_view bytecode, std::string_view name, + std::string_view &ret) { + // Check Wasm header. + if (!checkWasmHeader(bytecode)) { + return false; + } + + // Skip the Wasm header. + const char *pos = bytecode.data() + 8; + const char *end = bytecode.data() + bytecode.size(); + while (pos < end) { + if (pos + 1 > end) { + return false; + } + const auto section_type = *pos++; + uint32_t section_len = 0; + if (!parseVarint(pos, end, section_len) || pos + section_len > end) { + return false; + } + if (section_type == 0) { + // Custom section. + const auto section_data_start = pos; + uint32_t section_name_len = 0; + if (!BytecodeUtil::parseVarint(pos, end, section_name_len) || pos + section_name_len > end) { + return false; + } + if (section_name_len == name.size() && ::memcmp(pos, name.data(), section_name_len) == 0) { + pos += section_name_len; + ret = {pos, static_cast(section_data_start + section_len - pos)}; + return true; + } + pos = section_data_start + section_len; + } else { + // Skip other sections. + pos += section_len; + } + } + return true; +}; + +bool BytecodeUtil::getFunctionNameIndex(std::string_view bytecode, + std::unordered_map &ret) { + std::string_view name_section = {}; + if (!BytecodeUtil::getCustomSection(bytecode, "name", name_section)) { + return false; + }; + if (!name_section.empty()) { + const char *pos = name_section.data(); + const char *end = name_section.data() + name_section.size(); + while (pos < end) { + const auto subsection_id = *pos++; + uint32_t subsection_size = 0; + if (!parseVarint(pos, end, subsection_size) || pos + subsection_size > end) { + return false; + } + + if (subsection_id != 1) { + // Skip other subsctions. + pos += subsection_size; + } else { + // Enters function name subsection. + const auto start = pos; + uint32_t namemap_vector_size = 0; + if (!parseVarint(pos, end, namemap_vector_size) || pos + namemap_vector_size > end) { + return false; + } + for (uint32_t i = 0; i < namemap_vector_size; i++) { + uint32_t func_index = 0; + if (!parseVarint(pos, end, func_index)) { + return false; + } + + uint32_t func_name_size = 0; + if (!parseVarint(pos, end, func_name_size) || pos + func_name_size > end) { + return false; + } + ret.insert({func_index, std::string(pos, func_name_size)}); + pos += func_name_size; + } + if (start + subsection_size != pos) { + return false; + } + } + } + } + return true; +} + +bool BytecodeUtil::getStrippedSource(std::string_view bytecode, std::string &ret) { + // Check Wasm header. + if (!checkWasmHeader(bytecode)) { + return false; + } + + // Skip the Wasm header. + const char *pos = bytecode.data() + 8; + const char *end = bytecode.data() + bytecode.size(); + while (pos < end) { + const auto section_start = pos; + if (pos + 1 > end) { + return false; + } + const auto section_type = *pos++; + uint32_t section_len = 0; + if (!parseVarint(pos, end, section_len) || pos + section_len > end) { + return false; + } + if (section_type == 0 /* custom section */) { + const auto section_data_start = pos; + uint32_t section_name_len = 0; + if (!parseVarint(pos, end, section_name_len) || pos + section_name_len > end) { + return false; + } + auto section_name = std::string_view(pos, section_name_len); + if (section_name.find("precompiled_") != std::string::npos) { + // If this is the first "precompiled_" section, then save everything + // before it, otherwise skip it. + if (ret.empty()) { + const char *start = bytecode.data(); + ret.append(start, section_start); + } + } + pos = section_data_start + section_len; + } else { + pos += section_len; + // Save this section if we already saw a custom "precompiled_" section. + if (!ret.empty()) { + ret.append(section_start, pos); + } + } + } + if (ret.empty()) { + // Copy the original source code if it is empty. + ret = std::string(bytecode); + } + return true; +} + +bool BytecodeUtil::parseVarint(const char *&pos, const char *end, uint32_t &ret) { + uint32_t shift = 0; + char b; + do { + if (pos + 1 > end) { + return false; + } + b = *pos++; + ret += (b & 0x7f) << shift; + shift += 7; + } while ((b & 0x80) != 0); + return ret != static_cast(-1); +} + +} // namespace common +} // namespace proxy_wasm diff --git a/src/common/bytecode_util.h b/src/common/bytecode_util.h new file mode 100644 index 000000000..438969fb5 --- /dev/null +++ b/src/common/bytecode_util.h @@ -0,0 +1,77 @@ +// Copyright 2021 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +#pragma once + +#include +#include +#include + +#include "include/proxy-wasm/wasm_vm.h" + +namespace proxy_wasm { +namespace common { + +// Utilitiy functions which directly operate on Wasm bytecodes. +class BytecodeUtil { +public: + /** + * checkWasmHeader validates Wasm header. + * @param bytecode is the target bytecode. + * @return indicates whether the bytecode has valid Wasm header. + */ + static bool checkWasmHeader(std::string_view bytecode); + + /** + * getAbiVersion extracts ABI version from the bytecode. + * @param bytecode is the target bytecode. + * @param ret is the reference to store the extracted ABI version or UnKnown if it doesn't exist. + * @return indicates whether parsing succeeded or not. + */ + static bool getAbiVersion(std::string_view bytecode, proxy_wasm::AbiVersion &ret); + + /** + * getCustomSection extract the view of the custom section for a given name. + * @param bytecode is the target bytecode. + * @param name is the name of the custom section. + * @param ret is the reference to store the resulting view to the custom section. + * @return indicates whether parsing succeeded or not. + */ + static bool getCustomSection(std::string_view bytecode, std::string_view name, + std::string_view &ret); + + /** + * getFunctionNameIndex constructs the map from function indexes to function names stored in + * the function name subsection in "name" custom section. + * See https://webassembly.github.io/spec/core/appendix/custom.html#binary-funcnamesec for detail. + * @param bytecode is the target bytecode. + * @param ret is the reference to store map from function indexes to function names. + * @return indicates whether parsing succeeded or not. + */ + static bool getFunctionNameIndex(std::string_view bytecode, + std::unordered_map &ret); + + /** + * getStrippedSource gets Wasm module without Custom Sections to save some memory in workers. + * @param bytecode is the original bytecode. + * @param ret is the reference to the stripped bytecode or a copy of the original bytecode. + * @return indicates whether parsing succeeded or not. + */ + static bool getStrippedSource(std::string_view bytecode, std::string &ret); + +private: + static bool parseVarint(const char *&begin, const char *end, uint32_t &ret); +}; + +} // namespace common +} // namespace proxy_wasm diff --git a/src/null/null_vm.cc b/src/null/null_vm.cc index 80a2210e0..a048c7da4 100644 --- a/src/null/null_vm.cc +++ b/src/null/null_vm.cc @@ -104,11 +104,6 @@ bool NullVm::getWord(uint64_t pointer, Word *data) { size_t NullVm::getWordSize() { return sizeof(uint64_t); } -std::string_view NullVm::getCustomSection(std::string_view /* name */) { - // Return nothing: there is no WASM file. - return {}; -} - std::string_view NullVm::getPrecompiledSectionName() { // Return nothing: there is no WASM file. return {}; diff --git a/src/v8/v8.cc b/src/v8/v8.cc index 986ce10f1..14ba46f65 100644 --- a/src/v8/v8.cc +++ b/src/v8/v8.cc @@ -25,6 +25,8 @@ #include #include +#include "src/common/bytecode_util.h" + #include "v8.h" #include "v8-version.h" #include "wasm-api/wasm.hh" @@ -64,7 +66,6 @@ class V8 : public WasmVm { bool load(const std::string &code, bool allow_precompiled) override; AbiVersion getAbiVersion() override; - std::string_view getCustomSection(std::string_view name) override; std::string_view getPrecompiledSectionName() override; bool link(std::string_view debug_name) override; @@ -94,8 +95,6 @@ class V8 : public WasmVm { #undef _GET_MODULE_FUNCTION private: - void buildFunctionNameIndex(); - wasm::vec getStrippedSource(); std::string getFailMessage(std::string_view function_name, wasm::own trap); template @@ -114,7 +113,6 @@ class V8 : public WasmVm { void getModuleFunctionImpl(std::string_view function_name, std::function *function); - wasm::vec source_ = wasm::vec::invalid(); wasm::own store_; wasm::own module_; wasm::own> shared_module_; @@ -125,6 +123,7 @@ class V8 : public WasmVm { std::unordered_map host_functions_; std::unordered_map> module_functions_; + AbiVersion abi_version_; std::unordered_map function_names_index_; }; @@ -208,21 +207,6 @@ static bool equalValTypes(const wasm::ownvec &left, return true; } -static uint32_t parseVarint(const byte_t *&pos, const byte_t *end) { - uint32_t n = 0; - uint32_t shift = 0; - byte_t b; - do { - if (pos + 1 > end) { - abort(); - } - b = *pos++; - n += (b & 0x7f) << shift; - shift += 7; - } while ((b & 0x80) != 0); - return n; -} - // Template magic. template struct ConvertWordType { @@ -270,19 +254,20 @@ template constexpr T convertValTypesToArgsTuple(const U bool V8::load(const std::string &code, bool allow_precompiled) { store_ = wasm::Store::make(engine()); - // Wasm file header is 8 bytes (magic number + version). - static const uint8_t magic_number[4] = {0x00, 0x61, 0x73, 0x6d}; - if (code.size() < 8 || ::memcmp(code.data(), magic_number, 4) != 0) { + // Get ABI version from bytecode. + if (!common::BytecodeUtil::getAbiVersion(code, abi_version_)) { + fail(FailState::UnableToInitializeCode, "Failed to parse corrupted Wasm module"); return false; } - source_ = wasm::vec::make_uninitialized(code.size()); - ::memcpy(source_.get(), code.data(), code.size()); - if (allow_precompiled) { const auto section_name = getPrecompiledSectionName(); if (!section_name.empty()) { - const auto precompiled = getCustomSection(section_name); + std::string_view precompiled = {}; + if (!common::BytecodeUtil::getCustomSection(code, section_name, precompiled)) { + fail(FailState::UnableToInitializeCode, "Failed to parse corrupted Wasm module"); + return false; + } if (!precompiled.empty()) { auto vec = wasm::vec::make_uninitialized(precompiled.size()); ::memcpy(vec.get(), precompiled.data(), precompiled.size()); @@ -298,8 +283,13 @@ bool V8::load(const std::string &code, bool allow_precompiled) { } if (!module_) { - const auto stripped_source = getStrippedSource(); - module_ = wasm::Module::make(store_.get(), stripped_source ? stripped_source : source_); + std::string stripped; + if (!common::BytecodeUtil::getStrippedSource(code, stripped)) { + fail(FailState::UnableToInitializeCode, "Failed to parse corrupted Wasm module"); + return false; + }; + wasm::vec stripped_vec = wasm::vec::make(stripped.size(), stripped.data()); + module_ = wasm::Module::make(store_.get(), stripped_vec); } if (module_) { @@ -307,58 +297,14 @@ bool V8::load(const std::string &code, bool allow_precompiled) { assert((shared_module_ != nullptr)); } - buildFunctionNameIndex(); + if (!common::BytecodeUtil::getFunctionNameIndex(code, function_names_index_)) { + fail(FailState::UnableToInitializeCode, "Failed to parse corrupted Wasm module"); + return false; + }; return module_ != nullptr; } -void V8::buildFunctionNameIndex() { - // build function index -> function name map for backtrace - // https://webassembly.github.io/spec/core/appendix/custom.html#binary-namesubsection - auto name_section = getCustomSection("name"); - if (name_section.size()) { - const byte_t *pos = name_section.data(); - const byte_t *end = name_section.data() + name_section.size(); - while (pos < end) { - if (*pos++ != 1) { - pos += parseVarint(pos, end); - } else { - const auto size = parseVarint(pos, end); - if (size == static_cast(-1) || pos + size > end) { - function_names_index_ = {}; - return; - } - const auto start = pos; - const auto namemap_vector_size = parseVarint(pos, end); - if (namemap_vector_size == static_cast(-1) || pos + namemap_vector_size > end) { - function_names_index_ = {}; - return; - } - for (auto i = 0; i < namemap_vector_size; i++) { - const auto func_index = parseVarint(pos, end); - if (func_index == static_cast(-1)) { - function_names_index_ = {}; - return; - } - - const auto func_name_size = parseVarint(pos, end); - if (func_name_size == static_cast(-1) || pos + func_name_size > end) { - function_names_index_ = {}; - return; - } - function_names_index_.insert({func_index, std::string(pos, func_name_size)}); - pos += func_name_size; - } - - if (start + size != pos) { - function_names_index_ = {}; - return; - } - } - } - } -} - std::unique_ptr V8::clone() { assert(shared_module_ != nullptr); @@ -368,97 +314,11 @@ std::unique_ptr V8::clone() { clone->module_ = wasm::Module::obtain(clone->store_.get(), shared_module_.get()); clone->function_names_index_ = function_names_index_; + clone->abi_version_ = abi_version_; return clone; } -// Get Wasm module without Custom Sections to save some memory in workers. -wasm::vec V8::getStrippedSource() { - assert(source_.get() != nullptr); - - std::vector stripped; - - const byte_t *pos = source_.get() + 8 /* Wasm header */; - const byte_t *end = source_.get() + source_.size(); - while (pos < end) { - const auto section_start = pos; - if (pos + 1 > end) { - return wasm::vec::invalid(); - } - const auto section_type = *pos++; - const auto section_len = parseVarint(pos, end); - if (section_len == static_cast(-1) || pos + section_len > end) { - return wasm::vec::invalid(); - } - if (section_type == 0 /* custom section */) { - const auto section_data_start = pos; - const auto section_name_len = parseVarint(pos, end); - if (section_name_len == static_cast(-1) || pos + section_name_len > end) { - return wasm::vec::invalid(); - } - auto section_name = std::string_view(pos, section_name_len); - if (section_name.find("precompiled_") != std::string::npos) { - // If this is the first "precompiled_" section, then save everything - // before it, otherwise skip it. - if (stripped.empty()) { - const byte_t *start = source_.get(); - stripped.insert(stripped.end(), start, section_start); - } - } - pos = section_data_start + section_len; - } else { - pos += section_len; - // Save this section if we already saw a custom "precompiled_" section. - if (!stripped.empty()) { - stripped.insert(stripped.end(), section_start, pos /* section end */); - } - } - } - - // No custom sections found, use the original source. - if (stripped.empty()) { - return wasm::vec::invalid(); - } - - // Return stripped source, without custom sections. - return wasm::vec::make(stripped.size(), stripped.data()); -} - -std::string_view V8::getCustomSection(std::string_view name) { - assert(source_.get() != nullptr); - - const byte_t *pos = source_.get() + 8 /* Wasm header */; - const byte_t *end = source_.get() + source_.size(); - while (pos < end) { - if (pos + 1 > end) { - fail(FailState::UnableToInitializeCode, "Failed to parse corrupted Wasm module"); - return ""; - } - const auto section_type = *pos++; - const auto section_len = parseVarint(pos, end); - if (section_len == static_cast(-1) || pos + section_len > end) { - fail(FailState::UnableToInitializeCode, "Failed to parse corrupted Wasm module"); - return ""; - } - if (section_type == 0 /* custom section */) { - const auto section_data_start = pos; - const auto section_name_len = parseVarint(pos, end); - if (section_name_len == static_cast(-1) || pos + section_name_len > end) { - fail(FailState::UnableToInitializeCode, "Failed to parse corrupted Wasm module"); - return ""; - } - if (section_name_len == name.size() && ::memcmp(pos, name.data(), section_name_len) == 0) { - pos += section_name_len; - return {pos, static_cast(section_data_start + section_len - pos)}; - } - pos = section_data_start + section_len; - } else { - pos += section_len; - } - } - return ""; -} - #if defined(__linux__) && defined(__x86_64__) #define WEE8_PLATFORM "linux_x86_64" #else @@ -475,25 +335,7 @@ std::string_view V8::getPrecompiledSectionName() { return name; } -AbiVersion V8::getAbiVersion() { - assert(module_ != nullptr); - - const auto export_types = module_.get()->exports(); - for (size_t i = 0; i < export_types.size(); i++) { - if (export_types[i]->type()->kind() == wasm::EXTERN_FUNC) { - std::string_view name(export_types[i]->name().get(), export_types[i]->name().size()); - if (name == "proxy_abi_version_0_1_0") { - return AbiVersion::ProxyWasm_0_1_0; - } else if (name == "proxy_abi_version_0_2_0") { - return AbiVersion::ProxyWasm_0_2_0; - } else if (name == "proxy_abi_version_0_2_1") { - return AbiVersion::ProxyWasm_0_2_1; - } - } - } - - return AbiVersion::Unknown; -} +AbiVersion V8::getAbiVersion() { return abi_version_; } bool V8::link(std::string_view debug_name) { assert(module_ != nullptr); diff --git a/src/wasmtime/wasmtime.cc b/src/wasmtime/wasmtime.cc index dec8ecbbb..3ff503645 100644 --- a/src/wasmtime/wasmtime.cc +++ b/src/wasmtime/wasmtime.cc @@ -25,7 +25,9 @@ #include #include "include/proxy-wasm/wasm_vm.h" +#include "src/common/bytecode_util.h" #include "src/wasmtime/types.h" + #include "wasmtime/include/wasm.h" namespace proxy_wasm { @@ -57,7 +59,6 @@ class Wasmtime : public WasmVm { bool load(const std::string &code, bool allow_precompiled = false) override; AbiVersion getAbiVersion() override; - std::string_view getCustomSection(std::string_view name) override; bool link(std::string_view debug_name) override; std::unique_ptr clone() override; uint64_t getMemorySize() override; @@ -82,8 +83,6 @@ class Wasmtime : public WasmVm { FOR_ALL_WASM_VM_EXPORTS(_GET_MODULE_FUNCTION) #undef _GET_MODULE_FUNCTION private: - bool getStrippedSource(WasmByteVec *out); - template void registerHostFunctionImpl(std::string_view module_name, std::string_view function_name, void (*function)(void *, Args...)); @@ -100,50 +99,37 @@ class Wasmtime : public WasmVm { void getModuleFunctionImpl(std::string_view function_name, std::function *function); - WasmByteVec source_; WasmStorePtr store_; WasmModulePtr module_; WasmSharedModulePtr shared_module_; WasmInstancePtr instance_; - WasmMemoryPtr memory_; WasmTablePtr table_; std::unordered_map host_functions_; std::unordered_map module_functions_; -}; -// TODO(mathetake): move to proxy_wasm::common::* -static uint32_t parseVarint(const byte_t *&pos, const byte_t *end) { - uint32_t n = 0; - uint32_t shift = 0; - byte_t b; - do { - if (pos + 1 > end) { - abort(); - } - b = *pos++; - n += (b & 0x7f) << shift; - shift += 7; - } while ((b & 0x80) != 0); - return n; -} + AbiVersion abi_version_; +}; bool Wasmtime::load(const std::string &code, bool allow_precompiled) { store_ = wasm_store_new(engine()); - // Wasm file header is 8 bytes (magic number + version). - static const uint8_t magic_number[4] = {0x00, 0x61, 0x73, 0x6d}; - if (code.size() < 8 || ::memcmp(code.data(), magic_number, 4) != 0) { + // Get ABI version from bytecode. + if (!common::BytecodeUtil::getAbiVersion(code, abi_version_)) { + fail(FailState::UnableToInitializeCode, "Failed to parse corrupted Wasm module"); return false; } - wasm_byte_vec_new_uninitialized(source_.get(), code.size()); - ::memcpy(source_.get()->data, code.data(), code.size()); + std::string stripped; + if (!common::BytecodeUtil::getStrippedSource(code, stripped)) { + fail(FailState::UnableToInitializeCode, "Failed to parse corrupted Wasm module"); + return false; + }; - WasmByteVec stripped; - module_ = - wasm_module_new(store_.get(), getStrippedSource(&stripped) ? stripped.get() : source_.get()); + WasmByteVec stripped_vec; + wasm_byte_vec_new(stripped_vec.get(), stripped.size(), stripped.data()); + module_ = wasm_module_new(store_.get(), stripped_vec.get()); if (module_) { shared_module_ = wasm_module_share(module_.get()); @@ -160,56 +146,9 @@ std::unique_ptr Wasmtime::clone() { clone->integration().reset(integration()->clone()); clone->store_ = wasm_store_new(engine()); clone->module_ = wasm_module_obtain(clone->store_.get(), shared_module_.get()); - return clone; -} - -// TODO(mathetake): move to proxy_wasm::common::* -bool Wasmtime::getStrippedSource(WasmByteVec *out) { - std::vector stripped; - - const byte_t *pos = source_.get()->data + 8 /* Wasm header */; - const byte_t *end = source_.get()->data + source_.get()->size; - while (pos < end) { - const auto section_start = pos; - if (pos + 1 > end) { - return false; - } - const auto section_type = *pos++; - const auto section_len = parseVarint(pos, end); - if (section_len == static_cast(-1) || pos + section_len > end) { - return false; - } - if (section_type == 0 /* custom section */) { - const auto section_data_start = pos; - const auto section_name_len = parseVarint(pos, end); - if (section_name_len == static_cast(-1) || pos + section_name_len > end) { - return false; - } - auto section_name = std::string_view(pos, section_name_len); - if (section_name.find("precompiled_") != std::string::npos) { - // If this is the first "precompiled_" section, then save everything - // before it, otherwise skip it. - if (stripped.empty()) { - const byte_t *start = source_.get()->data; - stripped.insert(stripped.end(), start, section_start); - } - } - pos = section_data_start + section_len; - } else { - pos += section_len; - // Save this section if we already saw a custom "precompiled_" section. - if (!stripped.empty()) { - stripped.insert(stripped.end(), section_start, pos /* section end */); - } - } - } + clone->abi_version_ = abi_version_; - if (!stripped.empty()) { - wasm_byte_vec_new_uninitialized(out->get(), stripped.size()); - ::memcpy(out->get()->data, stripped.data(), stripped.size()); - return true; - } - return false; + return clone; } static bool equalValTypes(const wasm_valtype_vec_t *left, const wasm_valtype_vec_t *right) { @@ -400,41 +339,6 @@ bool Wasmtime::link(std::string_view debug_name) { return true; } -std::string_view Wasmtime::getCustomSection(std::string_view name) { - const byte_t *pos = source_.get()->data + 8 /* Wasm header */; - const byte_t *end = source_.get()->data + source_.get()->size; - while (pos < end) { - if (pos + 1 > end) { - fail(FailState::UnableToInitializeCode, "Failed to parse corrupted Wasm module"); - return ""; - } - const auto section_type = *pos++; - const auto section_len = parseVarint(pos, end); - if (section_len == static_cast(-1) || pos + section_len > end) { - fail(FailState::UnableToInitializeCode, "Failed to parse corrupted Wasm module"); - return ""; - } - - if (section_type == 0 /* custom section */) { - const auto section_data_start = pos; - const auto section_name_len = parseVarint(pos, end); - if (section_name_len == static_cast(-1) || pos + section_name_len > end) { - fail(FailState::UnableToInitializeCode, "Failed to parse corrupted Wasm module"); - return ""; - } - if (section_name_len == name.size() && ::memcmp(pos, name.data(), section_name_len) == 0) { - pos += section_name_len; - return {pos, static_cast(section_data_start + section_len - pos)}; - } - pos = section_data_start + section_len; - } else { - pos += section_len; - } - } - - return ""; -} - uint64_t Wasmtime::getMemorySize() { return wasm_memory_data_size(memory_.get()); } std::optional Wasmtime::getMemory(uint64_t pointer, uint64_t size) { @@ -739,27 +643,7 @@ void Wasmtime::getModuleFunctionImpl(std::string_view function_name, }; }; -AbiVersion Wasmtime::getAbiVersion() { - assert(module_ != nullptr); - WasmExportTypeVec export_types; - wasm_module_exports(module_.get(), export_types.get()); - - for (size_t i = 0; i < export_types.get()->size; i++) { - const wasm_externtype_t *exp_extern_type = wasm_exporttype_type(export_types.get()->data[i]); - if (wasm_externtype_kind(exp_extern_type) == WASM_EXTERN_FUNC) { - const wasm_name_t *name_ptr = wasm_exporttype_name(export_types.get()->data[i]); - std::string_view name(name_ptr->data, name_ptr->size); - if (name == "proxy_abi_version_0_1_0") { - return AbiVersion::ProxyWasm_0_1_0; - } else if (name == "proxy_abi_version_0_2_0") { - return AbiVersion::ProxyWasm_0_2_0; - } else if (name == "proxy_abi_version_0_2_1") { - return AbiVersion::ProxyWasm_0_2_1; - } - } - } - return AbiVersion::Unknown; -} +AbiVersion Wasmtime::getAbiVersion() { return abi_version_; } } // namespace wasmtime diff --git a/src/wavm/wavm.cc b/src/wavm/wavm.cc index f96b334fb..d3a4d7627 100644 --- a/src/wavm/wavm.cc +++ b/src/wavm/wavm.cc @@ -27,6 +27,8 @@ #include #include +#include "src/common/bytecode_util.h" + #include "WAVM/IR/Module.h" #include "WAVM/IR/Operators.h" #include "WAVM/IR/Types.h" @@ -230,7 +232,6 @@ struct Wavm : public WasmVm { bool getWord(uint64_t pointer, Word *data) override; bool setWord(uint64_t pointer, Word data) override; size_t getWordSize() override { return sizeof(uint32_t); }; - std::string_view getCustomSection(std::string_view name) override; std::string_view getPrecompiledSectionName() override; AbiVersion getAbiVersion() override; @@ -278,6 +279,7 @@ Wavm::~Wavm() { std::unique_ptr Wavm::clone() { auto wavm = std::make_unique(); wavm->integration().reset(integration()->clone()); + wavm->abi_version_ = abi_version_; wavm->compartment_ = WAVM::Runtime::cloneCompartment(compartment_); wavm->memory_ = WAVM::Runtime::remapToClonedCompartment(memory_, wavm->compartment_); @@ -301,44 +303,30 @@ bool Wavm::load(const std::string &code, bool allow_precompiled) { if (!loadModule(code, ir_module_)) { return false; } - getAbiVersion(); // Cache ABI version. - const CustomSection *precompiled_object_section = nullptr; + + // Get ABI version from bytecode. + if (!common::BytecodeUtil::getAbiVersion(code, abi_version_)) { + fail(FailState::UnableToInitializeCode, "Failed to parse corrupted Wasm module"); + return false; + } + + std::string_view precompiled = {}; if (allow_precompiled) { - for (const CustomSection &customSection : ir_module_.customSections) { - if (customSection.name == getPrecompiledSectionName()) { - precompiled_object_section = &customSection; - break; - } + if (!common::BytecodeUtil::getCustomSection(code, getPrecompiledSectionName(), precompiled)) { + fail(FailState::UnableToInitializeCode, "Failed to parse corrupted Wasm module"); + return false; } } - if (!precompiled_object_section) { + if (precompiled.empty()) { module_ = WAVM::Runtime::compileModule(ir_module_); } else { - module_ = WAVM::Runtime::loadPrecompiledModule(ir_module_, precompiled_object_section->data); + module_ = WAVM::Runtime::loadPrecompiledModule( + ir_module_, {precompiled.data(), precompiled.data() + precompiled.size()}); } return true; } -AbiVersion Wavm::getAbiVersion() { - if (abi_version_ != AbiVersion::Unknown) { - return abi_version_; - } - for (auto &e : ir_module_.exports) { - if (e.name == "proxy_abi_version_0_1_0") { - abi_version_ = AbiVersion::ProxyWasm_0_1_0; - return abi_version_; - } - if (e.name == "proxy_abi_version_0_2_0") { - abi_version_ = AbiVersion::ProxyWasm_0_2_0; - return abi_version_; - } - if (e.name == "proxy_abi_version_0_2_1") { - abi_version_ = AbiVersion::ProxyWasm_0_2_1; - return abi_version_; - } - } - return AbiVersion::Unknown; -} +AbiVersion Wavm::getAbiVersion() { return abi_version_; } bool Wavm::link(std::string_view debug_name) { RootResolver rootResolver(compartment_, this); @@ -400,15 +388,6 @@ bool Wavm::setWord(uint64_t pointer, Word data) { return setMemory(pointer, sizeof(uint32_t), &data32); } -std::string_view Wavm::getCustomSection(std::string_view name) { - for (auto §ion : ir_module_.customSections) { - if (section.name == name) { - return {reinterpret_cast(section.data.data()), section.data.size()}; - } - } - return {}; -} - std::string_view Wavm::getPrecompiledSectionName() { return "wavm.precompiled_object"; } } // namespace Wavm diff --git a/test/BUILD b/test/BUILD index 52b6113d0..0fad5ceb6 100644 --- a/test/BUILD +++ b/test/BUILD @@ -1,6 +1,8 @@ load("@rules_cc//cc:defs.bzl", "cc_library", "cc_test") load("@proxy_wasm_cpp_host//bazel:variables.bzl", "COPTS", "LINKOPTS") +package(default_visibility = ["//visibility:public"]) + cc_test( name = "null_vm_test", srcs = ["null_vm_test.cc"], diff --git a/test/common/BUILD b/test/common/BUILD new file mode 100644 index 000000000..c76fc5e48 --- /dev/null +++ b/test/common/BUILD @@ -0,0 +1,17 @@ +load("@rules_cc//cc:defs.bzl", "cc_test") +load("@proxy_wasm_cpp_host//bazel:variables.bzl", "COPTS") + +cc_test( + name = "bytecode_util_test", + srcs = ["bytecode_util_test.cc"], + copts = COPTS, + data = [ + "//test/test_data:abi_export.wasm", + ], + deps = [ + "//:lib", + "//test:utility_lib", + "@com_google_googletest//:gtest", + "@com_google_googletest//:gtest_main", + ], +) diff --git a/test/common/bytecode_util_test.cc b/test/common/bytecode_util_test.cc new file mode 100644 index 000000000..50900c0ce --- /dev/null +++ b/test/common/bytecode_util_test.cc @@ -0,0 +1,121 @@ +// Copyright 2021 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "src/common/bytecode_util.h" + +#include +#include +#include + +#include "test/utility.h" + +#include "gtest/gtest.h" + +namespace proxy_wasm { +namespace common { + +TEST(TestWasmCommonUtil, getCustomSection) { + std::string custom_section = { + 0x00, 0x61, 0x73, 0x6d, // Wasm magic + 0x01, 0x00, 0x00, 0x00, // Wasm version + 0x00, // custom section id + 0x0a, // section length + 0x04, 0x68, 0x65, 0x79, 0x21, // section name: "hey!" + 0x68, 0x65, 0x6c, 0x6c, 0x6f, // content: "hello" + }; + std::string_view section = {}; + + // OK. + EXPECT_TRUE(BytecodeUtil::getCustomSection(custom_section, "hey!", section)); + EXPECT_EQ(std::string(section), "hello"); + section = {}; + + // Non exist. + EXPECT_TRUE(BytecodeUtil::getCustomSection(custom_section, "non-exist", section)); + EXPECT_EQ(section, ""); + + // Fail due to the corrupted bytecode. + // TODO(@mathetake): here we haven't covered all the parsing failure branches. Add more cases. + std::string corrupted = {custom_section.data(), + custom_section.data() + custom_section.size() - 3}; + EXPECT_FALSE(BytecodeUtil::getCustomSection(corrupted, "hey", section)); + corrupted = {custom_section.data() + 1, custom_section.data() + custom_section.size()}; + EXPECT_FALSE(BytecodeUtil::getCustomSection(corrupted, "hey", section)); +} + +TEST(TestWasmCommonUtil, getFunctionNameIndex) { + const auto source = readTestWasmFile("abi_export.wasm"); + std::unordered_map actual; + // OK. + EXPECT_TRUE(BytecodeUtil::getFunctionNameIndex(source, actual)); + EXPECT_FALSE(actual.empty()); + EXPECT_EQ(actual.find(0)->second, "proxy_abi_version_0_2_0"); + + // Fail due to the corrupted bytecode. + // TODO(@mathetake): here we haven't covered all the parsing failure branches. Add more cases. + actual = {}; + std::string_view name_section = {}; + EXPECT_TRUE(BytecodeUtil::getCustomSection(source, "name", name_section)); + // Passing module with malformed custom section. + std::string corrupted = {source.data(), name_section.data() + 1}; + EXPECT_FALSE(BytecodeUtil::getFunctionNameIndex(corrupted, actual)); + EXPECT_TRUE(actual.empty()); +} + +TEST(TestWasmCommonUtil, getStrippedSource) { + // Unmodified case. + auto source = readTestWasmFile("abi_export.wasm"); + std::string actual; + EXPECT_TRUE(BytecodeUtil::getStrippedSource(source, actual)); + // If no `precompiled_` is found in the custom sections, + // then the copy of the original should be returned. + EXPECT_FALSE(actual.empty()); + EXPECT_TRUE(actual.data() != source.data()); + EXPECT_EQ(actual, source); + + // Append "precompiled_test" custom section + std::vector custom_section = {// custom section id + 0x00, + // section length + 0x13, + // name length + 0x10, + // name = precompiled_test + 0x70, 0x72, 0x65, 0x63, 0x6f, 0x6d, 0x70, 0x69, 0x6c, 0x65, + 0x64, 0x5f, 0x74, 0x65, 0x73, 0x74, + // content + 0x01, 0x01}; + + source.append(custom_section.data(), custom_section.size()); + std::string_view section = {}; + EXPECT_TRUE(BytecodeUtil::getCustomSection(source, "precompiled_test", section)); + EXPECT_FALSE(section.empty()); + + // Chcek if the custom section is stripped. + actual = {}; + EXPECT_TRUE(BytecodeUtil::getStrippedSource(source, actual)); + // No `precompiled_` is found in the custom sections. + EXPECT_FALSE(actual.empty()); + EXPECT_EQ(actual.size(), source.size() - custom_section.size()); +} + +TEST(TestWasmCommonUtil, getAbiVersion) { + const auto source = readTestWasmFile("abi_export.wasm"); + proxy_wasm::AbiVersion actual; + EXPECT_TRUE(BytecodeUtil::getAbiVersion(source, actual)); + EXPECT_EQ(actual, proxy_wasm::AbiVersion::ProxyWasm_0_2_0); +} + +} // namespace common +} // namespace proxy_wasm diff --git a/test/null_vm_test.cc b/test/null_vm_test.cc index 26d45473d..0201b8599 100644 --- a/test/null_vm_test.cc +++ b/test/null_vm_test.cc @@ -68,7 +68,6 @@ TEST_F(BaseVmTest, NullVmStartup) { EXPECT_TRUE(wasm_vm->cloneable() == Cloneable::InstantiatedModule); auto wasm_vm_clone = wasm_vm->clone(); EXPECT_TRUE(wasm_vm_clone != nullptr); - EXPECT_TRUE(wasm_vm->getCustomSection("user").empty()); EXPECT_TRUE(wasm_vm->load("test_null_vm_plugin", true)); EXPECT_NE(test_null_vm_plugin, nullptr); } diff --git a/test/runtime_test.cc b/test/runtime_test.cc index 078a1db05..7128d8c76 100644 --- a/test/runtime_test.cc +++ b/test/runtime_test.cc @@ -44,21 +44,6 @@ TEST_P(TestVM, ABIVersion) { ASSERT_EQ(vm_->getAbiVersion(), AbiVersion::ProxyWasm_0_2_0); } -TEST_P(TestVM, CustomSection) { - initialize("abi_export.wasm"); - char custom_section[12] = { - 0x00, // custom section id - 0x0a, // section length - 0x04, 0x68, 0x65, 0x79, 0x21, // section name: "hey!" - 0x68, 0x65, 0x6c, 0x6c, 0x6f, // content: "hello" - }; - - source_ = source_.append(&custom_section[0], 12); - ASSERT_TRUE(vm_->load(source_, false)); - auto name_section = vm_->getCustomSection("hey!"); - ASSERT_EQ(name_section, "hello"); -} - TEST_P(TestVM, Memory) { initialize("abi_export.wasm"); ASSERT_TRUE(vm_->load(source_, false)); @@ -188,5 +173,26 @@ TEST_P(TestVM, Trap) { ASSERT_TRUE(integration_->error_message_.find(exp_message) != std::string::npos); } +TEST_P(TestVM, WithPrecompiledSection) { + // Verify that stripping precompile_* custom section works. + initialize("abi_export.wasm"); + // Append precompiled_test section + std::vector custom_section = {// custom section id + 0x00, + // section length + 0x13, + // name length + 0x10, + // name = precompiled_test + 0x70, 0x72, 0x65, 0x63, 0x6f, 0x6d, 0x70, 0x69, 0x6c, 0x65, + 0x64, 0x5f, 0x74, 0x65, 0x73, 0x74, + // content + 0x01, 0x01}; + + source_.append(custom_section.data(), custom_section.size()); + ASSERT_TRUE(vm_->load(source_, false)); + ASSERT_EQ(vm_->getAbiVersion(), AbiVersion::ProxyWasm_0_2_0); +} + } // namespace } // namespace proxy_wasm diff --git a/test/utility.cc b/test/utility.cc index 7e7ba4177..e55a5773c 100644 --- a/test/utility.cc +++ b/test/utility.cc @@ -32,4 +32,13 @@ std::vector getRuntimes() { runtimes.pop_back(); return runtimes; } + +std::string readTestWasmFile(std::string filename) { + auto path = "test/test_data/" + filename; + std::ifstream file(path, std::ios::binary); + EXPECT_FALSE(file.fail()) << "failed to open: " << path; + std::stringstream file_string_stream; + file_string_stream << file.rdbuf(); + return file_string_stream.str(); +} } // namespace proxy_wasm diff --git a/test/utility.h b/test/utility.h index de844d8da..22a86f1f0 100644 --- a/test/utility.h +++ b/test/utility.h @@ -35,6 +35,9 @@ namespace proxy_wasm { +std::vector getRuntimes(); +std::string readTestWasmFile(std::string filename); + struct DummyIntegration : public WasmVmIntegration { ~DummyIntegration() override{}; WasmVmIntegration *clone() override { return new DummyIntegration{}; } @@ -81,21 +84,10 @@ class TestVM : public testing::TestWithParam { vm_->integration().reset(integration_); } - DummyIntegration *integration_; - - void initialize(std::string filename) { - auto path = "test/test_data/" + filename; - std::ifstream file(path, std::ios::binary); - EXPECT_FALSE(file.fail()) << "failed to open: " << path; - std::stringstream file_string_stream; - file_string_stream << file.rdbuf(); - source_ = file_string_stream.str(); - } + void initialize(std::string filename) { source_ = readTestWasmFile(filename); } + DummyIntegration *integration_; std::string source_; std::string runtime_; }; - -std::vector getRuntimes(); - } // namespace proxy_wasm