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 }