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/. */
7 #include "mozilla/dom/ClonedErrorHolder.h"
9 #include "mozilla/dom/BindingUtils.h"
10 #include "mozilla/dom/ClonedErrorHolderBinding.h"
11 #include "mozilla/dom/DOMException.h"
12 #include "mozilla/dom/DOMExceptionBinding.h"
13 #include "mozilla/dom/Exceptions.h"
14 #include "mozilla/dom/StructuredCloneTags.h"
15 #include "mozilla/dom/ToJSValue.h"
17 #include "jsfriendapi.h"
18 #include "js/StructuredClone.h"
19 #include "nsReadableUtils.h"
20 #include "xpcpublic.h"
22 using namespace mozilla
;
23 using namespace mozilla::dom
;
26 already_AddRefed
<ClonedErrorHolder
> ClonedErrorHolder::Constructor(
27 const GlobalObject
& aGlobal
, JS::Handle
<JSObject
*> aError
,
29 return Create(aGlobal
.Context(), aError
, aRv
);
33 already_AddRefed
<ClonedErrorHolder
> ClonedErrorHolder::Create(
34 JSContext
* aCx
, JS::Handle
<JSObject
*> aError
, ErrorResult
& aRv
) {
35 RefPtr
<ClonedErrorHolder
> ceh
= new ClonedErrorHolder();
36 ceh
->Init(aCx
, aError
, aRv
);
43 ClonedErrorHolder::ClonedErrorHolder()
44 : mName(VoidCString()),
45 mMessage(VoidCString()),
46 mFilename(VoidCString()),
47 mSourceLine(VoidCString()) {}
49 void ClonedErrorHolder::Init(JSContext
* aCx
, JS::Handle
<JSObject
*> aError
,
51 JS::Rooted
<JSObject
*> stack(aCx
);
53 if (JSErrorReport
* err
= JS_ErrorFromException(aCx
, aError
)) {
54 mType
= Type::JSError
;
56 mMessage
= err
->message().c_str();
59 mFilename
= err
->filename
;
63 nsDependentSubstring(err
->linebuf(), err
->linebufLength()),
65 mTokenOffset
= err
->tokenOffset();
67 mLineNumber
= err
->lineno
;
68 mColumn
= err
->column
;
69 mErrorNumber
= err
->errorNumber
;
70 mExnType
= JSExnType(err
->exnType
);
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.
77 stack
= JS::ExceptionStackOrNull(aError
);
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
;
88 aRv
.ThrowNotSupportedError(
89 "We can only clone DOM Exceptions and native JS Error objects");
96 CopyUTF16toUTF8(str
, mName
);
98 exn
->GetMessageMoz(str
);
99 CopyUTF16toUTF8(str
, mMessage
);
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.
105 // We also don't serialized Exception's mThrownJSVal or mData fields, since
106 // they generally won't be serializable.
108 mResult
= nsresult(exn
->Result());
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();
119 Maybe
<JSAutoRealm
> ar
;
121 ar
.emplace(aCx
, stack
);
123 JS::Rooted
<JS::Value
> stackValue(aCx
, JS::ObjectOrNullValue(stack
));
124 mStack
.Write(aCx
, stackValue
, aRv
);
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
);
133 static constexpr uint32_t kVoidStringLength
= ~0;
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");
143 return kVoidStringLength
;
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());
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);
161 char* data
= nullptr;
162 return aLength
== 0 || (aStr
.GetMutableData(&data
, aLength
, fallible
) &&
163 JS_ReadBytes(aReader
, data
, aLength
));
166 aString1
.Truncate(0);
167 aString2
.Truncate(0);
169 uint32_t length1
, length2
;
170 return JS_ReadUint32Pair(aReader
, &length1
, &length2
) &&
171 ReadString(aString1
, length1
) && ReadString(aString2
, length2
);
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
, mColumn
) &&
182 JS_WriteUint32Pair(aWriter
, mTokenOffset
, mErrorNumber
) &&
183 JS_WriteUint32Pair(aWriter
, uint32_t(mType
), uint32_t(mExnType
)) &&
184 JS_WriteUint32Pair(aWriter
, mCode
, uint32_t(mResult
)) &&
185 JS_WriteUint32Pair(aWriter
, data
.Size(),
186 JS_STRUCTURED_CLONE_VERSION
) &&
187 data
.ForEachDataChunk([&](const char* aData
, size_t aSize
) {
188 return JS_WriteBytes(aWriter
, aData
, aSize
);
192 bool ClonedErrorHolder::Init(JSContext
* aCx
, JSStructuredCloneReader
* aReader
) {
193 uint32_t type
, exnType
, result
, code
;
194 if (!(ReadStringPair(aReader
, mName
, mMessage
) &&
195 ReadStringPair(aReader
, mFilename
, mSourceLine
) &&
196 JS_ReadUint32Pair(aReader
, &mLineNumber
, &mColumn
) &&
197 JS_ReadUint32Pair(aReader
, &mTokenOffset
, &mErrorNumber
) &&
198 JS_ReadUint32Pair(aReader
, &type
, &exnType
) &&
199 JS_ReadUint32Pair(aReader
, &code
, &result
) &&
200 mStack
.ReadStructuredCloneInternal(aCx
, aReader
))) {
204 if (type
== uint32_t(Type::Uninitialized
) || type
>= uint32_t(Type::Max_
) ||
205 exnType
>= uint32_t(JSEXN_ERROR_LIMIT
)) {
210 mExnType
= JSExnType(exnType
);
211 mResult
= nsresult(result
);
218 JSObject
* ClonedErrorHolder::ReadStructuredClone(
219 JSContext
* aCx
, JSStructuredCloneReader
* aReader
,
220 StructuredCloneHolder
* aHolder
) {
221 // Keep the result object rooted across the call to ClonedErrorHolder::Release
222 // to avoid a potential rooting hazard.
223 JS::Rooted
<JS::Value
> errorVal(aCx
);
225 RefPtr
<ClonedErrorHolder
> ceh
= new ClonedErrorHolder();
226 if (!ceh
->Init(aCx
, aReader
) || !ceh
->ToErrorValue(aCx
, &errorVal
)) {
230 return &errorVal
.toObject();
233 static JS::UniqueTwoByteChars
ToNullTerminatedJSStringBuffer(
234 JSContext
* aCx
, const nsString
& aStr
) {
235 // Since nsString is null terminated, we can simply copy + 1 characters.
236 size_t nbytes
= (aStr
.Length() + 1) * sizeof(char16_t
);
237 JS::UniqueTwoByteChars
buffer(static_cast<char16_t
*>(JS_malloc(aCx
, nbytes
)));
239 memcpy(buffer
.get(), aStr
.get(), nbytes
);
244 static bool ToJSString(JSContext
* aCx
, const nsACString
& aStr
,
245 JS::MutableHandle
<JSString
*> aJSString
) {
247 aJSString
.set(nullptr);
250 JS::Rooted
<JS::Value
> res(aCx
);
251 if (xpc::NonVoidStringToJsval(aCx
, NS_ConvertUTF8toUTF16(aStr
), &res
)) {
252 aJSString
.set(res
.toString());
258 bool ClonedErrorHolder::ToErrorValue(JSContext
* aCx
,
259 JS::MutableHandle
<JS::Value
> aResult
) {
260 JS::Rooted
<JS::Value
> stackVal(aCx
);
261 JS::Rooted
<JSObject
*> stack(aCx
);
263 IgnoredErrorResult rv
;
264 mStack
.Read(xpc::CurrentNativeGlobal(aCx
), aCx
, &stackVal
, rv
);
265 // Note: We continue even if reading the stack fails, since we can still
266 // produce a useful error object even without a stack. That said, if decoding
267 // the stack fails, there's a pretty good chance that the rest of the message
268 // is corrupt, and there's no telling how useful the final result will
270 if (!rv
.Failed() && stackVal
.isObject()) {
271 stack
= &stackVal
.toObject();
272 // Make sure that this is really a saved frame. This mainly ensures that
273 // malicious code on the child side can't trigger a memory exploit by
274 // sending an incompatible data type, but also protects against potential
275 // issues like a cross-compartment wrapper being unexpectedly cut.
276 if (!js::IsSavedFrame(stack
)) {
281 if (mType
== Type::JSError
) {
282 JS::Rooted
<JSString
*> filename(aCx
);
283 JS::Rooted
<JSString
*> message(aCx
);
285 // For some unknown reason, we can end up with a void string in mFilename,
286 // which will cause filename to be null, which causes JS::CreateError() to
287 // crash. Make this code against robust against this by treating void
288 // strings as the empty string.
289 if (mFilename
.IsVoid()) {
290 mFilename
.Assign(""_ns
);
293 // When fuzzing, we can also end up with the message to be null,
294 // so we should handle that case as well.
295 if (mMessage
.IsVoid()) {
296 mMessage
.Assign(""_ns
);
299 if (!ToJSString(aCx
, mFilename
, &filename
) ||
300 !ToJSString(aCx
, mMessage
, &message
)) {
303 if (!JS::CreateError(aCx
, mExnType
, stack
, filename
, mLineNumber
, mColumn
,
304 nullptr, message
, JS::NothingHandleValue
, aResult
)) {
308 if (!mSourceLine
.IsVoid()) {
309 JS::Rooted
<JSObject
*> errObj(aCx
, &aResult
.toObject());
310 if (JSErrorReport
* err
= JS_ErrorFromException(aCx
, errObj
)) {
311 NS_ConvertUTF8toUTF16
sourceLine(mSourceLine
);
312 // Because this string ends up being consumed as an nsDependentString
313 // in nsXPCComponents_Utils::ReportError, this needs to be a null
314 // terminated string.
317 if (mTokenOffset
>= sourceLine
.Length()) {
318 // Corrupt data, leave linebuf unset.
319 } else if (JS::UniqueTwoByteChars buffer
=
320 ToNullTerminatedJSStringBuffer(aCx
, sourceLine
)) {
321 err
->initOwnedLinebuf(buffer
.release(), sourceLine
.Length(),
324 // Just ignore OOM and continue if the string copy failed.
325 JS_ClearPendingException(aCx
);
333 nsCOMPtr
<nsIStackFrame
> frame(exceptions::CreateStack(aCx
, stack
));
335 RefPtr
<Exception
> exn
;
336 if (mType
== Type::Exception
) {
337 exn
= new Exception(mMessage
, mResult
, mName
, frame
, nullptr);
339 MOZ_ASSERT(mType
== Type::DOMException
);
340 exn
= new DOMException(mResult
, mMessage
, mName
, mCode
, frame
);
343 return ToJSValue(aCx
, exn
, aResult
);
346 bool ClonedErrorHolder::Holder::ReadStructuredCloneInternal(
347 JSContext
* aCx
, JSStructuredCloneReader
* aReader
) {
350 if (!JS_ReadUint32Pair(aReader
, &length
, &version
)) {
353 if (length
% 8 != 0) {
357 JSStructuredCloneData
data(mStructuredCloneScope
);
360 char* buffer
= data
.AllocateBytes(length
, &size
);
361 if (!buffer
|| !JS_ReadBytes(aReader
, buffer
, size
)) {
367 mBuffer
= MakeUnique
<JSAutoStructuredCloneBuffer
>(
368 mStructuredCloneScope
, &StructuredCloneHolder::sCallbacks
, this);
369 mBuffer
->adopt(std::move(data
), version
, &StructuredCloneHolder::sCallbacks
);