diff options
Diffstat (limited to '')
-rw-r--r-- | js/src/wasm/WasmBinary.cpp | 335 |
1 files changed, 335 insertions, 0 deletions
diff --git a/js/src/wasm/WasmBinary.cpp b/js/src/wasm/WasmBinary.cpp new file mode 100644 index 0000000000..ccf50f9d2d --- /dev/null +++ b/js/src/wasm/WasmBinary.cpp @@ -0,0 +1,335 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * vim: set ts=8 sts=2 et sw=2 tw=80: + * + * Copyright 2021 Mozilla Foundation + * + * 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 "wasm/WasmBinary.h" + +#include "js/Printf.h" +#include "wasm/WasmValidate.h" + +using namespace js; +using namespace js::wasm; + +// Decoder implementation. + +bool Decoder::failf(const char* msg, ...) { + va_list ap; + va_start(ap, msg); + UniqueChars str(JS_vsmprintf(msg, ap)); + va_end(ap); + if (!str) { + return false; + } + + return fail(str.get()); +} + +void Decoder::warnf(const char* msg, ...) { + if (!warnings_) { + return; + } + + va_list ap; + va_start(ap, msg); + UniqueChars str(JS_vsmprintf(msg, ap)); + va_end(ap); + if (!str) { + return; + } + + (void)warnings_->append(std::move(str)); +} + +bool Decoder::fail(size_t errorOffset, const char* msg) { + MOZ_ASSERT(error_); + UniqueChars strWithOffset(JS_smprintf("at offset %zu: %s", errorOffset, msg)); + if (!strWithOffset) { + return false; + } + + *error_ = std::move(strWithOffset); + return false; +} + +bool Decoder::readSectionHeader(uint8_t* id, SectionRange* range) { + if (!readFixedU8(id)) { + return false; + } + + uint32_t size; + if (!readVarU32(&size)) { + return false; + } + + range->start = currentOffset(); + range->size = size; + return true; +} + +bool Decoder::startSection(SectionId id, ModuleEnvironment* env, + MaybeSectionRange* range, const char* sectionName) { + MOZ_ASSERT(!*range); + + // Record state at beginning of section to allow rewinding to this point + // if, after skipping through several custom sections, we don't find the + // section 'id'. + const uint8_t* const initialCur = cur_; + const size_t initialCustomSectionsLength = env->customSections.length(); + + // Maintain a pointer to the current section that gets updated as custom + // sections are skipped. + const uint8_t* currentSectionStart = cur_; + + // Only start a section with 'id', skipping any custom sections before it. + + uint8_t idValue; + if (!readFixedU8(&idValue)) { + goto rewind; + } + + while (idValue != uint8_t(id)) { + if (idValue != uint8_t(SectionId::Custom)) { + goto rewind; + } + + // Rewind to the beginning of the current section since this is what + // skipCustomSection() assumes. + cur_ = currentSectionStart; + if (!skipCustomSection(env)) { + return false; + } + + // Having successfully skipped a custom section, consider the next + // section. + currentSectionStart = cur_; + if (!readFixedU8(&idValue)) { + goto rewind; + } + } + + // Don't check the size since the range of bytes being decoded might not + // contain the section body. (This is currently the case when streaming: the + // code section header is decoded with the module environment bytes, the + // body of the code section is streamed in separately.) + + uint32_t size; + if (!readVarU32(&size)) { + goto fail; + } + + range->emplace(); + (*range)->start = currentOffset(); + (*range)->size = size; + return true; + +rewind: + cur_ = initialCur; + env->customSections.shrinkTo(initialCustomSectionsLength); + return true; + +fail: + return failf("failed to start %s section", sectionName); +} + +bool Decoder::finishSection(const SectionRange& range, + const char* sectionName) { + if (resilientMode_) { + return true; + } + if (range.size != currentOffset() - range.start) { + return failf("byte size mismatch in %s section", sectionName); + } + return true; +} + +bool Decoder::startCustomSection(const char* expected, size_t expectedLength, + ModuleEnvironment* env, + MaybeSectionRange* range) { + // Record state at beginning of section to allow rewinding to this point + // if, after skipping through several custom sections, we don't find the + // section 'id'. + const uint8_t* const initialCur = cur_; + const size_t initialCustomSectionsLength = env->customSections.length(); + + while (true) { + // Try to start a custom section. If we can't, rewind to the beginning + // since we may have skipped several custom sections already looking for + // 'expected'. + if (!startSection(SectionId::Custom, env, range, "custom")) { + return false; + } + if (!*range) { + goto rewind; + } + + if (bytesRemain() < (*range)->size) { + goto fail; + } + + CustomSectionEnv sec; + if (!readVarU32(&sec.nameLength) || sec.nameLength > bytesRemain()) { + goto fail; + } + + sec.nameOffset = currentOffset(); + sec.payloadOffset = sec.nameOffset + sec.nameLength; + + uint32_t payloadEnd = (*range)->start + (*range)->size; + if (sec.payloadOffset > payloadEnd) { + goto fail; + } + + sec.payloadLength = payloadEnd - sec.payloadOffset; + + // Now that we have a valid custom section, record its offsets in the + // metadata which can be queried by the user via Module.customSections. + // Note: after an entry is appended, it may be popped if this loop or + // the loop in startSection needs to rewind. + if (!env->customSections.append(sec)) { + return false; + } + + // If this is the expected custom section, we're done. + if (!expected || (expectedLength == sec.nameLength && + !memcmp(cur_, expected, sec.nameLength))) { + cur_ += sec.nameLength; + return true; + } + + // Otherwise, blindly skip the custom section and keep looking. + skipAndFinishCustomSection(**range); + range->reset(); + } + MOZ_CRASH("unreachable"); + +rewind: + cur_ = initialCur; + env->customSections.shrinkTo(initialCustomSectionsLength); + return true; + +fail: + return fail("failed to start custom section"); +} + +void Decoder::finishCustomSection(const char* name, const SectionRange& range) { + MOZ_ASSERT(cur_ >= beg_); + MOZ_ASSERT(cur_ <= end_); + + if (error_ && *error_) { + warnf("in the '%s' custom section: %s", name, error_->get()); + skipAndFinishCustomSection(range); + return; + } + + uint32_t actualSize = currentOffset() - range.start; + if (range.size != actualSize) { + if (actualSize < range.size) { + warnf("in the '%s' custom section: %" PRIu32 " unconsumed bytes", name, + uint32_t(range.size - actualSize)); + } else { + warnf("in the '%s' custom section: %" PRIu32 + " bytes consumed past the end", + name, uint32_t(actualSize - range.size)); + } + skipAndFinishCustomSection(range); + return; + } + + // Nothing to do! (c.f. skipAndFinishCustomSection()) +} + +void Decoder::skipAndFinishCustomSection(const SectionRange& range) { + MOZ_ASSERT(cur_ >= beg_); + MOZ_ASSERT(cur_ <= end_); + cur_ = (beg_ + (range.start - offsetInModule_)) + range.size; + MOZ_ASSERT(cur_ <= end_); + clearError(); +} + +bool Decoder::skipCustomSection(ModuleEnvironment* env) { + MaybeSectionRange range; + if (!startCustomSection(nullptr, 0, env, &range)) { + return false; + } + if (!range) { + return fail("expected custom section"); + } + + skipAndFinishCustomSection(*range); + return true; +} + +bool Decoder::startNameSubsection(NameType nameType, + Maybe<uint32_t>* endOffset) { + MOZ_ASSERT(!*endOffset); + + const uint8_t* const initialPosition = cur_; + + uint8_t nameTypeValue; + if (!readFixedU8(&nameTypeValue)) { + goto rewind; + } + + if (nameTypeValue != uint8_t(nameType)) { + goto rewind; + } + + uint32_t payloadLength; + if (!readVarU32(&payloadLength) || payloadLength > bytesRemain()) { + return fail("bad name subsection payload length"); + } + + *endOffset = Some(currentOffset() + payloadLength); + return true; + +rewind: + cur_ = initialPosition; + return true; +} + +bool Decoder::finishNameSubsection(uint32_t endOffset) { + uint32_t actual = currentOffset(); + if (endOffset != actual) { + return failf("bad name subsection length (endOffset: %" PRIu32 + ", actual: %" PRIu32 ")", + endOffset, actual); + } + + return true; +} + +bool Decoder::skipNameSubsection() { + uint8_t nameTypeValue; + if (!readFixedU8(&nameTypeValue)) { + return fail("unable to read name subsection id"); + } + + switch (nameTypeValue) { + case uint8_t(NameType::Module): + case uint8_t(NameType::Function): + return fail("out of order name subsections"); + default: + break; + } + + uint32_t payloadLength; + if (!readVarU32(&payloadLength) || !readBytes(payloadLength)) { + return fail("bad name subsection payload length"); + } + + return true; +} |