tor-browser

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

Xdr.h (12252B)


      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 * This Source Code Form is subject to the terms of the Mozilla Public
      4 * License, v. 2.0. If a copy of the MPL was not distributed with this
      5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
      6 
      7 #ifndef vm_Xdr_h
      8 #define vm_Xdr_h
      9 
     10 #include "mozilla/Assertions.h"  // MOZ_ASSERT, MOZ_CRASH
     11 #include "mozilla/MaybeOneOf.h"  // mozilla::MaybeOneOf
     12 #include "mozilla/Try.h"         // MOZ_TRY
     13 #include "mozilla/Utf8.h"        // mozilla::Utf8Unit
     14 
     15 #include <stddef.h>     // size_t
     16 #include <stdint.h>     // uint8_t, uint16_t, uint32_t, uint64_t
     17 #include <string.h>     // memcpy
     18 #include <type_traits>  // std::enable_if_t
     19 
     20 #include "js/AllocPolicy.h"  // ReportOutOfMemory
     21 #include "js/Transcoding.h"  // JS::TranscodeResult, JS::TranscodeBuffer, JS::TranscodeRange, IsTranscodingBytecodeAligned, IsTranscodingBytecodeOffsetAligned
     22 #include "js/TypeDecls.h"    // JS::Latin1Char
     23 #include "js/UniquePtr.h"    // UniquePtr
     24 #include "js/Utility.h"      // JS::FreePolicy
     25 
     26 struct JSContext;
     27 
     28 namespace js {
     29 
     30 enum XDRMode { XDR_ENCODE, XDR_DECODE };
     31 
     32 template <typename T>
     33 using XDRResultT = mozilla::Result<T, JS::TranscodeResult>;
     34 using XDRResult = XDRResultT<mozilla::Ok>;
     35 
     36 class XDRBufferBase {
     37 public:
     38  explicit XDRBufferBase(FrontendContext* fc, size_t cursor = 0)
     39      : fc_(fc), cursor_(cursor) {}
     40 
     41  FrontendContext* fc() const { return fc_; }
     42 
     43  size_t cursor() const { return cursor_; }
     44 
     45 protected:
     46  FrontendContext* const fc_;
     47  size_t cursor_;
     48 };
     49 
     50 template <XDRMode mode>
     51 class XDRBuffer;
     52 
     53 template <>
     54 class XDRBuffer<XDR_ENCODE> : public XDRBufferBase {
     55 public:
     56  XDRBuffer(FrontendContext* fc, JS::TranscodeBuffer& buffer, size_t cursor = 0)
     57      : XDRBufferBase(fc, cursor), buffer_(buffer) {}
     58 
     59  uint8_t* write(size_t n) {
     60    MOZ_ASSERT(n != 0);
     61    if (!buffer_.growByUninitialized(n)) {
     62      ReportOutOfMemory(fc());
     63      return nullptr;
     64    }
     65    uint8_t* ptr = &buffer_[cursor_];
     66    cursor_ += n;
     67    return ptr;
     68  }
     69 
     70  bool align32() {
     71    size_t extra = cursor_ % 4;
     72    if (extra) {
     73      size_t padding = 4 - extra;
     74      if (!buffer_.appendN(0, padding)) {
     75        ReportOutOfMemory(fc());
     76        return false;
     77      }
     78      cursor_ += padding;
     79    }
     80    return true;
     81  }
     82 
     83  bool isAligned32() { return cursor_ % 4 == 0; }
     84 
     85  const uint8_t* read(size_t n) {
     86    MOZ_CRASH("Should never read in encode mode");
     87    return nullptr;
     88  }
     89 
     90  const uint8_t* peek(size_t n) {
     91    MOZ_CRASH("Should never read in encode mode");
     92    return nullptr;
     93  }
     94 
     95  uint8_t* bufferAt(size_t cursor) {
     96    MOZ_ASSERT(cursor < buffer_.length());
     97    return &buffer_[cursor];
     98  }
     99 
    100 private:
    101  JS::TranscodeBuffer& buffer_;
    102 };
    103 
    104 template <>
    105 class XDRBuffer<XDR_DECODE> : public XDRBufferBase {
    106 public:
    107  XDRBuffer(FrontendContext* fc, const JS::TranscodeRange& range)
    108      : XDRBufferBase(fc), buffer_(range) {}
    109 
    110  // This isn't used by XDRStencilDecoder.
    111  // Defined just for XDRState, shared with XDRStencilEncoder.
    112  XDRBuffer(FrontendContext* fc, JS::TranscodeBuffer& buffer, size_t cursor = 0)
    113      : XDRBufferBase(fc, cursor), buffer_(buffer.begin(), buffer.length()) {}
    114 
    115  bool align32() {
    116    size_t extra = cursor_ % 4;
    117    if (extra) {
    118      size_t padding = 4 - extra;
    119      cursor_ += padding;
    120 
    121      // Don't let buggy code read past our buffer
    122      if (cursor_ > buffer_.length()) {
    123        return false;
    124      }
    125    }
    126    return true;
    127  }
    128 
    129  bool isAligned32() { return cursor_ % 4 == 0; }
    130 
    131  const uint8_t* read(size_t n) {
    132    MOZ_ASSERT(cursor_ < buffer_.length());
    133    const uint8_t* ptr = &buffer_[cursor_];
    134    cursor_ += n;
    135 
    136    // Don't let buggy code read past our buffer
    137    if (cursor_ > buffer_.length()) {
    138      return nullptr;
    139    }
    140 
    141    return ptr;
    142  }
    143 
    144  const uint8_t* peek(size_t n) {
    145    MOZ_ASSERT(cursor_ < buffer_.length());
    146    const uint8_t* ptr = &buffer_[cursor_];
    147 
    148    // Don't let buggy code read past our buffer
    149    if (cursor_ + n > buffer_.length()) {
    150      return nullptr;
    151    }
    152 
    153    return ptr;
    154  }
    155 
    156  uint8_t* write(size_t n) {
    157    MOZ_CRASH("Should never write in decode mode");
    158    return nullptr;
    159  }
    160 
    161 private:
    162  const JS::TranscodeRange buffer_;
    163 };
    164 
    165 template <typename CharT>
    166 using XDRTranscodeString =
    167    mozilla::MaybeOneOf<const CharT*, js::UniquePtr<CharT[], JS::FreePolicy>>;
    168 
    169 class XDRCoderBase {
    170 private:
    171 #ifdef DEBUG
    172  JS::TranscodeResult resultCode_;
    173 #endif
    174 
    175 protected:
    176  XDRCoderBase()
    177 #ifdef DEBUG
    178      : resultCode_(JS::TranscodeResult::Ok)
    179 #endif
    180  {
    181  }
    182 
    183 public:
    184 #ifdef DEBUG
    185  // Record logical failures of XDR.
    186  JS::TranscodeResult resultCode() const { return resultCode_; }
    187  void setResultCode(JS::TranscodeResult code) {
    188    MOZ_ASSERT(resultCode() == JS::TranscodeResult::Ok);
    189    resultCode_ = code;
    190  }
    191  bool validateResultCode(FrontendContext* fc, JS::TranscodeResult code) const;
    192 #endif
    193 };
    194 
    195 /*
    196 * XDR serialization state.  All data is encoded in native endian, except
    197 * bytecode.
    198 */
    199 template <XDRMode mode>
    200 class XDRState : public XDRCoderBase {
    201 protected:
    202  XDRBuffer<mode> mainBuf;
    203  XDRBuffer<mode>* buf;
    204 
    205 public:
    206  XDRState(FrontendContext* fc, JS::TranscodeBuffer& buffer, size_t cursor = 0)
    207      : mainBuf(fc, buffer, cursor), buf(&mainBuf) {}
    208 
    209  template <typename RangeType>
    210  XDRState(FrontendContext* fc, const RangeType& range)
    211      : mainBuf(fc, range), buf(&mainBuf) {}
    212 
    213  // No default copy constructor or copying assignment, because |buf|
    214  // is an internal pointer.
    215  XDRState(const XDRState&) = delete;
    216  XDRState& operator=(const XDRState&) = delete;
    217 
    218  ~XDRState() = default;
    219 
    220  FrontendContext* fc() const { return mainBuf.fc(); }
    221 
    222  template <typename T = mozilla::Ok>
    223  XDRResultT<T> fail(JS::TranscodeResult code) {
    224 #ifdef DEBUG
    225    MOZ_ASSERT(code != JS::TranscodeResult::Ok);
    226    MOZ_ASSERT(validateResultCode(fc(), code));
    227    setResultCode(code);
    228 #endif
    229    return mozilla::Err(code);
    230  }
    231 
    232  XDRResult align32() {
    233    if (!buf->align32()) {
    234      return fail(JS::TranscodeResult::Throw);
    235    }
    236    return mozilla::Ok();
    237  }
    238 
    239  bool isAligned32() { return buf->isAligned32(); }
    240 
    241  XDRResult readData(const uint8_t** pptr, size_t length) {
    242    const uint8_t* ptr = buf->read(length);
    243    if (!ptr) {
    244      return fail(JS::TranscodeResult::Failure_BadDecode);
    245    }
    246    *pptr = ptr;
    247    return mozilla::Ok();
    248  }
    249 
    250  // Peek the `sizeof(T)` bytes and return the pointer to `*pptr`.
    251  // The caller is responsible for aligning the buffer by calling `align32`.
    252  template <typename T>
    253  XDRResult peekData(const T** pptr) {
    254    static_assert(alignof(T) <= 4);
    255    MOZ_ASSERT(isAligned32());
    256    const uint8_t* ptr = buf->peek(sizeof(T));
    257    if (!ptr) {
    258      return fail(JS::TranscodeResult::Failure_BadDecode);
    259    }
    260    *pptr = reinterpret_cast<const T*>(ptr);
    261    return mozilla::Ok();
    262  }
    263 
    264  // Peek uint32_t data.
    265  XDRResult peekUint32(uint32_t* n) {
    266    MOZ_ASSERT(mode == XDR_DECODE);
    267    const uint8_t* ptr = buf->peek(sizeof(*n));
    268    if (!ptr) {
    269      return fail(JS::TranscodeResult::Failure_BadDecode);
    270    }
    271    *n = *reinterpret_cast<const uint32_t*>(ptr);
    272    return mozilla::Ok();
    273  }
    274 
    275  XDRResult codeUint8(uint8_t* n) {
    276    if (mode == XDR_ENCODE) {
    277      uint8_t* ptr = buf->write(sizeof(*n));
    278      if (!ptr) {
    279        return fail(JS::TranscodeResult::Throw);
    280      }
    281      *ptr = *n;
    282    } else {
    283      const uint8_t* ptr = buf->read(sizeof(*n));
    284      if (!ptr) {
    285        return fail(JS::TranscodeResult::Failure_BadDecode);
    286      }
    287      *n = *ptr;
    288    }
    289    return mozilla::Ok();
    290  }
    291 
    292 private:
    293  template <typename T>
    294  XDRResult codeUintImpl(T* n) {
    295    if (mode == XDR_ENCODE) {
    296      uint8_t* ptr = buf->write(sizeof(T));
    297      if (!ptr) {
    298        return fail(JS::TranscodeResult::Throw);
    299      }
    300      memcpy(ptr, n, sizeof(T));
    301    } else {
    302      const uint8_t* ptr = buf->read(sizeof(T));
    303      if (!ptr) {
    304        return fail(JS::TranscodeResult::Failure_BadDecode);
    305      }
    306      memcpy(n, ptr, sizeof(T));
    307    }
    308    return mozilla::Ok();
    309  }
    310 
    311 public:
    312  XDRResult codeUint16(uint16_t* n) { return codeUintImpl(n); }
    313 
    314  XDRResult codeUint32(uint32_t* n) { return codeUintImpl(n); }
    315 
    316  XDRResult codeUint64(uint64_t* n) { return codeUintImpl(n); }
    317 
    318  void codeUint32At(uint32_t* n, size_t cursor) {
    319    if constexpr (mode == XDR_ENCODE) {
    320      uint8_t* ptr = buf->bufferAt(cursor);
    321      memcpy(ptr, n, sizeof(uint32_t));
    322    } else {
    323      MOZ_CRASH("not supported.");
    324    }
    325  }
    326 
    327  const uint8_t* bufferAt(size_t cursor) const {
    328    if constexpr (mode == XDR_ENCODE) {
    329      return buf->bufferAt(cursor);
    330    }
    331 
    332    MOZ_CRASH("not supported.");
    333  }
    334 
    335  XDRResult peekArray(size_t n, const uint8_t** p) {
    336    if constexpr (mode == XDR_DECODE) {
    337      const uint8_t* ptr = buf->peek(n);
    338      if (!ptr) {
    339        return fail(JS::TranscodeResult::Failure_BadDecode);
    340      }
    341 
    342      *p = ptr;
    343 
    344      return mozilla::Ok();
    345    }
    346 
    347    MOZ_CRASH("not supported.");
    348  }
    349 
    350  /*
    351   * Use SFINAE to refuse any specialization which is not an enum.  Uses of
    352   * this function do not have to specialize the type of the enumerated field
    353   * as C++ will extract the parameterized from the argument list.
    354   */
    355  template <typename T>
    356  XDRResult codeEnum32(T* val, std::enable_if_t<std::is_enum_v<T>>* = nullptr) {
    357    // Mix the enumeration value with a random magic number, such that a
    358    // corruption with a low-ranged value (like 0) is less likely to cause a
    359    // miss-interpretation of the XDR content and instead cause a failure.
    360    const uint32_t MAGIC = 0x21AB218C;
    361    uint32_t tmp;
    362    if (mode == XDR_ENCODE) {
    363      tmp = uint32_t(*val) ^ MAGIC;
    364    }
    365    MOZ_TRY(codeUint32(&tmp));
    366    if (mode == XDR_DECODE) {
    367      *val = T(tmp ^ MAGIC);
    368    }
    369    return mozilla::Ok();
    370  }
    371 
    372  XDRResult codeDouble(double* dp) {
    373    union DoublePun {
    374      double d;
    375      uint64_t u;
    376    } pun;
    377    if (mode == XDR_ENCODE) {
    378      pun.d = *dp;
    379    }
    380    MOZ_TRY(codeUint64(&pun.u));
    381    if (mode == XDR_DECODE) {
    382      *dp = pun.d;
    383    }
    384    return mozilla::Ok();
    385  }
    386 
    387  XDRResult codeMarker(uint32_t magic) {
    388    uint32_t actual = magic;
    389    MOZ_TRY(codeUint32(&actual));
    390    if (actual != magic) {
    391      // Fail in debug, but only soft-fail in release
    392      MOZ_ASSERT(false, "Bad XDR marker");
    393      return fail(JS::TranscodeResult::Failure_BadDecode);
    394    }
    395    return mozilla::Ok();
    396  }
    397 
    398  XDRResult codeBytes(void* bytes, size_t len) {
    399    if (len == 0) {
    400      return mozilla::Ok();
    401    }
    402    if (mode == XDR_ENCODE) {
    403      uint8_t* ptr = buf->write(len);
    404      if (!ptr) {
    405        return fail(JS::TranscodeResult::Throw);
    406      }
    407      memcpy(ptr, bytes, len);
    408    } else {
    409      const uint8_t* ptr = buf->read(len);
    410      if (!ptr) {
    411        return fail(JS::TranscodeResult::Failure_BadDecode);
    412      }
    413      memcpy(bytes, ptr, len);
    414    }
    415    return mozilla::Ok();
    416  }
    417 
    418  // While encoding, code the given data to the buffer.
    419  // While decoding, borrow the buffer and return it to `*data`.
    420  //
    421  // The data can have extra bytes after `sizeof(T)`, and the caller should
    422  // provide the entire data length as `length`.
    423  //
    424  // The caller is responsible for aligning the buffer by calling `align32`.
    425  template <typename T>
    426  XDRResult borrowedData(T** data, uint32_t length) {
    427    static_assert(alignof(T) <= 4);
    428    MOZ_ASSERT(isAligned32());
    429 
    430    if (mode == XDR_ENCODE) {
    431      MOZ_TRY(codeBytes(*data, length));
    432    } else {
    433      const uint8_t* cursor = nullptr;
    434      MOZ_TRY(readData(&cursor, length));
    435      *data = reinterpret_cast<T*>(const_cast<uint8_t*>(cursor));
    436    }
    437    return mozilla::Ok();
    438  }
    439 
    440  // Prefer using a variant below that is encoding aware.
    441  XDRResult codeChars(char* chars, size_t nchars);
    442 
    443  XDRResult codeChars(JS::Latin1Char* chars, size_t nchars);
    444  XDRResult codeChars(mozilla::Utf8Unit* units, size_t nchars);
    445  XDRResult codeChars(char16_t* chars, size_t nchars);
    446 
    447  // Transcode null-terminated strings. When decoding, a new buffer is
    448  // allocated and ownership is returned to caller.
    449  //
    450  // NOTE: Throws if string longer than JSString::MAX_LENGTH.
    451  XDRResult codeCharsZ(XDRTranscodeString<char>& buffer);
    452  XDRResult codeCharsZ(XDRTranscodeString<char16_t>& buffer);
    453 };
    454 
    455 } /* namespace js */
    456 
    457 #endif /* vm_Xdr_h */