| /* |
| * Copyright (C) 2018 Yusuke Suzuki <yusukesuzuki@slowstart.org>. |
| * |
| * Redistribution and use in source and binary forms, with or without |
| * modification, are permitted provided that the following conditions |
| * are met: |
| * 1. Redistributions of source code must retain the above copyright |
| * notice, this list of conditions and the following disclaimer. |
| * 2. Redistributions in binary form must reproduce the above copyright |
| * notice, this list of conditions and the following disclaimer in the |
| * documentation and/or other materials provided with the distribution. |
| * |
| * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY |
| * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE |
| * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR |
| * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR |
| * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, |
| * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, |
| * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR |
| * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY |
| * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
| * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
| * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
| */ |
| |
| #include "config.h" |
| #include "WasmStreamingParser.h" |
| |
| #if ENABLE(WEBASSEMBLY) |
| |
| #include "WasmModuleInformation.h" |
| #include "WasmOps.h" |
| #include "WasmParser.h" |
| #include "WasmSectionParser.h" |
| #include "WasmTypeDefinitionInlines.h" |
| #include <wtf/UnalignedAccess.h> |
| |
| namespace JSC { namespace Wasm { |
| |
| namespace WasmStreamingParserInternal { |
| static constexpr bool verbose = false; |
| } |
| |
| #define WASM_STREAMING_PARSER_FAIL_IF_HELPER_FAILS(helper) \ |
| do { \ |
| auto helperResult = helper; \ |
| if (UNLIKELY(!helperResult)) { \ |
| m_errorMessage = helperResult.error(); \ |
| return State::FatalError; \ |
| } \ |
| } while (0) |
| |
| ALWAYS_INLINE std::optional<uint8_t> parseUInt7(const uint8_t* data, size_t& offset, size_t size) |
| { |
| if (offset >= size) |
| return false; |
| uint8_t result = data[offset++]; |
| if (result < 0x80) |
| return result; |
| return std::nullopt; |
| } |
| |
| template <typename ...Args> |
| NEVER_INLINE auto WARN_UNUSED_RETURN StreamingParser::fail(Args... args) -> State |
| { |
| using namespace FailureHelper; // See ADL comment in namespace above. |
| m_errorMessage = makeString("WebAssembly.Module doesn't parse at byte "_s, String::number(m_offset), ": "_s, makeString(args)...); |
| dataLogLnIf(WasmStreamingParserInternal::verbose, m_errorMessage); |
| return State::FatalError; |
| } |
| |
| StreamingParser::StreamingParser(ModuleInformation& info, StreamingParserClient& client) |
| : m_info(info) |
| , m_client(client) |
| { |
| dataLogLnIf(WasmStreamingParserInternal::verbose, "starting validation"); |
| } |
| |
| auto StreamingParser::parseModuleHeader(Vector<uint8_t>&& data) -> State |
| { |
| ASSERT(data.size() == moduleHeaderSize); |
| dataLogLnIf(WasmStreamingParserInternal::verbose, "header validation"); |
| WASM_PARSER_FAIL_IF(data[0] != '\0' || data[1] != 'a' || data[2] != 's' || data[3] != 'm', "module doesn't start with '\\0asm'"); |
| uint32_t versionNumber = WTF::unalignedLoad<uint32_t>(data.data() + 4); |
| WASM_PARSER_FAIL_IF(versionNumber != expectedVersionNumber, "unexpected version number ", versionNumber, " expected ", expectedVersionNumber); |
| return State::SectionID; |
| } |
| |
| auto StreamingParser::parseSectionID(Vector<uint8_t>&& data) -> State |
| { |
| ASSERT(data.size() == sectionIDSize); |
| size_t offset = 0; |
| auto result = parseUInt7(data.data(), offset, data.size()); |
| WASM_PARSER_FAIL_IF(!result, "can't get section byte"); |
| |
| Section section = Section::Custom; |
| WASM_PARSER_FAIL_IF(!decodeSection(*result, section), "invalid section"); |
| ASSERT(section != Section::Begin); |
| WASM_PARSER_FAIL_IF(!validateOrder(m_previousKnownSection, section), "invalid section order, ", m_previousKnownSection, " followed by ", section); |
| m_section = section; |
| if (isKnownSection(section)) |
| m_previousKnownSection = section; |
| return State::SectionSize; |
| } |
| |
| auto StreamingParser::parseSectionSize(uint32_t sectionLength) -> State |
| { |
| m_sectionLength = sectionLength; |
| if (m_section == Section::Code) |
| return State::CodeSectionSize; |
| return State::SectionPayload; |
| } |
| |
| auto StreamingParser::parseCodeSectionSize(uint32_t functionCount) -> State |
| { |
| m_info->codeSectionSize = m_sectionLength; |
| m_functionCount = functionCount; |
| m_functionIndex = 0; |
| m_codeOffset = m_offset; |
| |
| WASM_PARSER_FAIL_IF(functionCount == std::numeric_limits<uint32_t>::max(), "Code section's count is too big ", functionCount); |
| WASM_PARSER_FAIL_IF(functionCount != m_info->functions.size(), "Code section count ", functionCount, " exceeds the declared number of functions ", m_info->functions.size()); |
| |
| if (m_functionIndex == m_functionCount) { |
| WASM_PARSER_FAIL_IF((m_codeOffset + m_sectionLength) != m_nextOffset, "parsing ended before the end of ", m_section, " section"); |
| if (!m_client.didReceiveSectionData(m_section)) |
| return State::FatalError; |
| return State::SectionID; |
| } |
| return State::FunctionSize; |
| } |
| |
| auto StreamingParser::parseFunctionSize(uint32_t functionSize) -> State |
| { |
| m_functionSize = functionSize; |
| WASM_PARSER_FAIL_IF(functionSize > maxFunctionSize, "Code function's size ", functionSize, " is too big"); |
| return State::FunctionPayload; |
| } |
| |
| auto StreamingParser::parseFunctionPayload(Vector<uint8_t>&& data) -> State |
| { |
| auto& function = m_info->functions[m_functionIndex]; |
| function.start = m_offset; |
| function.end = m_offset + m_functionSize; |
| function.data = WTFMove(data); |
| dataLogLnIf(WasmStreamingParserInternal::verbose, "Processing function starting at: ", function.start, " and ending at: ", function.end); |
| if (!m_client.didReceiveFunctionData(m_functionIndex, function)) |
| return State::FatalError; |
| ++m_functionIndex; |
| |
| if (m_functionIndex == m_functionCount) { |
| WASM_PARSER_FAIL_IF((m_codeOffset + m_sectionLength) != (m_offset + m_functionSize), "parsing ended before the end of ", m_section, " section"); |
| if (!m_client.didReceiveSectionData(m_section)) |
| return State::FatalError; |
| return State::SectionID; |
| } |
| return State::FunctionSize; |
| } |
| |
| auto StreamingParser::parseSectionPayload(Vector<uint8_t>&& data) -> State |
| { |
| SectionParser parser(data.data(), data.size(), m_offset, m_info.get()); |
| switch (m_section) { |
| #define WASM_SECTION_PARSE(NAME, ID, DESCRIPTION) \ |
| case Section::NAME: { \ |
| WASM_STREAMING_PARSER_FAIL_IF_HELPER_FAILS(parser.parse ## NAME()); \ |
| break; \ |
| } |
| FOR_EACH_KNOWN_WASM_SECTION(WASM_SECTION_PARSE) |
| #undef WASM_SECTION_PARSE |
| |
| case Section::Custom: { |
| WASM_STREAMING_PARSER_FAIL_IF_HELPER_FAILS(parser.parseCustom()); |
| break; |
| } |
| |
| case Section::Begin: { |
| RELEASE_ASSERT_NOT_REACHED(); |
| break; |
| } |
| } |
| |
| WASM_PARSER_FAIL_IF(parser.length() != parser.offset(), "parsing ended before the end of ", m_section, " section"); |
| |
| if (!m_client.didReceiveSectionData(m_section)) |
| return State::FatalError; |
| return State::SectionID; |
| } |
| |
| auto StreamingParser::consume(const uint8_t* bytes, size_t bytesSize, size_t& offsetInBytes, size_t requiredSize) -> std::optional<Vector<uint8_t>> |
| { |
| if (m_remaining.size() == requiredSize) { |
| Vector<uint8_t> result = WTFMove(m_remaining); |
| m_nextOffset += requiredSize; |
| return result; |
| } |
| |
| if (m_remaining.size() > requiredSize) { |
| Vector<uint8_t> result { m_remaining.data(), requiredSize }; |
| m_remaining.remove(0, requiredSize); |
| m_nextOffset += requiredSize; |
| return result; |
| } |
| |
| ASSERT(m_remaining.size() < requiredSize); |
| size_t bytesRemainingSize = bytesSize - offsetInBytes; |
| size_t totalDataSize = m_remaining.size() + bytesRemainingSize; |
| if (totalDataSize < requiredSize) { |
| m_remaining.append(bytes + offsetInBytes, bytesRemainingSize); |
| offsetInBytes = bytesSize; |
| return std::nullopt; |
| } |
| |
| size_t usedSize = requiredSize - m_remaining.size(); |
| m_remaining.append(bytes + offsetInBytes, usedSize); |
| offsetInBytes += usedSize; |
| Vector<uint8_t> result = WTFMove(m_remaining); |
| m_nextOffset += requiredSize; |
| return result; |
| } |
| |
| auto StreamingParser::consumeVarUInt32(const uint8_t* bytes, size_t bytesSize, size_t& offsetInBytes, IsEndOfStream isEndOfStream) -> Expected<uint32_t, State> |
| { |
| constexpr size_t maxSize = WTF::LEBDecoder::maxByteLength<uint32_t>(); |
| size_t bytesRemainingSize = bytesSize - offsetInBytes; |
| size_t totalDataSize = m_remaining.size() + bytesRemainingSize; |
| if (m_remaining.size() >= maxSize) { |
| // Do nothing. |
| } else if (totalDataSize >= maxSize) { |
| size_t usedSize = maxSize - m_remaining.size(); |
| m_remaining.append(bytes + offsetInBytes, usedSize); |
| offsetInBytes += usedSize; |
| } else { |
| m_remaining.append(bytes + offsetInBytes, bytesRemainingSize); |
| offsetInBytes += bytesRemainingSize; |
| // If the given bytes are the end of the stream, we try to parse VarUInt32 |
| // with the current remaining data since VarUInt32 may not require `maxSize`. |
| if (isEndOfStream == IsEndOfStream::No) |
| return makeUnexpected(m_state); |
| } |
| |
| size_t offset = 0; |
| uint32_t result = 0; |
| if (!WTF::LEBDecoder::decodeUInt32(m_remaining.data(), m_remaining.size(), offset, result)) |
| return makeUnexpected(State::FatalError); |
| size_t consumedSize = offset; |
| m_remaining.remove(0, consumedSize); |
| m_nextOffset += consumedSize; |
| return result; |
| } |
| |
| auto StreamingParser::addBytes(const uint8_t* bytes, size_t bytesSize, IsEndOfStream isEndOfStream) -> State |
| { |
| if (m_state == State::FatalError) |
| return m_state; |
| |
| m_totalSize += bytesSize; |
| if (UNLIKELY(m_totalSize.hasOverflowed() || m_totalSize > maxModuleSize)) { |
| m_state = fail("module size is too large, maximum ", maxModuleSize); |
| return m_state; |
| } |
| |
| if (UNLIKELY(Options::useEagerWebAssemblyModuleHashing())) |
| m_hasher.addBytes(bytes, bytesSize); |
| |
| size_t offsetInBytes = 0; |
| while (true) { |
| ASSERT(offsetInBytes <= bytesSize); |
| switch (m_state) { |
| case State::ModuleHeader: { |
| auto result = consume(bytes, bytesSize, offsetInBytes, moduleHeaderSize); |
| if (!result) |
| return m_state; |
| m_state = parseModuleHeader(WTFMove(*result)); |
| break; |
| } |
| |
| case State::SectionID: { |
| auto result = consume(bytes, bytesSize, offsetInBytes, sectionIDSize); |
| if (!result) |
| return m_state; |
| m_state = parseSectionID(WTFMove(*result)); |
| break; |
| } |
| |
| case State::SectionSize: { |
| auto result = consumeVarUInt32(bytes, bytesSize, offsetInBytes, isEndOfStream); |
| if (!result) { |
| if (result.error() == State::FatalError) |
| m_state = failOnState(m_state); |
| else |
| m_state = result.error(); |
| return m_state; |
| } |
| m_state = parseSectionSize(*result); |
| break; |
| } |
| |
| case State::SectionPayload: { |
| auto result = consume(bytes, bytesSize, offsetInBytes, m_sectionLength); |
| if (!result) |
| return m_state; |
| m_state = parseSectionPayload(WTFMove(*result)); |
| break; |
| } |
| |
| case State::CodeSectionSize: { |
| auto result = consumeVarUInt32(bytes, bytesSize, offsetInBytes, isEndOfStream); |
| if (!result) { |
| if (result.error() == State::FatalError) |
| m_state = failOnState(m_state); |
| else |
| m_state = result.error(); |
| return m_state; |
| } |
| m_state = parseCodeSectionSize(*result); |
| break; |
| } |
| |
| case State::FunctionSize: { |
| auto result = consumeVarUInt32(bytes, bytesSize, offsetInBytes, isEndOfStream); |
| if (!result) { |
| if (result.error() == State::FatalError) |
| m_state = failOnState(m_state); |
| else |
| m_state = result.error(); |
| return m_state; |
| } |
| m_state = parseFunctionSize(*result); |
| break; |
| } |
| |
| case State::FunctionPayload: { |
| auto result = consume(bytes, bytesSize, offsetInBytes, m_functionSize); |
| if (!result) |
| return m_state; |
| m_state = parseFunctionPayload(WTFMove(*result)); |
| break; |
| } |
| |
| case State::Finished: |
| case State::FatalError: |
| return m_state; |
| } |
| |
| m_offset = m_nextOffset; |
| } |
| } |
| |
| auto StreamingParser::failOnState(State) -> State |
| { |
| switch (m_state) { |
| case State::ModuleHeader: |
| return fail("expected a module of at least ", moduleHeaderSize, " bytes"); |
| case State::SectionID: |
| return fail("can't get section byte"); |
| case State::SectionSize: |
| return fail("can't get ", m_section, " section's length"); |
| case State::SectionPayload: |
| return fail(m_section, " section of size ", m_sectionLength, " would overflow Module's size"); |
| case State::CodeSectionSize: |
| return fail("can't get Code section's count"); |
| case State::FunctionSize: |
| return fail("can't get ", m_functionIndex, "th Code function's size"); |
| case State::FunctionPayload: |
| return fail("Code function's size ", m_functionSize, " exceeds the module's remaining size"); |
| case State::Finished: |
| case State::FatalError: |
| return m_state; |
| } |
| return m_state; |
| } |
| |
| auto StreamingParser::finalize() -> State |
| { |
| addBytes(nullptr, 0, IsEndOfStream::Yes); |
| switch (m_state) { |
| case State::ModuleHeader: |
| case State::SectionSize: |
| case State::SectionPayload: |
| case State::CodeSectionSize: |
| case State::FunctionSize: |
| case State::FunctionPayload: |
| m_state = failOnState(m_state); |
| break; |
| |
| case State::Finished: |
| case State::FatalError: |
| break; |
| |
| case State::SectionID: |
| if (m_functionIndex != m_info->functions.size()) { |
| m_state = fail("Number of functions parsed (", m_functionCount, ") does not match the number of declared functions (", m_info->functions.size(), ")"); |
| break; |
| } |
| if (m_remaining.isEmpty()) { |
| if (UNLIKELY(Options::useEagerWebAssemblyModuleHashing())) |
| m_info->nameSection->setHash(m_hasher.computeHexDigest()); |
| m_state = State::Finished; |
| m_client.didFinishParsing(); |
| } else |
| m_state = failOnState(State::SectionID); |
| break; |
| } |
| return m_state; |
| } |
| |
| } } // namespace JSC::Wasm |
| |
| #endif // ENABLE(WEBASSEMBLY) |