tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

WasmBinary.cpp (9352B)


      1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
      2 * vim: set ts=8 sts=2 et sw=2 tw=80:
      3 *
      4 * Copyright 2021 Mozilla Foundation
      5 *
      6 * Licensed under the Apache License, Version 2.0 (the "License");
      7 * you may not use this file except in compliance with the License.
      8 * You may obtain a copy of the License at
      9 *
     10 *     http://www.apache.org/licenses/LICENSE-2.0
     11 *
     12 * Unless required by applicable law or agreed to in writing, software
     13 * distributed under the License is distributed on an "AS IS" BASIS,
     14 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     15 * See the License for the specific language governing permissions and
     16 * limitations under the License.
     17 */
     18 
     19 #include "wasm/WasmBinary.h"
     20 
     21 #include "js/Printf.h"
     22 #include "wasm/WasmMetadata.h"
     23 
     24 using namespace js;
     25 using namespace js::wasm;
     26 
     27 // Decoder implementation.
     28 
     29 bool Decoder::failf(const char* msg, ...) {
     30  va_list ap;
     31  va_start(ap, msg);
     32  UniqueChars str(JS_vsmprintf(msg, ap));
     33  va_end(ap);
     34  if (!str) {
     35    return false;
     36  }
     37 
     38  return fail(str.get());
     39 }
     40 
     41 void Decoder::warnf(const char* msg, ...) {
     42  if (!warnings_) {
     43    return;
     44  }
     45 
     46  va_list ap;
     47  va_start(ap, msg);
     48  UniqueChars str(JS_vsmprintf(msg, ap));
     49  va_end(ap);
     50  if (!str) {
     51    return;
     52  }
     53 
     54  (void)warnings_->append(std::move(str));
     55 }
     56 
     57 bool Decoder::fail(size_t errorOffset, const char* msg) {
     58  MOZ_ASSERT(error_);
     59  UniqueChars strWithOffset(JS_smprintf("at offset %zu: %s", errorOffset, msg));
     60  if (!strWithOffset) {
     61    return false;
     62  }
     63 
     64  *error_ = std::move(strWithOffset);
     65  return false;
     66 }
     67 
     68 bool Decoder::readSectionHeader(uint8_t* id, BytecodeRange* range) {
     69  if (!readFixedU8(id)) {
     70    return false;
     71  }
     72 
     73  uint32_t size;
     74  if (!readVarU32(&size)) {
     75    return false;
     76  }
     77 
     78  return BytecodeRange::fromStartAndSize(currentOffset(), size, range);
     79 }
     80 
     81 bool Decoder::startSection(SectionId id, CodeMetadata* codeMeta,
     82                           MaybeBytecodeRange* range, const char* sectionName) {
     83  MOZ_ASSERT(!*range);
     84 
     85  // Record state at beginning of section to allow rewinding to this point
     86  // if, after skipping through several custom sections, we don't find the
     87  // section 'id'.
     88  const uint8_t* const initialCur = cur_;
     89  const size_t initialCustomSectionsLength =
     90      codeMeta->customSectionRanges.length();
     91 
     92  // Maintain a pointer to the current section that gets updated as custom
     93  // sections are skipped.
     94  const uint8_t* currentSectionStart = cur_;
     95 
     96  // Only start a section with 'id', skipping any custom sections before it.
     97 
     98  uint8_t idValue;
     99  if (!readFixedU8(&idValue)) {
    100    goto rewind;
    101  }
    102 
    103  while (idValue != uint8_t(id)) {
    104    if (idValue != uint8_t(SectionId::Custom)) {
    105      goto rewind;
    106    }
    107 
    108    // Rewind to the beginning of the current section since this is what
    109    // skipCustomSection() assumes.
    110    cur_ = currentSectionStart;
    111    if (!skipCustomSection(codeMeta)) {
    112      return false;
    113    }
    114 
    115    // Having successfully skipped a custom section, consider the next
    116    // section.
    117    currentSectionStart = cur_;
    118    if (!readFixedU8(&idValue)) {
    119      goto rewind;
    120    }
    121  }
    122 
    123  // Don't check the size since the range of bytes being decoded might not
    124  // contain the section body. (This is currently the case when streaming: the
    125  // code section header is decoded with the module environment bytes, the
    126  // body of the code section is streamed in separately.)
    127 
    128  uint32_t size;
    129  if (!readVarU32(&size)) {
    130    goto fail;
    131  }
    132 
    133  range->emplace();
    134  if (!BytecodeRange::fromStartAndSize(currentOffset(), size, range->ptr())) {
    135    goto fail;
    136  }
    137  return true;
    138 
    139 rewind:
    140  cur_ = initialCur;
    141  codeMeta->customSectionRanges.shrinkTo(initialCustomSectionsLength);
    142  return true;
    143 
    144 fail:
    145  return failf("failed to start %s section", sectionName);
    146 }
    147 
    148 bool Decoder::finishSection(const BytecodeRange& range,
    149                            const char* sectionName) {
    150  if (range.end != currentOffset()) {
    151    return failf("byte size mismatch in %s section", sectionName);
    152  }
    153  return true;
    154 }
    155 
    156 bool Decoder::startCustomSection(const char* expected, size_t expectedLength,
    157                                 CodeMetadata* codeMeta,
    158                                 MaybeBytecodeRange* range) {
    159  // Record state at beginning of section to allow rewinding to this point
    160  // if, after skipping through several custom sections, we don't find the
    161  // section 'id'.
    162  const uint8_t* const initialCur = cur_;
    163  const size_t initialCustomSectionsLength =
    164      codeMeta->customSectionRanges.length();
    165 
    166  while (true) {
    167    // Try to start a custom section. If we can't, rewind to the beginning
    168    // since we may have skipped several custom sections already looking for
    169    // 'expected'.
    170    if (!startSection(SectionId::Custom, codeMeta, range, "custom")) {
    171      return false;
    172    }
    173    if (!*range) {
    174      goto rewind;
    175    }
    176 
    177    if (bytesRemain() < (*range)->size()) {
    178      goto fail;
    179    }
    180 
    181    uint32_t sectionNameSize;
    182    if (!readVarU32(&sectionNameSize) || sectionNameSize > bytesRemain()) {
    183      goto fail;
    184    }
    185 
    186    // A custom section name must be valid UTF-8
    187    if (!IsUtf8(AsChars(mozilla::Span(cur_, sectionNameSize)))) {
    188      goto fail;
    189    }
    190 
    191    CustomSectionRange secRange;
    192    secRange.name = BytecodeRange(currentOffset(), sectionNameSize);
    193    // The payload starts after the name, and goes to the end of the custom
    194    // section.
    195    if (secRange.name.end > (*range)->end) {
    196      goto fail;
    197    }
    198    secRange.payload.start = secRange.name.end;
    199    secRange.payload.end = (*range)->end;
    200 
    201    // Now that we have a valid custom section, record its offsets in the
    202    // metadata which can be queried by the user via Module.customSections.
    203    // Note: after an entry is appended, it may be popped if this loop or
    204    // the loop in startSection needs to rewind.
    205    if (!codeMeta->customSectionRanges.append(secRange)) {
    206      return false;
    207    }
    208 
    209    // If this is the expected custom section, we're done.
    210    if (!expected || (expectedLength == secRange.name.size() &&
    211                      !memcmp(cur_, expected, secRange.name.size()))) {
    212      cur_ += secRange.name.size();
    213      return true;
    214    }
    215 
    216    // Otherwise, blindly skip the custom section and keep looking.
    217    skipAndFinishCustomSection(**range);
    218    range->reset();
    219  }
    220  MOZ_CRASH("unreachable");
    221 
    222 rewind:
    223  cur_ = initialCur;
    224  codeMeta->customSectionRanges.shrinkTo(initialCustomSectionsLength);
    225  return true;
    226 
    227 fail:
    228  return fail("failed to start custom section");
    229 }
    230 
    231 bool Decoder::finishCustomSection(const char* name,
    232                                  const BytecodeRange& range) {
    233  MOZ_ASSERT(cur_ >= beg_);
    234  MOZ_ASSERT(cur_ <= end_);
    235 
    236  if (error_ && *error_) {
    237    warnf("in the '%s' custom section: %s", name, error_->get());
    238    skipAndFinishCustomSection(range);
    239    return false;
    240  }
    241 
    242  uint32_t actualSize = currentOffset() - range.start;
    243  if (range.size() != actualSize) {
    244    if (actualSize < range.size()) {
    245      warnf("in the '%s' custom section: %" PRIu32 " unconsumed bytes", name,
    246            uint32_t(range.size() - actualSize));
    247    } else {
    248      warnf("in the '%s' custom section: %" PRIu32
    249            " bytes consumed past the end",
    250            name, uint32_t(actualSize - range.size()));
    251    }
    252    skipAndFinishCustomSection(range);
    253    return false;
    254  }
    255 
    256  // Nothing to do! (c.f. skipAndFinishCustomSection())
    257  return true;
    258 }
    259 
    260 void Decoder::skipAndFinishCustomSection(const BytecodeRange& range) {
    261  MOZ_ASSERT(cur_ >= beg_);
    262  MOZ_ASSERT(cur_ <= end_);
    263  cur_ = beg_ + (range.end - offsetInModule_);
    264  MOZ_ASSERT(cur_ <= end_);
    265  clearError();
    266 }
    267 
    268 bool Decoder::skipCustomSection(CodeMetadata* codeMeta) {
    269  MaybeBytecodeRange range;
    270  if (!startCustomSection(nullptr, 0, codeMeta, &range)) {
    271    return false;
    272  }
    273  if (!range) {
    274    return fail("expected custom section");
    275  }
    276 
    277  skipAndFinishCustomSection(*range);
    278  return true;
    279 }
    280 
    281 bool Decoder::startNameSubsection(NameType nameType,
    282                                  mozilla::Maybe<uint32_t>* endOffset) {
    283  MOZ_ASSERT(!*endOffset);
    284 
    285  const uint8_t* const initialPosition = cur_;
    286 
    287  uint8_t nameTypeValue;
    288  if (!readFixedU8(&nameTypeValue)) {
    289    goto rewind;
    290  }
    291 
    292  if (nameTypeValue != uint8_t(nameType)) {
    293    goto rewind;
    294  }
    295 
    296  uint32_t payloadLength;
    297  if (!readVarU32(&payloadLength) || payloadLength > bytesRemain()) {
    298    return fail("bad name subsection payload length");
    299  }
    300 
    301  *endOffset = mozilla::Some(currentOffset() + payloadLength);
    302  return true;
    303 
    304 rewind:
    305  cur_ = initialPosition;
    306  return true;
    307 }
    308 
    309 bool Decoder::finishNameSubsection(uint32_t endOffset) {
    310  uint32_t actual = currentOffset();
    311  if (endOffset != actual) {
    312    return failf("bad name subsection length (endOffset: %" PRIu32
    313                 ", actual: %" PRIu32 ")",
    314                 endOffset, actual);
    315  }
    316 
    317  return true;
    318 }
    319 
    320 bool Decoder::skipNameSubsection() {
    321  uint8_t nameTypeValue;
    322  if (!readFixedU8(&nameTypeValue)) {
    323    return fail("unable to read name subsection id");
    324  }
    325 
    326  switch (nameTypeValue) {
    327    case uint8_t(NameType::Module):
    328    case uint8_t(NameType::Function):
    329      return fail("out of order name subsections");
    330    default:
    331      break;
    332  }
    333 
    334  uint32_t payloadLength;
    335  if (!readVarU32(&payloadLength) || !readBytes(payloadLength)) {
    336    return fail("bad name subsection payload length");
    337  }
    338 
    339  return true;
    340 }