tor-browser

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

ClonedErrorHolder.cpp (13096B)


      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 #include "mozilla/dom/ClonedErrorHolder.h"
      8 
      9 #include "js/StructuredClone.h"
     10 #include "jsapi.h"
     11 #include "jsfriendapi.h"
     12 #include "mozilla/dom/BindingUtils.h"
     13 #include "mozilla/dom/ClonedErrorHolderBinding.h"
     14 #include "mozilla/dom/DOMException.h"
     15 #include "mozilla/dom/DOMExceptionBinding.h"
     16 #include "mozilla/dom/Exceptions.h"
     17 #include "mozilla/dom/StructuredCloneTags.h"
     18 #include "mozilla/dom/ToJSValue.h"
     19 #include "nsReadableUtils.h"
     20 #include "xpcpublic.h"
     21 
     22 using namespace mozilla;
     23 using namespace mozilla::dom;
     24 
     25 // static
     26 UniquePtr<ClonedErrorHolder> ClonedErrorHolder::Constructor(
     27    const GlobalObject& aGlobal, JS::Handle<JSObject*> aError,
     28    ErrorResult& aRv) {
     29  return Create(aGlobal.Context(), aError, aRv);
     30 }
     31 
     32 // static
     33 UniquePtr<ClonedErrorHolder> ClonedErrorHolder::Create(
     34    JSContext* aCx, JS::Handle<JSObject*> aError, ErrorResult& aRv) {
     35  UniquePtr<ClonedErrorHolder> ceh(new ClonedErrorHolder());
     36  ceh->Init(aCx, aError, aRv);
     37  if (aRv.Failed()) {
     38    return nullptr;
     39  }
     40  return ceh;
     41 }
     42 
     43 ClonedErrorHolder::ClonedErrorHolder()
     44    : mName(VoidCString()),
     45      mMessage(VoidCString()),
     46      mFilename(VoidCString()),
     47      mSourceLine(VoidCString()) {}
     48 
     49 void ClonedErrorHolder::Init(JSContext* aCx, JS::Handle<JSObject*> aError,
     50                             ErrorResult& aRv) {
     51  JS::Rooted<JSObject*> stack(aCx);
     52 
     53  if (JSErrorReport* err = JS_ErrorFromException(aCx, aError)) {
     54    mType = Type::JSError;
     55    if (err->message()) {
     56      mMessage = err->message().c_str();
     57    }
     58    if (err->filename) {
     59      mFilename = err->filename.c_str();
     60    }
     61    if (err->linebuf()) {
     62      AppendUTF16toUTF8(
     63          nsDependentSubstring(err->linebuf(), err->linebufLength()),
     64          mSourceLine);
     65      mTokenOffset = err->tokenOffset();
     66    }
     67    mLineNumber = err->lineno;
     68    mColumn = err->column;
     69    mErrorNumber = err->errorNumber;
     70    mExnType = JSExnType(err->exnType);
     71 
     72    // Note: We don't save the souce ID here, since this object is cross-process
     73    // clonable, and the source ID won't be valid in other processes.
     74    // We don't store the source notes either, though for no other reason that
     75    // it isn't clear that it's worth the complexity.
     76 
     77    stack = JS::ExceptionStackOrNull(aError);
     78  } else {
     79    RefPtr<DOMException> domExn;
     80    RefPtr<Exception> exn;
     81    if (NS_SUCCEEDED(UNWRAP_OBJECT(DOMException, aError, domExn))) {
     82      mType = Type::DOMException;
     83      mCode = domExn->Code();
     84      exn = std::move(domExn);
     85    } else if (NS_SUCCEEDED(UNWRAP_OBJECT(Exception, aError, exn))) {
     86      mType = Type::Exception;
     87    } else {
     88      aRv.ThrowNotSupportedError(
     89          "We can only clone DOM Exceptions and native JS Error objects");
     90      return;
     91    }
     92 
     93    nsAutoString str;
     94 
     95    exn->GetName(str);
     96    CopyUTF16toUTF8(str, mName);
     97 
     98    exn->GetMessageMoz(str);
     99    CopyUTF16toUTF8(str, mMessage);
    100 
    101    // Note: In DOM exceptions, filename, line number, and column number come
    102    // from the stack frame, and don't need to be stored separately. mFilename,
    103    // mLineNumber, and mColumn are only used for JS exceptions.
    104    //
    105    // We also don't serialized Exception's mThrownJSVal or mData fields, since
    106    // they generally won't be serializable.
    107 
    108    mResult = nsresult(exn->Result());
    109 
    110    if (nsCOMPtr<nsIStackFrame> frame = exn->GetLocation()) {
    111      JS::Rooted<JS::Value> value(aCx);
    112      frame->GetNativeSavedFrame(&value);
    113      if (value.isObject()) {
    114        stack = &value.toObject();
    115      }
    116    }
    117  }
    118 
    119  Maybe<JSAutoRealm> ar;
    120  if (stack) {
    121    ar.emplace(aCx, stack);
    122  }
    123  JS::Rooted<JS::Value> stackValue(aCx, JS::ObjectOrNullValue(stack));
    124  mStack.Write(aCx, stackValue, aRv);
    125 }
    126 
    127 bool ClonedErrorHolder::WrapObject(JSContext* aCx,
    128                                   JS::Handle<JSObject*> aGivenProto,
    129                                   JS::MutableHandle<JSObject*> aReflector) {
    130  return ClonedErrorHolder_Binding::Wrap(aCx, this, aGivenProto, aReflector);
    131 }
    132 
    133 static constexpr uint32_t kVoidStringLength = ~0;
    134 
    135 static bool WriteStringPair(JSStructuredCloneWriter* aWriter,
    136                            const nsACString& aString1,
    137                            const nsACString& aString2) {
    138  auto StringLength = [](const nsACString& aStr) -> uint32_t {
    139    auto length = uint32_t(aStr.Length());
    140    MOZ_DIAGNOSTIC_ASSERT(length != kVoidStringLength,
    141                          "We should not be serializing a 4GiB string");
    142    if (aStr.IsVoid()) {
    143      return kVoidStringLength;
    144    }
    145    return length;
    146  };
    147 
    148  return JS_WriteUint32Pair(aWriter, StringLength(aString1),
    149                            StringLength(aString2)) &&
    150         JS_WriteBytes(aWriter, aString1.BeginReading(), aString1.Length()) &&
    151         JS_WriteBytes(aWriter, aString2.BeginReading(), aString2.Length());
    152 }
    153 
    154 static bool ReadStringPair(JSStructuredCloneReader* aReader,
    155                           nsACString& aString1, nsACString& aString2) {
    156  auto ReadString = [&](nsACString& aStr, uint32_t aLength) {
    157    if (aLength == kVoidStringLength) {
    158      aStr.SetIsVoid(true);
    159      return true;
    160    }
    161    char* data = nullptr;
    162    return aLength == 0 || (aStr.GetMutableData(&data, aLength, fallible) &&
    163                            JS_ReadBytes(aReader, data, aLength));
    164  };
    165 
    166  aString1.Truncate(0);
    167  aString2.Truncate(0);
    168 
    169  uint32_t length1, length2;
    170  return JS_ReadUint32Pair(aReader, &length1, &length2) &&
    171         ReadString(aString1, length1) && ReadString(aString2, length2);
    172 }
    173 
    174 bool ClonedErrorHolder::WriteStructuredClone(JSContext* aCx,
    175                                             JSStructuredCloneWriter* aWriter,
    176                                             StructuredCloneHolder* aHolder) {
    177  auto& data = mStack.BufferData();
    178  return JS_WriteUint32Pair(aWriter, SCTAG_DOM_CLONED_ERROR_OBJECT, 0) &&
    179         WriteStringPair(aWriter, mName, mMessage) &&
    180         WriteStringPair(aWriter, mFilename, mSourceLine) &&
    181         JS_WriteUint32Pair(aWriter, mLineNumber,
    182                            *mColumn.addressOfValueForTranscode()) &&
    183         JS_WriteUint32Pair(aWriter, mTokenOffset, mErrorNumber) &&
    184         JS_WriteUint32Pair(aWriter, uint32_t(mType), uint32_t(mExnType)) &&
    185         JS_WriteUint32Pair(aWriter, mCode, uint32_t(mResult)) &&
    186         JS_WriteUint32Pair(aWriter, data.Size(),
    187                            JS_STRUCTURED_CLONE_VERSION) &&
    188         data.ForEachDataChunk([&](const char* aData, size_t aSize) {
    189           return JS_WriteBytes(aWriter, aData, aSize);
    190         });
    191 }
    192 
    193 bool ClonedErrorHolder::Init(JSContext* aCx, JSStructuredCloneReader* aReader) {
    194  uint32_t type, exnType, result, code;
    195  if (!(ReadStringPair(aReader, mName, mMessage) &&
    196        ReadStringPair(aReader, mFilename, mSourceLine) &&
    197        JS_ReadUint32Pair(aReader, &mLineNumber,
    198                          mColumn.addressOfValueForTranscode()) &&
    199        JS_ReadUint32Pair(aReader, &mTokenOffset, &mErrorNumber) &&
    200        JS_ReadUint32Pair(aReader, &type, &exnType) &&
    201        JS_ReadUint32Pair(aReader, &code, &result) &&
    202        mStack.ReadStructuredCloneInternal(aCx, aReader))) {
    203    return false;
    204  }
    205 
    206  if (type == uint32_t(Type::Uninitialized) || type >= uint32_t(Type::Max_) ||
    207      exnType >= uint32_t(JSEXN_ERROR_LIMIT)) {
    208    return false;
    209  }
    210 
    211  mType = Type(type);
    212  mExnType = JSExnType(exnType);
    213  mResult = nsresult(result);
    214  mCode = code;
    215 
    216  return true;
    217 }
    218 
    219 /* static */
    220 JSObject* ClonedErrorHolder::ReadStructuredClone(
    221    JSContext* aCx, JSStructuredCloneReader* aReader,
    222    StructuredCloneHolder* aHolder) {
    223  // Keep the result object rooted across the call to ClonedErrorHolder::Release
    224  // to avoid a potential rooting hazard.
    225  JS::Rooted<JS::Value> errorVal(aCx);
    226  {
    227    UniquePtr<ClonedErrorHolder> ceh(new ClonedErrorHolder());
    228    if (!ceh->Init(aCx, aReader) || !ceh->ToErrorValue(aCx, &errorVal)) {
    229      return nullptr;
    230    }
    231  }
    232  return &errorVal.toObject();
    233 }
    234 
    235 static JS::UniqueTwoByteChars ToNullTerminatedJSStringBuffer(
    236    JSContext* aCx, const nsString& aStr) {
    237  // Since nsString is null terminated, we can simply copy + 1 characters.
    238  size_t nbytes = (aStr.Length() + 1) * sizeof(char16_t);
    239  JS::UniqueTwoByteChars buffer(static_cast<char16_t*>(JS_malloc(aCx, nbytes)));
    240  if (buffer) {
    241    memcpy(buffer.get(), aStr.get(), nbytes);
    242  }
    243  return buffer;
    244 }
    245 
    246 static bool ToJSString(JSContext* aCx, const nsACString& aStr,
    247                       JS::MutableHandle<JSString*> aJSString) {
    248  if (aStr.IsVoid()) {
    249    aJSString.set(nullptr);
    250    return true;
    251  }
    252  JS::Rooted<JS::Value> res(aCx);
    253  if (xpc::NonVoidStringToJsval(aCx, NS_ConvertUTF8toUTF16(aStr), &res)) {
    254    aJSString.set(res.toString());
    255    return true;
    256  }
    257  return false;
    258 }
    259 
    260 bool ClonedErrorHolder::ToErrorValue(JSContext* aCx,
    261                                     JS::MutableHandle<JS::Value> aResult) {
    262  JS::Rooted<JS::Value> stackVal(aCx);
    263  JS::Rooted<JSObject*> stack(aCx);
    264 
    265  IgnoredErrorResult rv;
    266  mStack.Read(xpc::CurrentNativeGlobal(aCx), aCx, &stackVal, rv);
    267  // Note: We continue even if reading the stack fails, since we can still
    268  // produce a useful error object even without a stack. That said, if decoding
    269  // the stack fails, there's a pretty good chance that the rest of the message
    270  // is corrupt, and there's no telling how useful the final result will
    271  // actually be.
    272  if (!rv.Failed() && stackVal.isObject()) {
    273    stack = &stackVal.toObject();
    274    // Make sure that this is really a saved frame. This mainly ensures that
    275    // malicious code on the child side can't trigger a memory exploit by
    276    // sending an incompatible data type, but also protects against potential
    277    // issues like a cross-compartment wrapper being unexpectedly cut.
    278    if (!js::IsSavedFrame(stack)) {
    279      stack = nullptr;
    280    }
    281  }
    282 
    283  if (mType == Type::JSError) {
    284    JS::Rooted<JSString*> filename(aCx);
    285    JS::Rooted<JSString*> message(aCx);
    286 
    287    // For some unknown reason, we can end up with a void string in mFilename,
    288    // which will cause filename to be null, which causes JS::CreateError() to
    289    // crash. Make this code against robust against this by treating void
    290    // strings as the empty string.
    291    if (mFilename.IsVoid()) {
    292      mFilename.Assign(""_ns);
    293    }
    294 
    295    // When fuzzing, we can also end up with the message to be null,
    296    // so we should handle that case as well.
    297    if (mMessage.IsVoid()) {
    298      mMessage.Assign(""_ns);
    299    }
    300 
    301    if (!ToJSString(aCx, mFilename, &filename) ||
    302        !ToJSString(aCx, mMessage, &message)) {
    303      return false;
    304    }
    305    if (!JS::CreateError(aCx, mExnType, stack, filename, mLineNumber, mColumn,
    306                         nullptr, message, JS::NothingHandleValue, aResult)) {
    307      return false;
    308    }
    309 
    310    if (!mSourceLine.IsVoid()) {
    311      JS::Rooted<JSObject*> errObj(aCx, &aResult.toObject());
    312      if (JSErrorReport* err = JS_ErrorFromException(aCx, errObj)) {
    313        NS_ConvertUTF8toUTF16 sourceLine(mSourceLine);
    314        // Because this string ends up being consumed as an nsDependentString
    315        // in nsXPCComponents_Utils::ReportError, this needs to be a null
    316        // terminated string.
    317        //
    318        // See Bug 1699569.
    319        if (mTokenOffset >= sourceLine.Length()) {
    320          // Corrupt data, leave linebuf unset.
    321        } else if (JS::UniqueTwoByteChars buffer =
    322                       ToNullTerminatedJSStringBuffer(aCx, sourceLine)) {
    323          err->initOwnedLinebuf(buffer.release(), sourceLine.Length(),
    324                                mTokenOffset);
    325        } else {
    326          // Just ignore OOM and continue if the string copy failed.
    327          JS_ClearPendingException(aCx);
    328        }
    329      }
    330    }
    331 
    332    return true;
    333  }
    334 
    335  nsCOMPtr<nsIStackFrame> frame(exceptions::CreateStack(aCx, stack));
    336 
    337  RefPtr<Exception> exn;
    338  if (mType == Type::Exception) {
    339    exn = new Exception(mMessage, mResult, mName, frame, nullptr);
    340  } else {
    341    MOZ_ASSERT(mType == Type::DOMException);
    342    exn = new DOMException(mResult, mMessage, mName, mCode, frame);
    343  }
    344 
    345  return ToJSValue(aCx, exn, aResult);
    346 }
    347 
    348 bool ClonedErrorHolder::Holder::ReadStructuredCloneInternal(
    349    JSContext* aCx, JSStructuredCloneReader* aReader) {
    350  uint32_t length;
    351  uint32_t version;
    352  if (!JS_ReadUint32Pair(aReader, &length, &version)) {
    353    return false;
    354  }
    355  if (length % 8 != 0) {
    356    return false;
    357  }
    358 
    359  JSStructuredCloneData data(mStructuredCloneScope);
    360  while (length) {
    361    size_t size;
    362    char* buffer = data.AllocateBytes(length, &size);
    363    if (!buffer || !JS_ReadBytes(aReader, buffer, size)) {
    364      return false;
    365    }
    366    length -= size;
    367  }
    368 
    369  mBuffer = MakeUnique<JSAutoStructuredCloneBuffer>(
    370      mStructuredCloneScope, &StructuredCloneHolder::sCallbacks, this);
    371  mBuffer->adopt(std::move(data), version, &StructuredCloneHolder::sCallbacks);
    372  return true;
    373 }