Line data Source code
1 : /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2 : /* vim: set ts=8 sts=4 et sw=4 tw=99: */
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 "XrayWrapper.h"
8 : #include "AccessCheck.h"
9 : #include "WrapperFactory.h"
10 :
11 : #include "nsDependentString.h"
12 : #include "nsIScriptError.h"
13 : #include "mozilla/dom/Element.h"
14 : #include "mozilla/dom/ScriptSettings.h"
15 :
16 : #include "XPCWrapper.h"
17 : #include "xpcprivate.h"
18 :
19 : #include "jsapi.h"
20 : #include "nsJSUtils.h"
21 : #include "nsPrintfCString.h"
22 :
23 : #include "mozilla/dom/BindingUtils.h"
24 : #include "mozilla/dom/WindowBinding.h"
25 : #include "mozilla/dom/XrayExpandoClass.h"
26 : #include "nsGlobalWindow.h"
27 :
28 : using namespace mozilla::dom;
29 : using namespace JS;
30 : using namespace mozilla;
31 :
32 : using js::Wrapper;
33 : using js::BaseProxyHandler;
34 : using js::IsCrossCompartmentWrapper;
35 : using js::UncheckedUnwrap;
36 : using js::CheckedUnwrap;
37 :
38 : namespace xpc {
39 :
40 : using namespace XrayUtils;
41 :
42 : #define Between(x, a, b) (a <= x && x <= b)
43 :
44 : static_assert(JSProto_URIError - JSProto_Error == 7, "New prototype added in error object range");
45 : #define AssertErrorObjectKeyInBounds(key) \
46 : static_assert(Between(key, JSProto_Error, JSProto_URIError), "We depend on js/ProtoKey.h ordering here");
47 : MOZ_FOR_EACH(AssertErrorObjectKeyInBounds, (),
48 : (JSProto_Error, JSProto_InternalError, JSProto_EvalError, JSProto_RangeError,
49 : JSProto_ReferenceError, JSProto_SyntaxError, JSProto_TypeError, JSProto_URIError));
50 :
51 : static_assert(JSProto_Uint8ClampedArray - JSProto_Int8Array == 8, "New prototype added in typed array range");
52 : #define AssertTypedArrayKeyInBounds(key) \
53 : static_assert(Between(key, JSProto_Int8Array, JSProto_Uint8ClampedArray), "We depend on js/ProtoKey.h ordering here");
54 : MOZ_FOR_EACH(AssertTypedArrayKeyInBounds, (),
55 : (JSProto_Int8Array, JSProto_Uint8Array, JSProto_Int16Array, JSProto_Uint16Array,
56 : JSProto_Int32Array, JSProto_Uint32Array, JSProto_Float32Array, JSProto_Float64Array, JSProto_Uint8ClampedArray));
57 :
58 : #undef Between
59 :
60 : inline bool
61 : IsErrorObjectKey(JSProtoKey key)
62 : {
63 0 : return key >= JSProto_Error && key <= JSProto_URIError;
64 : }
65 :
66 : inline bool
67 : IsTypedArrayKey(JSProtoKey key)
68 : {
69 0 : return key >= JSProto_Int8Array && key <= JSProto_Uint8ClampedArray;
70 : }
71 :
72 : // Whitelist for the standard ES classes we can Xray to.
73 : static bool
74 0 : IsJSXraySupported(JSProtoKey key)
75 : {
76 0 : if (IsTypedArrayKey(key))
77 : return true;
78 0 : if (IsErrorObjectKey(key))
79 : return true;
80 : switch (key) {
81 : case JSProto_Date:
82 : case JSProto_DataView:
83 : case JSProto_Object:
84 : case JSProto_Array:
85 : case JSProto_Function:
86 : case JSProto_TypedArray:
87 : case JSProto_SavedFrame:
88 : case JSProto_RegExp:
89 : case JSProto_Promise:
90 : case JSProto_ArrayBuffer:
91 : case JSProto_SharedArrayBuffer:
92 : case JSProto_Map:
93 : case JSProto_Set:
94 : return true;
95 : default:
96 0 : return false;
97 : }
98 : }
99 :
100 : XrayType
101 0 : GetXrayType(JSObject* obj)
102 : {
103 0 : obj = js::UncheckedUnwrap(obj, /* stopAtWindowProxy = */ false);
104 0 : if (mozilla::dom::UseDOMXray(obj))
105 : return XrayForDOMObject;
106 :
107 0 : MOZ_ASSERT(!js::IsWindowProxy(obj));
108 :
109 1312 : JSProtoKey standardProto = IdentifyStandardInstanceOrPrototype(obj);
110 1312 : if (IsJSXraySupported(standardProto))
111 : return XrayForJSObject;
112 :
113 : // Modulo a few exceptions, everything else counts as an XrayWrapper to an
114 : // opaque object, which means that more-privileged code sees nothing from
115 : // the underlying object. This is very important for security. In some cases
116 : // though, we need to make an exception for compatibility.
117 842 : if (IsSandbox(obj))
118 : return NotXray;
119 :
120 842 : return XrayForOpaqueObject;
121 : }
122 :
123 : JSObject*
124 2313 : XrayAwareCalleeGlobal(JSObject* fun)
125 : {
126 0 : MOZ_ASSERT(js::IsFunctionObject(fun));
127 :
128 0 : if (!js::FunctionHasNativeReserved(fun)) {
129 : // Just a normal function, no Xrays involved.
130 0 : return js::GetGlobalForObjectCrossCompartment(fun);
131 : }
132 :
133 : // The functions we expect here have the Xray wrapper they're associated with
134 : // in their XRAY_DOM_FUNCTION_PARENT_WRAPPER_SLOT and, in a debug build,
135 : // themselves in their XRAY_DOM_FUNCTION_NATIVE_SLOT_FOR_SELF. Assert that
136 : // last bit.
137 0 : MOZ_ASSERT(&js::GetFunctionNativeReserved(fun, XRAY_DOM_FUNCTION_NATIVE_SLOT_FOR_SELF).toObject() ==
138 : fun);
139 :
140 : Value v =
141 0 : js::GetFunctionNativeReserved(fun, XRAY_DOM_FUNCTION_PARENT_WRAPPER_SLOT);
142 0 : MOZ_ASSERT(IsXrayWrapper(&v.toObject()));
143 :
144 0 : JSObject* xrayTarget = js::UncheckedUnwrap(&v.toObject());
145 0 : return js::GetGlobalForObjectCrossCompartment(xrayTarget);
146 : }
147 :
148 : JSObject*
149 1111 : XrayTraits::getExpandoChain(HandleObject obj)
150 : {
151 1 : return ObjectScope(obj)->GetExpandoChain(obj);
152 : }
153 :
154 : JSObject*
155 0 : XrayTraits::detachExpandoChain(HandleObject obj)
156 : {
157 0 : return ObjectScope(obj)->DetachExpandoChain(obj);
158 : }
159 :
160 : bool
161 0 : XrayTraits::setExpandoChain(JSContext* cx, HandleObject obj, HandleObject chain)
162 : {
163 0 : return ObjectScope(obj)->SetExpandoChain(cx, obj, chain);
164 : }
165 :
166 : const JSClass XrayTraits::HolderClass = {
167 : "XrayHolder", JSCLASS_HAS_RESERVED_SLOTS(HOLDER_SHARED_SLOT_COUNT)
168 : };
169 :
170 : const JSClass JSXrayTraits::HolderClass = {
171 : "JSXrayHolder", JSCLASS_HAS_RESERVED_SLOTS(SLOT_COUNT)
172 : };
173 :
174 : bool
175 0 : OpaqueXrayTraits::resolveOwnProperty(JSContext* cx, HandleObject wrapper, HandleObject target,
176 : HandleObject holder, HandleId id,
177 : MutableHandle<PropertyDescriptor> desc)
178 : {
179 0 : bool ok = XrayTraits::resolveOwnProperty(cx, wrapper, target, holder, id, desc);
180 0 : if (!ok || desc.object())
181 : return ok;
182 :
183 0 : return ReportWrapperDenial(cx, id, WrapperDenialForXray, "object is not safely Xrayable");
184 : }
185 :
186 : bool
187 0 : ReportWrapperDenial(JSContext* cx, HandleId id, WrapperDenialType type, const char* reason)
188 : {
189 0 : CompartmentPrivate* priv = CompartmentPrivate::Get(CurrentGlobalOrNull(cx));
190 0 : bool alreadyWarnedOnce = priv->wrapperDenialWarnings[type];
191 0 : priv->wrapperDenialWarnings[type] = true;
192 :
193 : // The browser console warning is only emitted for the first violation,
194 : // whereas the (debug-only) NS_WARNING is emitted for each violation.
195 : #ifndef DEBUG
196 : if (alreadyWarnedOnce)
197 : return true;
198 : #endif
199 :
200 0 : nsAutoJSString propertyName;
201 0 : RootedValue idval(cx);
202 0 : if (!JS_IdToValue(cx, id, &idval))
203 : return false;
204 0 : JSString* str = JS_ValueToSource(cx, idval);
205 0 : if (!str)
206 : return false;
207 0 : if (!propertyName.init(cx, str))
208 : return false;
209 0 : AutoFilename filename;
210 0 : unsigned line = 0, column = 0;
211 0 : DescribeScriptedCaller(cx, &filename, &line, &column);
212 :
213 : // Warn to the terminal for the logs.
214 0 : NS_WARNING(nsPrintfCString("Silently denied access to property %s: %s (@%s:%u:%u)",
215 : NS_LossyConvertUTF16toASCII(propertyName).get(), reason,
216 0 : filename.get(), line, column).get());
217 :
218 : // If this isn't the first warning on this topic for this global, we've
219 : // already bailed out in opt builds. Now that the NS_WARNING is done, bail
220 : // out in debug builds as well.
221 0 : if (alreadyWarnedOnce)
222 : return true;
223 :
224 : //
225 : // Log a message to the console service.
226 : //
227 :
228 : // Grab the pieces.
229 0 : nsCOMPtr<nsIConsoleService> consoleService = do_GetService(NS_CONSOLESERVICE_CONTRACTID);
230 0 : NS_ENSURE_TRUE(consoleService, true);
231 0 : nsCOMPtr<nsIScriptError> errorObject = do_CreateInstance(NS_SCRIPTERROR_CONTRACTID);
232 0 : NS_ENSURE_TRUE(errorObject, true);
233 :
234 : // Compute the current window id if any.
235 0 : uint64_t windowId = 0;
236 0 : nsGlobalWindowInner* win = WindowGlobalOrNull(CurrentGlobalOrNull(cx));
237 0 : if (win)
238 0 : windowId = win->WindowID();
239 :
240 :
241 0 : Maybe<nsPrintfCString> errorMessage;
242 0 : if (type == WrapperDenialForXray) {
243 : errorMessage.emplace("XrayWrapper denied access to property %s (reason: %s). "
244 : "See https://developer.mozilla.org/en-US/docs/Xray_vision "
245 : "for more information. Note that only the first denied "
246 : "property access from a given global object will be reported.",
247 0 : NS_LossyConvertUTF16toASCII(propertyName).get(),
248 0 : reason);
249 : } else {
250 0 : MOZ_ASSERT(type == WrapperDenialForCOW);
251 : errorMessage.emplace("Security wrapper denied access to property %s on privileged "
252 : "Javascript object. Support for exposing privileged objects "
253 : "to untrusted content via __exposedProps__ has been "
254 : "removed - use WebIDL bindings or Components.utils.cloneInto "
255 : "instead. Note that only the first denied property access from a "
256 : "given global object will be reported.",
257 0 : NS_LossyConvertUTF16toASCII(propertyName).get());
258 : }
259 0 : nsString filenameStr(NS_ConvertASCIItoUTF16(filename.get()));
260 0 : nsresult rv = errorObject->InitWithWindowID(NS_ConvertASCIItoUTF16(errorMessage.ref()),
261 : filenameStr,
262 0 : EmptyString(),
263 : line, column,
264 : nsIScriptError::warningFlag,
265 : "XPConnect",
266 0 : windowId);
267 0 : NS_ENSURE_SUCCESS(rv, true);
268 0 : rv = consoleService->LogMessage(errorObject);
269 0 : NS_ENSURE_SUCCESS(rv, true);
270 :
271 : return true;
272 : }
273 :
274 46 : bool JSXrayTraits::getOwnPropertyFromWrapperIfSafe(JSContext* cx,
275 : HandleObject wrapper,
276 : HandleId id,
277 : MutableHandle<PropertyDescriptor> outDesc)
278 : {
279 92 : MOZ_ASSERT(js::IsObjectInContextCompartment(wrapper, cx));
280 92 : RootedObject target(cx, getTargetObject(wrapper));
281 : {
282 0 : JSAutoRealm ar(cx, target);
283 0 : JS_MarkCrossZoneId(cx, id);
284 0 : if (!getOwnPropertyFromTargetIfSafe(cx, target, wrapper, id, outDesc))
285 0 : return false;
286 : }
287 0 : return JS_WrapPropertyDescriptor(cx, outDesc);
288 : }
289 :
290 46 : bool JSXrayTraits::getOwnPropertyFromTargetIfSafe(JSContext* cx,
291 : HandleObject target,
292 : HandleObject wrapper,
293 : HandleId id,
294 : MutableHandle<PropertyDescriptor> outDesc)
295 : {
296 : // Note - This function operates in the target compartment, because it
297 : // avoids a bunch of back-and-forth wrapping in enumerateNames.
298 92 : MOZ_ASSERT(getTargetObject(wrapper) == target);
299 92 : MOZ_ASSERT(js::IsObjectInContextCompartment(target, cx));
300 46 : MOZ_ASSERT(WrapperFactory::IsXrayWrapper(wrapper));
301 92 : MOZ_ASSERT(outDesc.object() == nullptr);
302 :
303 0 : Rooted<PropertyDescriptor> desc(cx);
304 46 : if (!JS_GetOwnPropertyDescriptorById(cx, target, id, &desc))
305 : return false;
306 :
307 : // If the property doesn't exist at all, we're done.
308 0 : if (!desc.object())
309 : return true;
310 :
311 : // Disallow accessor properties.
312 0 : if (desc.hasGetterOrSetter()) {
313 0 : JSAutoRealm ar(cx, wrapper);
314 0 : JS_MarkCrossZoneId(cx, id);
315 0 : return ReportWrapperDenial(cx, id, WrapperDenialForXray, "property has accessor");
316 : }
317 :
318 : // Apply extra scrutiny to objects.
319 38 : if (desc.value().isObject()) {
320 0 : RootedObject propObj(cx, js::UncheckedUnwrap(&desc.value().toObject()));
321 12 : JSAutoRealm ar(cx, propObj);
322 :
323 : // Disallow non-subsumed objects.
324 8 : if (!AccessCheck::subsumes(target, propObj)) {
325 0 : JSAutoRealm ar(cx, wrapper);
326 0 : JS_MarkCrossZoneId(cx, id);
327 0 : return ReportWrapperDenial(cx, id, WrapperDenialForXray, "value not same-origin with target");
328 : }
329 :
330 : // Disallow non-Xrayable objects.
331 1 : XrayType xrayType = GetXrayType(propObj);
332 4 : if (xrayType == NotXray || xrayType == XrayForOpaqueObject) {
333 0 : JSAutoRealm ar(cx, wrapper);
334 0 : JS_MarkCrossZoneId(cx, id);
335 0 : return ReportWrapperDenial(cx, id, WrapperDenialForXray, "value not Xrayable");
336 : }
337 :
338 : // Disallow callables.
339 4 : if (JS::IsCallable(propObj)) {
340 0 : JSAutoRealm ar(cx, wrapper);
341 0 : JS_MarkCrossZoneId(cx, id);
342 0 : return ReportWrapperDenial(cx, id, WrapperDenialForXray, "value is callable");
343 : }
344 : }
345 :
346 : // Disallow any property that shadows something on its (Xrayed)
347 : // prototype chain.
348 114 : JSAutoRealm ar2(cx, wrapper);
349 0 : JS_MarkCrossZoneId(cx, id);
350 0 : RootedObject proto(cx);
351 38 : bool foundOnProto = false;
352 152 : if (!JS_GetPrototype(cx, wrapper, &proto) ||
353 76 : (proto && !JS_HasPropertyById(cx, proto, id, &foundOnProto)))
354 : {
355 : return false;
356 : }
357 38 : if (foundOnProto)
358 0 : return ReportWrapperDenial(cx, id, WrapperDenialForXray, "value shadows a property on the standard prototype");
359 :
360 : // We made it! Assign over the descriptor, and don't forget to wrap.
361 0 : outDesc.assign(desc.get());
362 38 : return true;
363 : }
364 :
365 : // Returns true on success (in the JSAPI sense), false on failure. If true is
366 : // returned, desc.object() will indicate whether we actually resolved
367 : // the property.
368 : //
369 : // id is the property id we're looking for.
370 : // holder is the object to define the property on.
371 : // fs is the relevant JSFunctionSpec*.
372 : // ps is the relevant JSPropertySpec*.
373 : // desc is the descriptor we're resolving into.
374 : static bool
375 92 : TryResolvePropertyFromSpecs(JSContext* cx, HandleId id, HandleObject holder,
376 : const JSFunctionSpec* fs,
377 : const JSPropertySpec* ps,
378 : MutableHandle<PropertyDescriptor> desc)
379 : {
380 : // Scan through the functions.
381 0 : const JSFunctionSpec* fsMatch = nullptr;
382 3728 : for ( ; fs && fs->name; ++fs) {
383 1824 : if (PropertySpecNameEqualsId(fs->name, id)) {
384 : fsMatch = fs;
385 : break;
386 : }
387 : }
388 0 : if (fsMatch) {
389 : // Generate an Xrayed version of the method.
390 12 : RootedFunction fun(cx, JS::NewFunctionFromSpec(cx, fsMatch, id));
391 6 : if (!fun)
392 : return false;
393 :
394 : // The generic Xray machinery only defines non-own properties of the target on
395 : // the holder. This is broken, and will be fixed at some point, but for now we
396 : // need to cache the value explicitly. See the corresponding call to
397 : // JS_GetOwnPropertyDescriptorById at the top of
398 : // JSXrayTraits::resolveOwnProperty.
399 0 : RootedObject funObj(cx, JS_GetFunctionObject(fun));
400 18 : return JS_DefinePropertyById(cx, holder, id, funObj, 0) &&
401 6 : JS_GetOwnPropertyDescriptorById(cx, holder, id, desc);
402 : }
403 :
404 : // Scan through the properties.
405 : const JSPropertySpec* psMatch = nullptr;
406 176 : for ( ; ps && ps->name; ++ps) {
407 0 : if (PropertySpecNameEqualsId(ps->name, id)) {
408 : psMatch = ps;
409 : break;
410 : }
411 : }
412 86 : if (psMatch) {
413 : // The generic Xray machinery only defines non-own properties on the holder.
414 : // This is broken, and will be fixed at some point, but for now we need to
415 : // cache the value explicitly. See the corresponding call to
416 : // JS_GetPropertyById at the top of JSXrayTraits::resolveOwnProperty.
417 : //
418 : // Note also that the public-facing API here doesn't give us a way to
419 : // pass along JITInfo. It's probably ok though, since Xrays are already
420 : // pretty slow.
421 0 : desc.value().setUndefined();
422 3 : unsigned flags = psMatch->flags;
423 6 : if (psMatch->isAccessor()) {
424 6 : RootedFunction getterObj(cx);
425 6 : RootedFunction setterObj(cx);
426 3 : if (psMatch->isSelfHosted()) {
427 0 : getterObj = JS::GetSelfHostedFunction(cx, psMatch->accessors.getter.selfHosted.funname, id, 0);
428 0 : if (!getterObj)
429 0 : return false;
430 0 : desc.setGetterObject(JS_GetFunctionObject(getterObj));
431 0 : if (psMatch->accessors.setter.selfHosted.funname) {
432 0 : MOZ_ASSERT(flags & JSPROP_SETTER);
433 0 : setterObj = JS::GetSelfHostedFunction(cx, psMatch->accessors.setter.selfHosted.funname, id, 0);
434 0 : if (!setterObj)
435 : return false;
436 0 : desc.setSetterObject(JS_GetFunctionObject(setterObj));
437 : }
438 : } else {
439 3 : desc.setGetter(JS_CAST_NATIVE_TO(psMatch->accessors.getter.native.op,
440 3 : JSGetterOp));
441 3 : desc.setSetter(JS_CAST_NATIVE_TO(psMatch->accessors.setter.native.op,
442 : JSSetterOp));
443 : }
444 3 : desc.setAttributes(flags);
445 0 : if (!JS_DefinePropertyById(cx, holder, id,
446 0 : JS_PROPERTYOP_GETTER(desc.getter()),
447 0 : JS_PROPERTYOP_SETTER(desc.setter()),
448 : // This particular descriptor, unlike most,
449 : // actually stores JSNatives directly,
450 : // since we just set it up. Do NOT pass
451 : // JSPROP_PROPOP_ACCESSORS here!
452 : desc.attributes()))
453 : {
454 : return false;
455 : }
456 : } else {
457 0 : RootedValue v(cx);
458 0 : if (!psMatch->getValue(cx, &v))
459 0 : return false;
460 0 : if (!JS_DefinePropertyById(cx, holder, id, v, flags & ~JSPROP_INTERNAL_USE_BIT))
461 : return false;
462 : }
463 :
464 3 : return JS_GetOwnPropertyDescriptorById(cx, holder, id, desc);
465 : }
466 :
467 : return true;
468 : }
469 :
470 : static bool
471 : ShouldResolveStaticProperties(JSProtoKey key)
472 : {
473 : // Don't try to resolve static properties on RegExp, because they
474 : // have issues. In particular, some of them grab state off the
475 : // global of the RegExp constructor that describes the last regexp
476 : // evaluation in that global, which is not a useful thing to do
477 : // over Xrays.
478 : return key != JSProto_RegExp;
479 : }
480 :
481 : bool
482 1 : JSXrayTraits::resolveOwnProperty(JSContext* cx, HandleObject wrapper,
483 : HandleObject target, HandleObject holder,
484 : HandleId id,
485 : MutableHandle<PropertyDescriptor> desc)
486 : {
487 : // Call the common code.
488 173 : bool ok = XrayTraits::resolveOwnProperty(cx, wrapper, target, holder,
489 173 : id, desc);
490 0 : if (!ok || desc.object())
491 : return ok;
492 :
493 : // The non-HasPrototypes semantics implemented by traditional Xrays are kind
494 : // of broken with respect to |own|-ness and the holder. The common code
495 : // muddles through by only checking the holder for non-|own| lookups, but
496 : // that doesn't work for us. So we do an explicit holder check here, and hope
497 : // that this mess gets fixed up soon.
498 173 : if (!JS_GetOwnPropertyDescriptorById(cx, holder, id, desc))
499 : return false;
500 346 : if (desc.object()) {
501 38 : desc.object().set(wrapper);
502 19 : return true;
503 : }
504 :
505 1 : JSProtoKey key = getProtoKey(holder);
506 0 : if (!isPrototype(holder)) {
507 : // For Object and Array instances, we expose some properties from the
508 : // underlying object, but only after filtering them carefully.
509 : //
510 : // Note that, as far as JS observables go, Arrays are just Objects with
511 : // a different prototype and a magic (own, non-configurable) |.length| that
512 : // serves as a non-tight upper bound on |own| indexed properties. So while
513 : // it's tempting to try to impose some sort of structure on what Arrays
514 : // "should" look like over Xrays, the underlying object is squishy enough
515 : // that it makes sense to just treat them like Objects for Xray purposes.
516 58 : if (key == JSProto_Object || key == JSProto_Array) {
517 46 : return getOwnPropertyFromWrapperIfSafe(cx, wrapper, id, desc);
518 : }
519 12 : if (IsTypedArrayKey(key)) {
520 0 : if (IsArrayIndex(GetArrayIndexFromId(cx, id))) {
521 : // WebExtensions can't use cloneInto(), so we just let them do
522 : // the slow thing to maximize compatibility.
523 0 : if (CompartmentPrivate::Get(CurrentGlobalOrNull(cx))->isWebExtensionContentScript) {
524 0 : Rooted<PropertyDescriptor> innerDesc(cx);
525 : {
526 0 : JSAutoRealm ar(cx, target);
527 0 : JS_MarkCrossZoneId(cx, id);
528 0 : if (!JS_GetOwnPropertyDescriptorById(cx, target, id, &innerDesc))
529 0 : return false;
530 : }
531 0 : if (innerDesc.isDataDescriptor() && innerDesc.value().isNumber()) {
532 0 : desc.setValue(innerDesc.value());
533 0 : desc.object().set(wrapper);
534 : }
535 : return true;
536 : }
537 : JS_ReportErrorASCII(cx, "Accessing TypedArray data over Xrays is slow, and forbidden "
538 : "in order to encourage performant code. To copy TypedArrays "
539 0 : "across origin boundaries, consider using Components.utils.cloneInto().");
540 0 : return false;
541 : }
542 12 : } else if (key == JSProto_Function) {
543 8 : if (id == GetJSIDByIndex(cx, XPCJSContext::IDX_LENGTH)) {
544 0 : FillPropertyDescriptor(desc, wrapper, JSPROP_PERMANENT | JSPROP_READONLY,
545 0 : NumberValue(JS_GetFunctionArity(JS_GetObjectFunction(target))));
546 0 : return true;
547 : }
548 0 : if (id == GetJSIDByIndex(cx, XPCJSContext::IDX_NAME)) {
549 0 : RootedString fname(cx, JS_GetFunctionId(JS_GetObjectFunction(target)));
550 0 : if (fname)
551 0 : JS_MarkCrossZoneIdValue(cx, StringValue(fname));
552 0 : FillPropertyDescriptor(desc, wrapper, JSPROP_PERMANENT | JSPROP_READONLY,
553 0 : fname ? StringValue(fname) : JS_GetEmptyStringValue(cx));
554 : } else {
555 : // Look for various static properties/methods and the
556 : // 'prototype' property.
557 4 : JSProtoKey standardConstructor = constructorFor(holder);
558 4 : if (standardConstructor != JSProto_Null) {
559 : // Handle the 'prototype' property to make
560 : // xrayedGlobal.StandardClass.prototype work.
561 8 : if (id == GetJSIDByIndex(cx, XPCJSContext::IDX_PROTOTYPE)) {
562 0 : RootedObject standardProto(cx);
563 : {
564 0 : JSAutoRealm ar(cx, target);
565 0 : if (!JS_GetClassPrototype(cx, standardConstructor, &standardProto))
566 0 : return false;
567 0 : MOZ_ASSERT(standardProto);
568 : }
569 :
570 0 : if (!JS_WrapObject(cx, &standardProto))
571 : return false;
572 0 : FillPropertyDescriptor(desc, wrapper, JSPROP_PERMANENT | JSPROP_READONLY,
573 0 : ObjectValue(*standardProto));
574 0 : return true;
575 : }
576 :
577 0 : if (ShouldResolveStaticProperties(standardConstructor)) {
578 0 : const js::Class* clasp = js::ProtoKeyToClass(standardConstructor);
579 0 : MOZ_ASSERT(clasp->specDefined());
580 :
581 12 : if (!TryResolvePropertyFromSpecs(cx, id, holder,
582 : clasp->specConstructorFunctions(),
583 : clasp->specConstructorProperties(), desc)) {
584 : return false;
585 : }
586 :
587 8 : if (desc.object()) {
588 0 : desc.object().set(wrapper);
589 0 : return true;
590 : }
591 : }
592 : }
593 : }
594 0 : } else if (IsErrorObjectKey(key)) {
595 : // The useful state of error objects (except for .stack) is
596 : // (unfortunately) represented as own data properties per-spec. This
597 : // means that we can't have a a clean representation of the data
598 : // (free from tampering) without doubling the slots of Error
599 : // objects, which isn't great. So we forward these properties to the
600 : // underlying object and then just censor any values with the wrong
601 : // type. This limits the ability of content to do anything all that
602 : // confusing.
603 : bool isErrorIntProperty =
604 0 : id == GetJSIDByIndex(cx, XPCJSContext::IDX_LINENUMBER) ||
605 0 : id == GetJSIDByIndex(cx, XPCJSContext::IDX_COLUMNNUMBER);
606 : bool isErrorStringProperty =
607 0 : id == GetJSIDByIndex(cx, XPCJSContext::IDX_FILENAME) ||
608 0 : id == GetJSIDByIndex(cx, XPCJSContext::IDX_MESSAGE);
609 0 : if (isErrorIntProperty || isErrorStringProperty) {
610 0 : RootedObject waiver(cx, wrapper);
611 0 : if (!WrapperFactory::WaiveXrayAndWrap(cx, &waiver))
612 : return false;
613 0 : if (!JS_GetOwnPropertyDescriptorById(cx, waiver, id, desc))
614 : return false;
615 0 : bool valueMatchesType = (isErrorIntProperty && desc.value().isInt32()) ||
616 0 : (isErrorStringProperty && desc.value().isString());
617 0 : if (desc.hasGetterOrSetter() || !valueMatchesType)
618 0 : FillPropertyDescriptor(desc, nullptr, 0, UndefinedValue());
619 : return true;
620 : }
621 8 : } else if (key == JSProto_RegExp) {
622 0 : if (id == GetJSIDByIndex(cx, XPCJSContext::IDX_LASTINDEX))
623 0 : return getOwnPropertyFromWrapperIfSafe(cx, wrapper, id, desc);
624 : }
625 :
626 : // The rest of this function applies only to prototypes.
627 : return true;
628 : }
629 :
630 : // Handle the 'constructor' property.
631 192 : if (id == GetJSIDByIndex(cx, XPCJSContext::IDX_CONSTRUCTOR)) {
632 16 : RootedObject constructor(cx);
633 : {
634 0 : JSAutoRealm ar(cx, target);
635 0 : if (!JS_GetClassObject(cx, key, &constructor))
636 0 : return false;
637 : }
638 8 : if (!JS_WrapObject(cx, &constructor))
639 : return false;
640 0 : desc.object().set(wrapper);
641 8 : desc.setAttributes(0);
642 8 : desc.setGetter(nullptr);
643 8 : desc.setSetter(nullptr);
644 16 : desc.value().setObject(*constructor);
645 8 : return true;
646 : }
647 :
648 : // Grab the JSClass. We require all Xrayable classes to have a ClassSpec.
649 176 : const js::Class* clasp = js::GetObjectClass(target);
650 0 : MOZ_ASSERT(clasp->specDefined());
651 :
652 : // Indexed array properties are handled above, so we can just work with the
653 : // class spec here.
654 0 : if (!TryResolvePropertyFromSpecs(cx, id, holder,
655 : clasp->specPrototypeFunctions(),
656 : clasp->specPrototypeProperties(),
657 : desc)) {
658 : return false;
659 : }
660 :
661 0 : if (desc.object()) {
662 0 : desc.object().set(wrapper);
663 : }
664 :
665 : return true;
666 : }
667 :
668 : bool
669 0 : JSXrayTraits::delete_(JSContext* cx, HandleObject wrapper, HandleId id, ObjectOpResult& result)
670 : {
671 0 : RootedObject holder(cx, ensureHolder(cx, wrapper));
672 0 : if (!holder)
673 : return false;
674 :
675 : // If we're using Object Xrays, we allow callers to attempt to delete any
676 : // property from the underlying object that they are able to resolve. Note
677 : // that this deleting may fail if the property is non-configurable.
678 0 : JSProtoKey key = getProtoKey(holder);
679 0 : bool isObjectOrArrayInstance = (key == JSProto_Object || key == JSProto_Array) &&
680 0 : !isPrototype(holder);
681 0 : if (isObjectOrArrayInstance) {
682 0 : RootedObject target(cx, getTargetObject(wrapper));
683 0 : JSAutoRealm ar(cx, target);
684 0 : JS_MarkCrossZoneId(cx, id);
685 0 : Rooted<PropertyDescriptor> desc(cx);
686 0 : if (!getOwnPropertyFromTargetIfSafe(cx, target, wrapper, id, &desc))
687 0 : return false;
688 0 : if (desc.object())
689 0 : return JS_DeletePropertyById(cx, target, id, result);
690 : }
691 0 : return result.succeed();
692 : }
693 :
694 : bool
695 0 : JSXrayTraits::defineProperty(JSContext* cx, HandleObject wrapper, HandleId id,
696 : Handle<PropertyDescriptor> desc,
697 : Handle<PropertyDescriptor> existingDesc,
698 : ObjectOpResult& result,
699 : bool* defined)
700 : {
701 0 : *defined = false;
702 0 : RootedObject holder(cx, ensureHolder(cx, wrapper));
703 0 : if (!holder)
704 : return false;
705 :
706 :
707 : // Object and Array instances are special. For those cases, we forward property
708 : // definitions to the underlying object if the following conditions are met:
709 : // * The property being defined is a value-prop.
710 : // * The property being defined is either a primitive or subsumed by the target.
711 : // * As seen from the Xray, any existing property that we would overwrite is an
712 : // |own| value-prop.
713 : //
714 : // To avoid confusion, we disallow expandos on Object and Array instances, and
715 : // therefore raise an exception here if the above conditions aren't met.
716 0 : JSProtoKey key = getProtoKey(holder);
717 0 : bool isInstance = !isPrototype(holder);
718 0 : bool isObjectOrArray = (key == JSProto_Object || key == JSProto_Array);
719 0 : if (isObjectOrArray && isInstance) {
720 0 : RootedObject target(cx, getTargetObject(wrapper));
721 0 : if (desc.hasGetterOrSetter()) {
722 0 : JS_ReportErrorASCII(cx, "Not allowed to define accessor property on [Object] or [Array] XrayWrapper");
723 0 : return false;
724 : }
725 0 : if (desc.value().isObject() &&
726 0 : !AccessCheck::subsumes(target, js::UncheckedUnwrap(&desc.value().toObject())))
727 : {
728 0 : JS_ReportErrorASCII(cx, "Not allowed to define cross-origin object as property on [Object] or [Array] XrayWrapper");
729 0 : return false;
730 : }
731 0 : if (existingDesc.hasGetterOrSetter()) {
732 0 : JS_ReportErrorASCII(cx, "Not allowed to overwrite accessor property on [Object] or [Array] XrayWrapper");
733 0 : return false;
734 : }
735 0 : if (existingDesc.object() && existingDesc.object() != wrapper) {
736 0 : JS_ReportErrorASCII(cx, "Not allowed to shadow non-own Xray-resolved property on [Object] or [Array] XrayWrapper");
737 0 : return false;
738 : }
739 :
740 0 : Rooted<PropertyDescriptor> wrappedDesc(cx, desc);
741 0 : JSAutoRealm ar(cx, target);
742 0 : JS_MarkCrossZoneId(cx, id);
743 0 : if (!JS_WrapPropertyDescriptor(cx, &wrappedDesc) ||
744 0 : !JS_DefinePropertyById(cx, target, id, wrappedDesc, result))
745 : {
746 : return false;
747 : }
748 0 : *defined = true;
749 0 : return true;
750 : }
751 :
752 : // For WebExtensions content scripts, we forward the definition of indexed properties. By
753 : // validating that the key and value are both numbers, we can avoid doing any wrapping.
754 0 : if (isInstance && IsTypedArrayKey(key) &&
755 0 : CompartmentPrivate::Get(JS::CurrentGlobalOrNull(cx))->isWebExtensionContentScript &&
756 0 : desc.isDataDescriptor() && (desc.value().isNumber() || desc.value().isUndefined()) &&
757 0 : IsArrayIndex(GetArrayIndexFromId(cx, id)))
758 : {
759 0 : RootedObject target(cx, getTargetObject(wrapper));
760 0 : JSAutoRealm ar(cx, target);
761 0 : JS_MarkCrossZoneId(cx, id);
762 0 : if (!JS_DefinePropertyById(cx, target, id, desc, result))
763 : return false;
764 0 : *defined = true;
765 0 : return true;
766 : }
767 :
768 : return true;
769 : }
770 :
771 : static bool
772 0 : MaybeAppend(jsid id, unsigned flags, AutoIdVector& props)
773 : {
774 0 : MOZ_ASSERT(!(flags & JSITER_SYMBOLSONLY));
775 0 : if (!(flags & JSITER_SYMBOLS) && JSID_IS_SYMBOL(id))
776 : return true;
777 0 : return props.append(id);
778 : }
779 :
780 : // Append the names from the given function and property specs to props.
781 : static bool
782 0 : AppendNamesFromFunctionAndPropertySpecs(JSContext* cx,
783 : const JSFunctionSpec* fs,
784 : const JSPropertySpec* ps,
785 : unsigned flags,
786 : AutoIdVector& props)
787 : {
788 : // Convert the method and property names to jsids and pass them to the caller.
789 0 : for ( ; fs && fs->name; ++fs) {
790 0 : jsid id;
791 0 : if (!PropertySpecNameToPermanentId(cx, fs->name, &id))
792 0 : return false;
793 0 : if (!MaybeAppend(id, flags, props))
794 : return false;
795 : }
796 0 : for ( ; ps && ps->name; ++ps) {
797 0 : jsid id;
798 0 : if (!PropertySpecNameToPermanentId(cx, ps->name, &id))
799 0 : return false;
800 0 : if (!MaybeAppend(id, flags, props))
801 : return false;
802 : }
803 :
804 : return true;
805 : }
806 :
807 : bool
808 0 : JSXrayTraits::enumerateNames(JSContext* cx, HandleObject wrapper, unsigned flags,
809 : AutoIdVector& props)
810 : {
811 0 : RootedObject target(cx, getTargetObject(wrapper));
812 0 : RootedObject holder(cx, ensureHolder(cx, wrapper));
813 0 : if (!holder)
814 : return false;
815 :
816 0 : JSProtoKey key = getProtoKey(holder);
817 0 : if (!isPrototype(holder)) {
818 : // For Object and Array instances, we expose some properties from the underlying
819 : // object, but only after filtering them carefully.
820 0 : if (key == JSProto_Object || key == JSProto_Array) {
821 0 : MOZ_ASSERT(props.empty());
822 : {
823 0 : JSAutoRealm ar(cx, target);
824 0 : AutoIdVector targetProps(cx);
825 0 : if (!js::GetPropertyKeys(cx, target, flags | JSITER_OWNONLY, &targetProps))
826 0 : return false;
827 : // Loop over the properties, and only pass along the ones that
828 : // we determine to be safe.
829 0 : if (!props.reserve(targetProps.length()))
830 : return false;
831 0 : for (size_t i = 0; i < targetProps.length(); ++i) {
832 0 : Rooted<PropertyDescriptor> desc(cx);
833 0 : RootedId id(cx, targetProps[i]);
834 0 : if (!getOwnPropertyFromTargetIfSafe(cx, target, wrapper, id, &desc))
835 0 : return false;
836 0 : if (desc.object())
837 : props.infallibleAppend(id);
838 : }
839 : }
840 0 : for (size_t i = 0; i < props.length(); ++i)
841 0 : JS_MarkCrossZoneId(cx, props[i]);
842 : return true;
843 : }
844 0 : if (IsTypedArrayKey(key)) {
845 0 : uint32_t length = JS_GetTypedArrayLength(target);
846 : // TypedArrays enumerate every indexed property in range, but
847 : // |length| is a getter that lives on the proto, like it should be.
848 0 : if (!props.reserve(length))
849 : return false;
850 0 : for (int32_t i = 0; i <= int32_t(length - 1); ++i)
851 0 : props.infallibleAppend(INT_TO_JSID(i));
852 0 : } else if (key == JSProto_Function) {
853 0 : if (!props.append(GetJSIDByIndex(cx, XPCJSContext::IDX_LENGTH)))
854 : return false;
855 0 : if (!props.append(GetJSIDByIndex(cx, XPCJSContext::IDX_NAME)))
856 : return false;
857 : // Handle the .prototype property and static properties on standard
858 : // constructors.
859 0 : JSProtoKey standardConstructor = constructorFor(holder);
860 0 : if (standardConstructor != JSProto_Null) {
861 0 : if (!props.append(GetJSIDByIndex(cx, XPCJSContext::IDX_PROTOTYPE)))
862 : return false;
863 :
864 0 : if (ShouldResolveStaticProperties(standardConstructor)) {
865 0 : const js::Class* clasp = js::ProtoKeyToClass(standardConstructor);
866 0 : MOZ_ASSERT(clasp->specDefined());
867 :
868 0 : if (!AppendNamesFromFunctionAndPropertySpecs(
869 : cx, clasp->specConstructorFunctions(),
870 : clasp->specConstructorProperties(), flags, props)) {
871 : return false;
872 : }
873 : }
874 : }
875 0 : } else if (IsErrorObjectKey(key)) {
876 0 : if (!props.append(GetJSIDByIndex(cx, XPCJSContext::IDX_FILENAME)) ||
877 0 : !props.append(GetJSIDByIndex(cx, XPCJSContext::IDX_LINENUMBER)) ||
878 0 : !props.append(GetJSIDByIndex(cx, XPCJSContext::IDX_COLUMNNUMBER)) ||
879 0 : !props.append(GetJSIDByIndex(cx, XPCJSContext::IDX_STACK)) ||
880 0 : !props.append(GetJSIDByIndex(cx, XPCJSContext::IDX_MESSAGE)))
881 : {
882 : return false;
883 : }
884 0 : } else if (key == JSProto_RegExp) {
885 0 : if (!props.append(GetJSIDByIndex(cx, XPCJSContext::IDX_LASTINDEX)))
886 : return false;
887 : }
888 :
889 : // The rest of this function applies only to prototypes.
890 : return true;
891 : }
892 :
893 : // Add the 'constructor' property.
894 0 : if (!props.append(GetJSIDByIndex(cx, XPCJSContext::IDX_CONSTRUCTOR)))
895 : return false;
896 :
897 : // Grab the JSClass. We require all Xrayable classes to have a ClassSpec.
898 0 : const js::Class* clasp = js::GetObjectClass(target);
899 0 : MOZ_ASSERT(clasp->specDefined());
900 :
901 0 : return AppendNamesFromFunctionAndPropertySpecs(
902 : cx, clasp->specPrototypeFunctions(),
903 0 : clasp->specPrototypeProperties(), flags, props);
904 : }
905 :
906 : bool
907 0 : JSXrayTraits::construct(JSContext* cx, HandleObject wrapper,
908 : const JS::CallArgs& args, const js::Wrapper& baseInstance)
909 : {
910 0 : JSXrayTraits& self = JSXrayTraits::singleton;
911 0 : JS::RootedObject holder(cx, self.ensureHolder(cx, wrapper));
912 0 : if (!holder)
913 : return false;
914 :
915 8 : if (xpc::JSXrayTraits::getProtoKey(holder) == JSProto_Function) {
916 8 : JSProtoKey standardConstructor = constructorFor(holder);
917 8 : if (standardConstructor == JSProto_Null)
918 0 : return baseInstance.construct(cx, wrapper, args);
919 :
920 8 : const js::Class* clasp = js::ProtoKeyToClass(standardConstructor);
921 0 : MOZ_ASSERT(clasp);
922 1 : if (!(clasp->flags & JSCLASS_HAS_XRAYED_CONSTRUCTOR))
923 0 : return baseInstance.construct(cx, wrapper, args);
924 :
925 : // If the JSCLASS_HAS_XRAYED_CONSTRUCTOR flag is set on the Class,
926 : // we don't use the constructor at hand. Instead, we retrieve the
927 : // equivalent standard constructor in the xray compartment and run
928 : // it in that compartment. The newTarget isn't unwrapped, and the
929 : // constructor has to be able to detect and handle this situation.
930 : // See the comments in js/public/Class.h and PromiseConstructor for
931 : // details and an example.
932 16 : RootedObject ctor(cx);
933 8 : if (!JS_GetClassObject(cx, standardConstructor, &ctor))
934 : return false;
935 :
936 16 : RootedValue ctorVal(cx, ObjectValue(*ctor));
937 8 : HandleValueArray vals(args);
938 16 : RootedObject result(cx);
939 16 : if (!JS::Construct(cx, ctorVal, wrapper, vals, &result))
940 : return false;
941 8 : AssertSameCompartment(cx, result);
942 16 : args.rval().setObject(*result);
943 8 : return true;
944 : }
945 :
946 0 : JS::RootedValue v(cx, JS::ObjectValue(*wrapper));
947 0 : js::ReportIsNotFunction(cx, v);
948 : return false;
949 : }
950 :
951 : JSObject*
952 25 : JSXrayTraits::createHolder(JSContext* cx, JSObject* wrapper)
953 : {
954 50 : RootedObject target(cx, getTargetObject(wrapper));
955 50 : RootedObject holder(cx, JS_NewObjectWithGivenProto(cx, &HolderClass,
956 0 : nullptr));
957 0 : if (!holder)
958 : return nullptr;
959 :
960 : // Compute information about the target.
961 0 : bool isPrototype = false;
962 0 : JSProtoKey key = IdentifyStandardInstance(target);
963 0 : if (key == JSProto_Null) {
964 0 : isPrototype = true;
965 8 : key = IdentifyStandardPrototype(target);
966 : }
967 0 : MOZ_ASSERT(key != JSProto_Null);
968 :
969 : // Store it on the holder.
970 50 : RootedValue v(cx);
971 25 : v.setNumber(static_cast<uint32_t>(key));
972 50 : js::SetReservedSlot(holder, SLOT_PROTOKEY, v);
973 50 : v.setBoolean(isPrototype);
974 50 : js::SetReservedSlot(holder, SLOT_ISPROTOTYPE, v);
975 :
976 : // If this is a function, also compute whether it serves as a constructor
977 : // for a standard class.
978 0 : if (key == JSProto_Function) {
979 0 : v.setNumber(static_cast<uint32_t>(IdentifyStandardConstructor(target)));
980 8 : js::SetReservedSlot(holder, SLOT_CONSTRUCTOR_FOR, v);
981 : }
982 :
983 0 : return holder;
984 : }
985 :
986 : DOMXrayTraits DOMXrayTraits::singleton;
987 : JSXrayTraits JSXrayTraits::singleton;
988 : OpaqueXrayTraits OpaqueXrayTraits::singleton;
989 :
990 : XrayTraits*
991 101 : GetXrayTraits(JSObject* obj)
992 : {
993 1 : switch (GetXrayType(obj)) {
994 : case XrayForDOMObject:
995 : return &DOMXrayTraits::singleton;
996 : case XrayForJSObject:
997 0 : return &JSXrayTraits::singleton;
998 : case XrayForOpaqueObject:
999 0 : return &OpaqueXrayTraits::singleton;
1000 : default:
1001 0 : return nullptr;
1002 : }
1003 : }
1004 :
1005 : /*
1006 : * Xray expando handling.
1007 : *
1008 : * We hang expandos for Xray wrappers off a reserved slot on the target object
1009 : * so that same-origin compartments can share expandos for a given object. We
1010 : * have a linked list of expando objects, one per origin. The properties on these
1011 : * objects are generally wrappers pointing back to the compartment that applied
1012 : * them.
1013 : *
1014 : * The expando objects should _never_ be exposed to script. The fact that they
1015 : * live in the target compartment is a detail of the implementation, and does
1016 : * not imply that code in the target compartment should be allowed to inspect
1017 : * them. They are private to the origin that placed them.
1018 : */
1019 :
1020 : // Certain globals do not share expandos with other globals. Xrays in these
1021 : // globals cache expandos on the wrapper's holder, as there is only one such
1022 : // wrapper which can create or access the expando. This allows for faster
1023 : // access to the expando, including through JIT inline caches.
1024 : static inline bool
1025 0 : GlobalHasExclusiveExpandos(JSObject* obj)
1026 : {
1027 2 : MOZ_ASSERT(JS_IsGlobalObject(obj));
1028 2 : return !strcmp(js::GetObjectJSClass(obj)->name, "Sandbox");
1029 : }
1030 :
1031 : static inline JSObject*
1032 : GetCachedXrayExpando(JSObject* wrapper);
1033 :
1034 : static inline void
1035 : SetCachedXrayExpando(JSObject* holder, JSObject* expandoWrapper);
1036 :
1037 : static nsIPrincipal*
1038 0 : ObjectPrincipal(JSObject* obj)
1039 : {
1040 0 : return GetCompartmentPrincipal(js::GetObjectCompartment(obj));
1041 : }
1042 :
1043 : static nsIPrincipal*
1044 0 : GetExpandoObjectPrincipal(JSObject* expandoObject)
1045 : {
1046 0 : Value v = JS_GetReservedSlot(expandoObject, JSSLOT_EXPANDO_ORIGIN);
1047 0 : return static_cast<nsIPrincipal*>(v.toPrivate());
1048 : }
1049 :
1050 : static void
1051 0 : ExpandoObjectFinalize(JSFreeOp* fop, JSObject* obj)
1052 : {
1053 : // Release the principal.
1054 0 : nsIPrincipal* principal = GetExpandoObjectPrincipal(obj);
1055 0 : NS_RELEASE(principal);
1056 0 : }
1057 :
1058 : const JSClassOps XrayExpandoObjectClassOps = {
1059 : nullptr, nullptr, nullptr, nullptr,
1060 : nullptr, nullptr,
1061 : ExpandoObjectFinalize
1062 : };
1063 :
1064 : bool
1065 0 : XrayTraits::expandoObjectMatchesConsumer(JSContext* cx,
1066 : HandleObject expandoObject,
1067 : nsIPrincipal* consumerOrigin)
1068 : {
1069 0 : MOZ_ASSERT(js::IsObjectInContextCompartment(expandoObject, cx));
1070 :
1071 : // First, compare the principals.
1072 0 : nsIPrincipal* o = GetExpandoObjectPrincipal(expandoObject);
1073 : // Note that it's very important here to ignore document.domain. We
1074 : // pull the principal for the expando object off of the first consumer
1075 : // for a given origin, and freely share the expandos amongst multiple
1076 : // same-origin consumers afterwards. However, this means that we have
1077 : // no way to know whether _all_ consumers have opted in to collaboration
1078 : // by explicitly setting document.domain. So we just mandate that expando
1079 : // sharing is unaffected by it.
1080 0 : if (!consumerOrigin->Equals(o))
1081 : return false;
1082 :
1083 : // Certain globals exclusively own the associated expandos, in which case
1084 : // the caller should have used the cached expando on the wrapper instead.
1085 0 : JSObject* owner = JS_GetReservedSlot(expandoObject,
1086 0 : JSSLOT_EXPANDO_EXCLUSIVE_WRAPPER_HOLDER)
1087 0 : .toObjectOrNull();
1088 0 : return owner == nullptr;
1089 : }
1090 :
1091 : bool
1092 0 : XrayTraits::getExpandoObjectInternal(JSContext* cx, JSObject* expandoChain,
1093 : HandleObject exclusiveWrapper,
1094 : nsIPrincipal* origin,
1095 : MutableHandleObject expandoObject)
1096 : {
1097 0 : MOZ_ASSERT(!JS_IsExceptionPending(cx));
1098 0 : expandoObject.set(nullptr);
1099 :
1100 : // Use the cached expando if this wrapper has exclusive access to it.
1101 0 : if (exclusiveWrapper) {
1102 0 : JSObject* expandoWrapper = GetCachedXrayExpando(exclusiveWrapper);
1103 0 : expandoObject.set(expandoWrapper ? UncheckedUnwrap(expandoWrapper) : nullptr);
1104 : #ifdef DEBUG
1105 : // Make sure the expando we found is on the target's chain. While we
1106 : // don't use this chain to look up expandos for the wrapper,
1107 : // the expando still needs to be on the chain to keep the wrapper and
1108 : // expando alive.
1109 0 : if (expandoObject) {
1110 0 : JSObject* head = expandoChain;
1111 0 : while (head && head != expandoObject)
1112 0 : head = JS_GetReservedSlot(head, JSSLOT_EXPANDO_NEXT).toObjectOrNull();
1113 0 : MOZ_ASSERT(head == expandoObject);
1114 : }
1115 : #endif
1116 : return true;
1117 : }
1118 :
1119 : // The expando object lives in the compartment of the target, so all our
1120 : // work needs to happen there.
1121 0 : RootedObject head(cx, expandoChain);
1122 0 : JSAutoRealm ar(cx, head);
1123 :
1124 : // Iterate through the chain, looking for a same-origin object.
1125 0 : while (head) {
1126 0 : if (expandoObjectMatchesConsumer(cx, head, origin)) {
1127 0 : expandoObject.set(head);
1128 0 : return true;
1129 : }
1130 0 : head = JS_GetReservedSlot(head, JSSLOT_EXPANDO_NEXT).toObjectOrNull();
1131 : }
1132 :
1133 : // Not found.
1134 : return true;
1135 : }
1136 :
1137 : bool
1138 1010 : XrayTraits::getExpandoObject(JSContext* cx, HandleObject target, HandleObject consumer,
1139 : MutableHandleObject expandoObject)
1140 : {
1141 : // Return early if no expando object has ever been attached, which is
1142 : // usually the case.
1143 1010 : JSObject* chain = getExpandoChain(target);
1144 1010 : if (!chain)
1145 : return true;
1146 :
1147 0 : JSObject* consumerGlobal = js::GetGlobalForObjectCrossCompartment(consumer);
1148 0 : bool isExclusive = GlobalHasExclusiveExpandos(consumerGlobal);
1149 0 : return getExpandoObjectInternal(cx, chain, isExclusive ? consumer : nullptr,
1150 0 : ObjectPrincipal(consumer), expandoObject);
1151 : }
1152 :
1153 : // Wrappers which have exclusive access to the expando on their target object
1154 : // need to be kept alive as long as the target object exists. This is done by
1155 : // keeping the expando in the expando chain on the target (even though it will
1156 : // not be used while looking up the expando for the wrapper), and keeping a
1157 : // strong reference from that expando to the wrapper itself, via the
1158 : // JSSLOT_EXPANDO_EXCLUSIVE_WRAPPER_HOLDER reserved slot. This slot does not
1159 : // point to the wrapper itself, because it is a cross compartment edge and we
1160 : // can't create a wrapper for a wrapper. Instead, the slot points to an
1161 : // instance of the holder class below in the wrapper's compartment, and the
1162 : // wrapper is held via this holder object's reserved slot.
1163 : static const JSClass gWrapperHolderClass = {
1164 : "XrayExpandoWrapperHolder",
1165 : JSCLASS_HAS_RESERVED_SLOTS(1)
1166 : };
1167 : static const size_t JSSLOT_WRAPPER_HOLDER_CONTENTS = 0;
1168 :
1169 : JSObject*
1170 0 : XrayTraits::attachExpandoObject(JSContext* cx, HandleObject target,
1171 : HandleObject exclusiveWrapper,
1172 : nsIPrincipal* origin)
1173 : {
1174 : // Make sure the compartments are sane.
1175 0 : MOZ_ASSERT(js::IsObjectInContextCompartment(target, cx));
1176 0 : MOZ_ASSERT_IF(exclusiveWrapper, !js::IsObjectInContextCompartment(exclusiveWrapper, cx));
1177 :
1178 : // No duplicates allowed.
1179 : #ifdef DEBUG
1180 : {
1181 0 : JSObject* chain = getExpandoChain(target);
1182 0 : if (chain) {
1183 0 : RootedObject existingExpandoObject(cx);
1184 0 : if (getExpandoObjectInternal(cx, chain, exclusiveWrapper, origin, &existingExpandoObject))
1185 0 : MOZ_ASSERT(!existingExpandoObject);
1186 : else
1187 0 : JS_ClearPendingException(cx);
1188 : }
1189 : }
1190 : #endif
1191 :
1192 : // Create the expando object.
1193 0 : const JSClass* expandoClass = getExpandoClass(cx, target);
1194 0 : MOZ_ASSERT(!strcmp(expandoClass->name, "XrayExpandoObject"));
1195 : RootedObject expandoObject(cx,
1196 0 : JS_NewObjectWithGivenProto(cx, expandoClass, nullptr));
1197 0 : if (!expandoObject)
1198 : return nullptr;
1199 :
1200 : // AddRef and store the principal.
1201 0 : NS_ADDREF(origin);
1202 0 : JS_SetReservedSlot(expandoObject, JSSLOT_EXPANDO_ORIGIN, JS::PrivateValue(origin));
1203 :
1204 : // Note the exclusive wrapper, if there is one.
1205 0 : RootedObject wrapperHolder(cx);
1206 0 : if (exclusiveWrapper) {
1207 0 : JSAutoRealm ar(cx, exclusiveWrapper);
1208 0 : wrapperHolder = JS_NewObjectWithGivenProto(cx, &gWrapperHolderClass, nullptr);
1209 0 : if (!wrapperHolder)
1210 0 : return nullptr;
1211 0 : JS_SetReservedSlot(wrapperHolder, JSSLOT_WRAPPER_HOLDER_CONTENTS, ObjectValue(*exclusiveWrapper));
1212 : }
1213 0 : if (!JS_WrapObject(cx, &wrapperHolder))
1214 : return nullptr;
1215 0 : JS_SetReservedSlot(expandoObject, JSSLOT_EXPANDO_EXCLUSIVE_WRAPPER_HOLDER,
1216 0 : ObjectOrNullValue(wrapperHolder));
1217 :
1218 : // Store it on the exclusive wrapper, if there is one.
1219 0 : if (exclusiveWrapper) {
1220 0 : RootedObject cachedExpandoObject(cx, expandoObject);
1221 0 : JSAutoRealm ar(cx, exclusiveWrapper);
1222 0 : if (!JS_WrapObject(cx, &cachedExpandoObject))
1223 0 : return nullptr;
1224 0 : JSObject* holder = ensureHolder(cx, exclusiveWrapper);
1225 0 : if (!holder)
1226 : return nullptr;
1227 0 : SetCachedXrayExpando(holder, cachedExpandoObject);
1228 : }
1229 :
1230 : // If this is our first expando object, take the opportunity to preserve
1231 : // the wrapper. This keeps our expandos alive even if the Xray wrapper gets
1232 : // collected.
1233 0 : RootedObject chain(cx, getExpandoChain(target));
1234 0 : if (!chain)
1235 0 : preserveWrapper(target);
1236 :
1237 : // Insert it at the front of the chain.
1238 0 : JS_SetReservedSlot(expandoObject, JSSLOT_EXPANDO_NEXT, ObjectOrNullValue(chain));
1239 0 : setExpandoChain(cx, target, expandoObject);
1240 :
1241 0 : return expandoObject;
1242 : }
1243 :
1244 : JSObject*
1245 0 : XrayTraits::ensureExpandoObject(JSContext* cx, HandleObject wrapper,
1246 : HandleObject target)
1247 : {
1248 : // Expando objects live in the target compartment.
1249 0 : JSAutoRealm ar(cx, target);
1250 0 : RootedObject expandoObject(cx);
1251 0 : if (!getExpandoObject(cx, target, wrapper, &expandoObject))
1252 : return nullptr;
1253 0 : if (!expandoObject) {
1254 0 : JSObject* consumerGlobal = js::GetGlobalForObjectCrossCompartment(wrapper);
1255 0 : bool isExclusive = GlobalHasExclusiveExpandos(consumerGlobal);
1256 0 : expandoObject = attachExpandoObject(cx, target, isExclusive ? wrapper : nullptr,
1257 0 : ObjectPrincipal(wrapper));
1258 : }
1259 0 : return expandoObject;
1260 : }
1261 :
1262 : bool
1263 0 : XrayTraits::cloneExpandoChain(JSContext* cx, HandleObject dst, HandleObject srcChain)
1264 : {
1265 0 : MOZ_ASSERT(js::IsObjectInContextCompartment(dst, cx));
1266 0 : MOZ_ASSERT(getExpandoChain(dst) == nullptr);
1267 :
1268 0 : RootedObject oldHead(cx, srcChain);
1269 0 : while (oldHead) {
1270 0 : // If movingIntoXrayCompartment is true, then our new reflector is in a
1271 0 : // compartment that used to have an Xray-with-expandos to the old reflector
1272 0 : // and we should copy the expandos to the new reflector directly.
1273 0 : bool movingIntoXrayCompartment;
1274 0 :
1275 : // exclusiveWrapper is only used if movingIntoXrayCompartment ends up true.
1276 : RootedObject exclusiveWrapper(cx);
1277 : RootedObject wrapperHolder(cx, JS_GetReservedSlot(oldHead,
1278 0 : JSSLOT_EXPANDO_EXCLUSIVE_WRAPPER_HOLDER)
1279 0 : .toObjectOrNull());
1280 0 : if (wrapperHolder) {
1281 0 : RootedObject unwrappedHolder(cx, UncheckedUnwrap(wrapperHolder));
1282 : // unwrappedHolder is the compartment of the relevant Xray, so check
1283 0 : // whether that matches the compartment of cx (which matches the
1284 0 : // compartment of dst).
1285 0 : movingIntoXrayCompartment =
1286 0 : js::IsObjectInContextCompartment(unwrappedHolder, cx);
1287 0 :
1288 : if (!movingIntoXrayCompartment) {
1289 : // The global containing this wrapper holder has an xray for |src|
1290 : // with expandos. Create an xray in the global for |dst| which
1291 : // will be associated with a clone of |src|'s expando object.
1292 : JSAutoRealm ar(cx, unwrappedHolder);
1293 261 : exclusiveWrapper = dst;
1294 : if (!JS_WrapObject(cx, &exclusiveWrapper))
1295 261 : return false;
1296 : }
1297 160 : } else {
1298 : JSAutoRealm ar(cx, oldHead);
1299 : movingIntoXrayCompartment =
1300 0 : expandoObjectMatchesConsumer(cx, oldHead, ObjectPrincipal(dst));
1301 101 : }
1302 0 :
1303 0 : if (movingIntoXrayCompartment) {
1304 0 : // Just copy properties directly onto dst.
1305 : if (!JS_CopyPropertiesFrom(cx, dst, oldHead))
1306 0 : return false;
1307 101 : } else {
1308 0 : // Create a new expando object in the compartment of dst to replace
1309 0 : // oldHead.
1310 0 : RootedObject newHead(cx, attachExpandoObject(cx, dst, exclusiveWrapper,
1311 : GetExpandoObjectPrincipal(oldHead)));
1312 : if (!JS_CopyPropertiesFrom(cx, newHead, oldHead))
1313 : return false;
1314 : }
1315 0 : oldHead = JS_GetReservedSlot(oldHead, JSSLOT_EXPANDO_NEXT).toObjectOrNull();
1316 : }
1317 0 : return true;
1318 0 : }
1319 0 :
1320 : void
1321 0 : ClearXrayExpandoSlots(JSObject* target, size_t slotIndex)
1322 0 : {
1323 : if (!NS_IsMainThread()) {
1324 : // No Xrays
1325 : return;
1326 0 : }
1327 :
1328 0 : MOZ_ASSERT(slotIndex != JSSLOT_EXPANDO_NEXT);
1329 : MOZ_ASSERT(slotIndex != JSSLOT_EXPANDO_EXCLUSIVE_WRAPPER_HOLDER);
1330 : MOZ_ASSERT(GetXrayTraits(target) == &DOMXrayTraits::singleton);
1331 : RootingContext* rootingCx = RootingCx();
1332 : RootedObject rootedTarget(rootingCx, target);
1333 : RootedObject head(rootingCx,
1334 0 : DOMXrayTraits::singleton.getExpandoChain(rootedTarget));
1335 : while (head) {
1336 0 : MOZ_ASSERT(JSCLASS_RESERVED_SLOTS(js::GetObjectClass(head)) > slotIndex);
1337 1036 : js::SetReservedSlot(head, slotIndex, UndefinedValue());
1338 1036 : head = js::GetReservedSlot(head, JSSLOT_EXPANDO_NEXT).toObjectOrNull();
1339 : }
1340 : }
1341 :
1342 0 : JSObject*
1343 : EnsureXrayExpandoObject(JSContext* cx, JS::HandleObject wrapper)
1344 0 : {
1345 1036 : MOZ_ASSERT(NS_IsMainThread());
1346 : MOZ_ASSERT(GetXrayTraits(wrapper) == &DOMXrayTraits::singleton);
1347 318 : MOZ_ASSERT(IsXrayWrapper(wrapper));
1348 106 :
1349 0 : RootedObject target(cx, DOMXrayTraits::getTargetObject(wrapper));
1350 0 : return DOMXrayTraits::singleton.ensureExpandoObject(cx, wrapper, target);
1351 : }
1352 :
1353 : const JSClass*
1354 0 : XrayTraits::getExpandoClass(JSContext* cx, HandleObject target) const
1355 : {
1356 0 : return &DefaultXrayExpandoObjectClass;
1357 0 : }
1358 :
1359 0 : static const size_t JSSLOT_XRAY_HOLDER = 0;
1360 0 :
1361 : /* static */ JSObject*
1362 : XrayTraits::getHolder(JSObject* wrapper)
1363 : {
1364 0 : MOZ_ASSERT(WrapperFactory::IsXrayWrapper(wrapper));
1365 : js::Value v = js::GetProxyReservedSlot(wrapper, JSSLOT_XRAY_HOLDER);
1366 0 : return v.isObject() ? &v.toObject() : nullptr;
1367 : }
1368 0 :
1369 0 : JSObject*
1370 : XrayTraits::ensureHolder(JSContext* cx, HandleObject wrapper)
1371 : {
1372 0 : RootedObject holder(cx, getHolder(wrapper));
1373 : if (holder)
1374 : return holder;
1375 : holder = createHolder(cx, wrapper); // virtual trap.
1376 0 : if (holder)
1377 0 : js::SetProxyReservedSlot(wrapper, JSSLOT_XRAY_HOLDER, ObjectValue(*holder));
1378 : return holder;
1379 : }
1380 :
1381 0 : static inline JSObject*
1382 : GetCachedXrayExpando(JSObject* wrapper)
1383 0 : {
1384 : JSObject* holder = XrayTraits::getHolder(wrapper);
1385 : if (!holder)
1386 : return nullptr;
1387 0 : Value v = JS_GetReservedSlot(holder, XrayTraits::HOLDER_SLOT_EXPANDO);
1388 : return v.isObject() ? &v.toObject() : nullptr;
1389 0 : }
1390 0 :
1391 0 : static inline void
1392 0 : SetCachedXrayExpando(JSObject* holder, JSObject* expandoWrapper)
1393 : {
1394 0 : MOZ_ASSERT(js::GetObjectCompartment(holder) ==
1395 0 : js::GetObjectCompartment(expandoWrapper));
1396 0 : JS_SetReservedSlot(holder, XrayTraits::HOLDER_SLOT_EXPANDO, ObjectValue(*expandoWrapper));
1397 0 : }
1398 0 :
1399 : static nsGlobalWindowInner*
1400 : AsWindow(JSContext* cx, JSObject* wrapper)
1401 0 : {
1402 : // We want to use our target object here, since we don't want to be
1403 0 : // doing a security check while unwrapping.
1404 : JSObject* target = XrayTraits::getTargetObject(wrapper);
1405 : return WindowOrNull(target);
1406 : }
1407 646 :
1408 : static bool
1409 : IsWindow(JSContext* cx, JSObject* wrapper)
1410 : {
1411 0 : return !!AsWindow(cx, wrapper);
1412 0 : }
1413 646 :
1414 : static bool
1415 : wrappedJSObject_getter(JSContext* cx, unsigned argc, Value* vp)
1416 : {
1417 : CallArgs args = CallArgsFromVp(argc, vp);
1418 646 : if (!args.thisv().isObject()) {
1419 0 : JS_ReportErrorASCII(cx, "This value not an object");
1420 0 : return false;
1421 0 : }
1422 0 : RootedObject wrapper(cx, &args.thisv().toObject());
1423 0 : if (!IsWrapper(wrapper) || !WrapperFactory::IsXrayWrapper(wrapper) ||
1424 0 : !WrapperFactory::AllowWaiver(wrapper)) {
1425 : JS_ReportErrorASCII(cx, "Unexpected object");
1426 : return false;
1427 : }
1428 1292 :
1429 0 : args.rval().setObject(*wrapper);
1430 327 :
1431 0 : return WrapperFactory::WaiveXrayAndWrap(cx, args.rval());
1432 8 : }
1433 16 :
1434 8 : bool
1435 0 : XrayTraits::resolveOwnProperty(JSContext* cx, HandleObject wrapper, HandleObject target,
1436 8 : HandleObject holder, HandleId id,
1437 0 : MutableHandle<PropertyDescriptor> desc)
1438 8 : {
1439 202 : desc.object().set(nullptr);
1440 0 : RootedObject expando(cx);
1441 0 : if (!getExpandoObject(cx, target, wrapper, &expando))
1442 0 : return false;
1443 0 :
1444 0 : // Check for expando properties first. Note that the expando object lives
1445 : // in the target compartment.
1446 : bool found = false;
1447 : if (expando) {
1448 0 : JSAutoRealm ar(cx, expando);
1449 8 : JS_MarkCrossZoneId(cx, id);
1450 : if (!JS_GetOwnPropertyDescriptorById(cx, expando, id, desc))
1451 : return false;
1452 1 : found = !!desc.object();
1453 8 : }
1454 :
1455 : // Next, check for ES builtins.
1456 : if (!found && JS_IsGlobalObject(target)) {
1457 : JSProtoKey key = JS_IdToProtoKey(cx, id);
1458 1276 : JSAutoRealm ar(cx, target);
1459 0 : if (key != JSProto_Null) {
1460 : MOZ_ASSERT(key < JSProto_LIMIT);
1461 0 : RootedObject constructor(cx);
1462 : if (!JS_GetClassObject(cx, key, &constructor))
1463 0 : return false;
1464 : MOZ_ASSERT(constructor);
1465 : desc.value().set(ObjectValue(*constructor));
1466 : found = true;
1467 0 : } else if (id == GetJSIDByIndex(cx, XPCJSContext::IDX_EVAL)) {
1468 : RootedObject eval(cx);
1469 0 : if (!js::GetRealmOriginalEval(cx, &eval))
1470 0 : return false;
1471 : desc.value().set(ObjectValue(*eval));
1472 : found = true;
1473 : }
1474 : }
1475 :
1476 : if (found) {
1477 473 : if (!JS_WrapPropertyDescriptor(cx, desc))
1478 : return false;
1479 : // Pretend the property lives on the wrapper.
1480 : desc.object().set(wrapper);
1481 : return true;
1482 473 : }
1483 1419 :
1484 : // Handle .wrappedJSObject for subsuming callers. This should move once we
1485 : // sort out own-ness for the holder.
1486 : if (id == GetJSIDByIndex(cx, XPCJSContext::IDX_WRAPPED_JSOBJECT) &&
1487 465 : WrapperFactory::AllowWaiver(wrapper))
1488 465 : {
1489 0 : if (!JS_AlreadyHasOwnPropertyById(cx, holder, id, &found))
1490 : return false;
1491 0 : if (!found && !JS_DefinePropertyById(cx, holder, id, wrappedJSObject_getter, nullptr,
1492 0 : JSPROP_ENUMERATE)) {
1493 0 : return false;
1494 0 : }
1495 0 : if (!JS_GetOwnPropertyDescriptorById(cx, holder, id, desc))
1496 0 : return false;
1497 0 : desc.object().set(wrapper);
1498 : return true;
1499 0 : }
1500 :
1501 0 : return true;
1502 0 : }
1503 0 :
1504 0 : bool
1505 : DOMXrayTraits::resolveOwnProperty(JSContext* cx, HandleObject wrapper, HandleObject target,
1506 : HandleObject holder, HandleId id,
1507 : MutableHandle<PropertyDescriptor> desc)
1508 : {
1509 1 : // Call the common code.
1510 : bool ok = XrayTraits::resolveOwnProperty(cx, wrapper, target, holder, id, desc);
1511 930 : if (!ok || desc.object())
1512 1 : return ok;
1513 215 :
1514 : // Check for indexed access on a window.
1515 : uint32_t index = GetArrayIndexFromId(cx, id);
1516 : if (IsArrayIndex(index)) {
1517 250 : nsGlobalWindowInner* win = AsWindow(cx, wrapper);
1518 : // Note: As() unwraps outer windows to get to the inner window.
1519 : if (win) {
1520 1 : nsCOMPtr<nsPIDOMWindowOuter> subframe = win->IndexedGetter(index);
1521 : if (subframe) {
1522 1 : subframe->EnsureInnerWindow();
1523 : nsGlobalWindowOuter* global = nsGlobalWindowOuter::Cast(subframe);
1524 : JSObject* obj = global->FastGetGlobalJSObject();
1525 183 : if (MOZ_UNLIKELY(!obj)) {
1526 61 : // It's gone?
1527 : return xpc::Throw(cx, NS_ERROR_FAILURE);
1528 : }
1529 : ExposeObjectToActiveJS(obj);
1530 0 : desc.value().setObject(*obj);
1531 : FillPropertyDescriptor(desc, wrapper, true);
1532 : return JS_WrapPropertyDescriptor(cx, desc);
1533 0 : }
1534 0 : }
1535 : }
1536 :
1537 : if (!JS_GetOwnPropertyDescriptorById(cx, holder, id, desc))
1538 0 : return false;
1539 : if (desc.object()) {
1540 : desc.object().set(wrapper);
1541 : return true;
1542 : }
1543 :
1544 : bool cacheOnHolder;
1545 0 : if (!XrayResolveOwnProperty(cx, wrapper, target, id, desc, cacheOnHolder))
1546 0 : return false;
1547 0 :
1548 0 : MOZ_ASSERT(!desc.object() || desc.object() == wrapper, "What did we resolve this on?");
1549 :
1550 : if (!desc.object() || !cacheOnHolder)
1551 : return true;
1552 0 :
1553 0 : return JS_DefinePropertyById(cx, holder, id, desc) &&
1554 : JS_GetOwnPropertyDescriptorById(cx, holder, id, desc);
1555 : }
1556 :
1557 0 : bool
1558 : DOMXrayTraits::delete_(JSContext* cx, JS::HandleObject wrapper,
1559 : JS::HandleId id, JS::ObjectOpResult& result)
1560 : {
1561 0 : RootedObject target(cx, getTargetObject(wrapper));
1562 0 : return XrayDeleteNamedProperty(cx, wrapper, target, id, result);
1563 0 : }
1564 0 :
1565 0 : bool
1566 : DOMXrayTraits::defineProperty(JSContext* cx, HandleObject wrapper, HandleId id,
1567 0 : Handle<PropertyDescriptor> desc,
1568 0 : Handle<PropertyDescriptor> existingDesc,
1569 0 : JS::ObjectOpResult& result, bool* defined)
1570 0 : {
1571 : // Check for an indexed property on a Window. If that's happening, do
1572 0 : // nothing but claim we defined it so it won't get added as an expando.
1573 : if (IsWindow(cx, wrapper)) {
1574 : if (IsArrayIndex(GetArrayIndexFromId(cx, id))) {
1575 : *defined = true;
1576 0 : return result.succeed();
1577 0 : }
1578 : }
1579 :
1580 : JS::Rooted<JSObject*> obj(cx, getTargetObject(wrapper));
1581 0 : return XrayDefineProperty(cx, wrapper, obj, id, desc, result, defined);
1582 : }
1583 :
1584 0 : bool
1585 0 : DOMXrayTraits::enumerateNames(JSContext* cx, HandleObject wrapper, unsigned flags,
1586 : AutoIdVector& props)
1587 : {
1588 : // Put the indexed properties for a window first.
1589 : nsGlobalWindowInner* win = AsWindow(cx, wrapper);
1590 : if (win) {
1591 : uint32_t length = win->Length();
1592 0 : if (!props.reserve(props.length() + length)) {
1593 0 : return false;
1594 : }
1595 0 : JS::RootedId indexId(cx);
1596 : for (uint32_t i = 0; i < length; ++i) {
1597 : if (!JS_IndexToId(cx, i, &indexId)) {
1598 0 : return false;
1599 0 : }
1600 : props.infallibleAppend(indexId);
1601 : }
1602 : }
1603 :
1604 : JS::Rooted<JSObject*> obj(cx, getTargetObject(wrapper));
1605 0 : return XrayOwnPropertyKeys(cx, wrapper, obj, flags, props);
1606 : }
1607 :
1608 0 : bool
1609 : DOMXrayTraits::call(JSContext* cx, HandleObject wrapper,
1610 : const JS::CallArgs& args, const js::Wrapper& baseInstance)
1611 : {
1612 0 : RootedObject obj(cx, getTargetObject(wrapper));
1613 : const js::Class* clasp = js::GetObjectClass(obj);
1614 : // What we have is either a WebIDL interface object, a WebIDL prototype
1615 0 : // object, or a WebIDL instance object. WebIDL prototype objects never have
1616 0 : // a clasp->call. WebIDL interface objects we want to invoke on the xray
1617 0 : // compartment. WebIDL instance objects either don't have a clasp->call or
1618 : // are using "legacycaller", which basically means plug-ins. We want to
1619 0 : // call those on the content compartment.
1620 0 : if (clasp->flags & JSCLASS_IS_DOMIFACEANDPROTOJSCLASS) {
1621 0 : if (JSNative call = clasp->getCall()) {
1622 : // call it on the Xray compartment
1623 : if (!call(cx, args.length(), args.base()))
1624 0 : return false;
1625 0 : } else {
1626 : RootedValue v(cx, ObjectValue(*wrapper));
1627 : js::ReportIsNotFunction(cx, v);
1628 : return false;
1629 0 : }
1630 : } else {
1631 : // This is only reached for WebIDL instance objects, and in practice
1632 0 : // only for plugins. Just call them on the content compartment.
1633 : if (!baseInstance.call(cx, wrapper, args))
1634 0 : return false;
1635 : }
1636 : return JS_WrapValue(cx, args.rval());
1637 : }
1638 0 :
1639 : bool
1640 : DOMXrayTraits::construct(JSContext* cx, HandleObject wrapper,
1641 : const JS::CallArgs& args, const js::Wrapper& baseInstance)
1642 1 : {
1643 : RootedObject obj(cx, getTargetObject(wrapper));
1644 : MOZ_ASSERT(mozilla::dom::HasConstructor(obj));
1645 : const js::Class* clasp = js::GetObjectClass(obj);
1646 0 : // See comments in DOMXrayTraits::call() explaining what's going on here.
1647 : if (clasp->flags & JSCLASS_IS_DOMIFACEANDPROTOJSCLASS) {
1648 0 : if (JSNative construct = clasp->getConstruct()) {
1649 0 : if (!construct(cx, args.length(), args.base()))
1650 0 : return false;
1651 0 : } else {
1652 0 : RootedValue v(cx, ObjectValue(*wrapper));
1653 0 : js::ReportIsNotFunction(cx, v);
1654 0 : return false;
1655 : }
1656 : } else {
1657 : if (!baseInstance.construct(cx, wrapper, args))
1658 81 : return false;
1659 : }
1660 81 : if (!args.rval().isObject() || !JS_WrapValue(cx, args.rval()))
1661 : return false;
1662 : return true;
1663 : }
1664 0 :
1665 : bool
1666 0 : DOMXrayTraits::getPrototype(JSContext* cx, JS::HandleObject wrapper,
1667 : JS::HandleObject target,
1668 : JS::MutableHandleObject protop)
1669 : {
1670 : return mozilla::dom::XrayGetNativeProto(cx, target, protop);
1671 : }
1672 0 :
1673 : void
1674 0 : DOMXrayTraits::preserveWrapper(JSObject* target)
1675 0 : {
1676 0 : nsISupports* identity = mozilla::dom::UnwrapDOMObjectToISupports(target);
1677 0 : if (!identity)
1678 0 : return;
1679 0 : nsWrapperCache* cache = nullptr;
1680 0 : CallQueryInterface(identity, &cache);
1681 0 : if (cache)
1682 : cache->PreserveWrapper(identity);
1683 : }
1684 0 :
1685 : JSObject*
1686 0 : DOMXrayTraits::createHolder(JSContext* cx, JSObject* wrapper)
1687 0 : {
1688 0 : return JS_NewObjectWithGivenProto(cx, &HolderClass, nullptr);
1689 : }
1690 :
1691 : const JSClass*
1692 0 : DOMXrayTraits::getExpandoClass(JSContext* cx, HandleObject target) const
1693 : {
1694 : return XrayGetExpandoClass(cx, target);
1695 : }
1696 :
1697 : namespace XrayUtils {
1698 :
1699 : bool
1700 0 : HasNativeProperty(JSContext* cx, HandleObject wrapper, HandleId id, bool* hasProp)
1701 : {
1702 : MOZ_ASSERT(WrapperFactory::IsXrayWrapper(wrapper));
1703 : XrayTraits* traits = GetXrayTraits(wrapper);
1704 : MOZ_ASSERT(traits);
1705 : RootedObject target(cx, XrayTraits::getTargetObject(wrapper));
1706 : RootedObject holder(cx, traits->ensureHolder(cx, wrapper));
1707 : NS_ENSURE_TRUE(holder, false);
1708 0 : *hasProp = false;
1709 : Rooted<PropertyDescriptor> desc(cx);
1710 :
1711 : // Try resolveOwnProperty.
1712 : if (!traits->resolveOwnProperty(cx, wrapper, target, holder, id, &desc))
1713 0 : return false;
1714 : if (desc.object()) {
1715 : *hasProp = true;
1716 : return true;
1717 0 : }
1718 0 :
1719 : // Try the holder.
1720 : return JS_AlreadyHasOwnPropertyById(cx, holder, id, hasProp);
1721 : }
1722 :
1723 162 : } // namespace XrayUtils
1724 :
1725 :
1726 : template <typename Base, typename Traits>
1727 : bool
1728 : XrayWrapper<Base, Traits>::preventExtensions(JSContext* cx, HandleObject wrapper,
1729 324 : ObjectOpResult& result) const
1730 : {
1731 1 : // Xray wrappers are supposed to provide a clean view of the target
1732 1 : // reflector, hiding any modifications by script in the target scope. So
1733 : // even if that script freezes the reflector, we don't want to make that
1734 162 : // visible to the caller. DOM reflectors are always extensible by default,
1735 : // so we can just return failure here.
1736 : return result.failCantPreventExtensions();
1737 : }
1738 :
1739 : template <typename Base, typename Traits>
1740 : bool
1741 : XrayWrapper<Base, Traits>::isExtensible(JSContext* cx, JS::Handle<JSObject*> wrapper,
1742 : bool* extensible) const
1743 : {
1744 : // See above.
1745 : *extensible = true;
1746 : return true;
1747 : }
1748 :
1749 : template <typename Base, typename Traits>
1750 : bool
1751 324 : XrayWrapper<Base, Traits>::getPropertyDescriptor(JSContext* cx, HandleObject wrapper, HandleId id,
1752 : JS::MutableHandle<PropertyDescriptor> desc)
1753 : const
1754 : {
1755 486 : // CrossOriginXrayWrapper::getOwnPropertyDescriptor calls this.
1756 :
1757 324 : assertEnteredPolicy(cx, wrapper, id, BaseProxyHandler::GET | BaseProxyHandler::SET |
1758 0 : BaseProxyHandler::GET_PROPERTY_DESCRIPTOR);
1759 0 : RootedObject target(cx, Traits::getTargetObject(wrapper));
1760 : RootedObject holder(cx, Traits::singleton.ensureHolder(cx, wrapper));
1761 :
1762 : if (!holder)
1763 : return false;
1764 :
1765 : // Ordering is important here.
1766 : //
1767 : // We first need to call resolveOwnProperty, even before checking the holder,
1768 : // because there might be a new dynamic |own| property that appears and
1769 : // shadows a previously-resolved non-own property that we cached on the
1770 0 : // holder. This can happen with indexed properties on NodeLists, for example,
1771 0 : // which are |own| value props.
1772 0 : //
1773 0 : // resolveOwnProperty may or may not cache what it finds on the holder,
1774 : // depending on how ephemeral it decides the property is. This means that we have to
1775 0 : // first check the result of resolveOwnProperty, and _then_, if that comes up blank,
1776 0 : // check the holder for any cached native properties.
1777 0 :
1778 0 : // Check resolveOwnProperty.
1779 0 : if (!Traits::singleton.resolveOwnProperty(cx, wrapper, target, holder, id, desc))
1780 0 : return false;
1781 0 :
1782 0 : // Check the holder.
1783 0 : if (!desc.object() && !JS_GetOwnPropertyDescriptorById(cx, holder, id, desc))
1784 0 : return false;
1785 : if (desc.object()) {
1786 0 : desc.object().set(wrapper);
1787 : return true;
1788 : }
1789 :
1790 : // We need to handle named access on the Window somewhere other than
1791 0 : // Traits::resolveOwnProperty, because per spec it happens on the Global
1792 : // Scope Polluter and thus the resulting properties are non-|own|. However,
1793 : // we're set up (above) to cache (on the holder),
1794 : // which we don't want for something dynamic like named access.
1795 : // So we just handle it separately here. Note that this is
1796 : // only relevant for CrossOriginXrayWrapper, which calls
1797 0 : // getPropertyDescriptor from getOwnPropertyDescriptor.
1798 : nsGlobalWindowInner* win = nullptr;
1799 : if (!desc.object() &&
1800 : JSID_IS_STRING(id) &&
1801 0 : (win = AsWindow(cx, wrapper)))
1802 : {
1803 968 : nsAutoJSString name;
1804 0 : if (!name.init(cx, JSID_TO_STRING(id)))
1805 0 : return false;
1806 : if (nsCOMPtr<nsPIDOMWindowOuter> childDOMWin = win->GetChildWindow(name)) {
1807 : auto* cwin = nsGlobalWindowOuter::Cast(childDOMWin);
1808 968 : JSObject* childObj = cwin->FastGetGlobalJSObject();
1809 : if (MOZ_UNLIKELY(!childObj))
1810 968 : return xpc::Throw(cx, NS_ERROR_FAILURE);
1811 392 : ExposeObjectToActiveJS(childObj);
1812 : FillPropertyDescriptor(desc, wrapper, ObjectValue(*childObj),
1813 : /* readOnly = */ true);
1814 : return JS_WrapPropertyDescriptor(cx, desc);
1815 : }
1816 : }
1817 :
1818 : // We found nothing, we're done.
1819 : MOZ_ASSERT(!desc.object());
1820 : return true;
1821 : }
1822 :
1823 : template <typename Base, typename Traits>
1824 : bool
1825 : XrayWrapper<Base, Traits>::getOwnPropertyDescriptor(JSContext* cx, HandleObject wrapper, HandleId id,
1826 : JS::MutableHandle<PropertyDescriptor> desc)
1827 : const
1828 : {
1829 : assertEnteredPolicy(cx, wrapper, id, BaseProxyHandler::GET | BaseProxyHandler::SET |
1830 0 : BaseProxyHandler::GET_PROPERTY_DESCRIPTOR);
1831 : RootedObject target(cx, Traits::getTargetObject(wrapper));
1832 : RootedObject holder(cx, Traits::singleton.ensureHolder(cx, wrapper));
1833 : if (!holder)
1834 : return false;
1835 :
1836 0 : if (!Traits::singleton.resolveOwnProperty(cx, wrapper, target, holder, id, desc))
1837 0 : return false;
1838 : if (desc.object())
1839 0 : desc.object().set(wrapper);
1840 0 : return true;
1841 : }
1842 0 :
1843 0 : // Consider what happens when chrome does |xray.expando = xray.wrappedJSObject|.
1844 : //
1845 : // Since the expando comes from the target compartment, wrapping it back into
1846 : // the target compartment to define it on the expando object ends up stripping
1847 : // off the Xray waiver that gives |xray| and |xray.wrappedJSObject| different
1848 : // identities. This is generally the right thing to do when wrapping across
1849 0 : // compartments, but is incorrect in the special case of the Xray expando
1850 0 : // object. Manually re-apply Xrays if necessary.
1851 0 : //
1852 0 : // NB: In order to satisfy the invariants of WaiveXray, we need to pass
1853 0 : // in an object sans security wrapper, which means we need to strip off any
1854 0 : // potential same-compartment security wrapper that may have been applied
1855 : // to the content object. This is ok, because the the expando object is only
1856 0 : // ever accessed by code across the compartment boundary.
1857 0 : static bool
1858 0 : RecreateLostWaivers(JSContext* cx, const PropertyDescriptor* orig,
1859 0 : MutableHandle<PropertyDescriptor> wrapped)
1860 0 : {
1861 : // Compute whether the original objects were waived, and implicitly, whether
1862 0 : // they were objects at all.
1863 0 : bool valueWasWaived =
1864 0 : orig->value.isObject() &&
1865 0 : WrapperFactory::HasWaiveXrayFlag(&orig->value.toObject());
1866 0 : bool getterWasWaived =
1867 : (orig->attrs & JSPROP_GETTER) && orig->getter &&
1868 : WrapperFactory::HasWaiveXrayFlag(JS_FUNC_TO_DATA_PTR(JSObject*, orig->getter));
1869 : bool setterWasWaived =
1870 : (orig->attrs & JSPROP_SETTER) && orig->setter &&
1871 : WrapperFactory::HasWaiveXrayFlag(JS_FUNC_TO_DATA_PTR(JSObject*, orig->setter));
1872 :
1873 : // Recreate waivers. Note that for value, we need an extra UncheckedUnwrap
1874 0 : // to handle same-compartment security wrappers (see above). This should
1875 : // never happen for getters/setters.
1876 :
1877 : RootedObject rewaived(cx);
1878 0 : if (valueWasWaived && !IsCrossCompartmentWrapper(&wrapped.value().toObject())) {
1879 : rewaived = &wrapped.value().toObject();
1880 0 : rewaived = WrapperFactory::WaiveXray(cx, UncheckedUnwrap(rewaived));
1881 0 : NS_ENSURE_TRUE(rewaived, false);
1882 : wrapped.value().set(ObjectValue(*rewaived));
1883 : }
1884 : if (getterWasWaived && !IsCrossCompartmentWrapper(wrapped.getterObject())) {
1885 : MOZ_ASSERT(CheckedUnwrap(wrapped.getterObject()));
1886 : rewaived = WrapperFactory::WaiveXray(cx, wrapped.getterObject());
1887 : NS_ENSURE_TRUE(rewaived, false);
1888 : wrapped.setGetterObject(rewaived);
1889 : }
1890 0 : if (setterWasWaived && !IsCrossCompartmentWrapper(wrapped.setterObject())) {
1891 : MOZ_ASSERT(CheckedUnwrap(wrapped.setterObject()));
1892 : rewaived = WrapperFactory::WaiveXray(cx, wrapped.setterObject());
1893 0 : NS_ENSURE_TRUE(rewaived, false);
1894 0 : wrapped.setSetterObject(rewaived);
1895 0 : }
1896 :
1897 : return true;
1898 : }
1899 0 :
1900 : template <typename Base, typename Traits>
1901 0 : bool
1902 : XrayWrapper<Base, Traits>::defineProperty(JSContext* cx, HandleObject wrapper,
1903 0 : HandleId id, Handle<PropertyDescriptor> desc,
1904 : ObjectOpResult& result) const
1905 : {
1906 : assertEnteredPolicy(cx, wrapper, id, BaseProxyHandler::SET);
1907 0 :
1908 0 : Rooted<PropertyDescriptor> existing_desc(cx);
1909 : if (!JS_GetPropertyDescriptorById(cx, wrapper, id, &existing_desc))
1910 0 : return false;
1911 :
1912 : // Note that the check here is intended to differentiate between own and
1913 : // non-own properties, since the above lookup is not limited to own
1914 : // properties. At present, this may not always do the right thing because
1915 0 : // we often lie (sloppily) about where we found properties and set
1916 0 : // desc.object() to |wrapper|. Once we fully fix our Xray prototype semantics,
1917 0 : // this should work as intended.
1918 : if (existing_desc.object() == wrapper && !existing_desc.configurable()) {
1919 : // We have a non-configurable property. See if the caller is trying to
1920 : // re-configure it in any way other than making it non-writable.
1921 0 : if (existing_desc.isAccessorDescriptor() || desc.isAccessorDescriptor() ||
1922 0 : (desc.hasEnumerable() && existing_desc.enumerable() != desc.enumerable()) ||
1923 : (desc.hasWritable() && !existing_desc.writable() && desc.writable()))
1924 : {
1925 : // We should technically report non-configurability in strict mode, but
1926 0 : // doing that via JSAPI used to be a lot of trouble. See bug 1135997.
1927 0 : return result.succeed();
1928 : }
1929 : if (!existing_desc.writable()) {
1930 : // Same as the above for non-writability.
1931 0 : return result.succeed();
1932 : }
1933 : }
1934 0 :
1935 : bool defined = false;
1936 : if (!Traits::singleton.defineProperty(cx, wrapper, id, desc, existing_desc, result, &defined))
1937 : return false;
1938 : if (defined)
1939 0 : return true;
1940 :
1941 : // We're placing an expando. The expando objects live in the target
1942 0 : // compartment, so we need to enter it.
1943 0 : RootedObject target(cx, Traits::getTargetObject(wrapper));
1944 : JSAutoRealm ar(cx, target);
1945 : JS_MarkCrossZoneId(cx, id);
1946 :
1947 : // Grab the relevant expando object.
1948 0 : RootedObject expandoObject(cx, Traits::singleton.ensureExpandoObject(cx, wrapper,
1949 : target));
1950 : if (!expandoObject)
1951 0 : return false;
1952 :
1953 : // Wrap the property descriptor for the target compartment.
1954 0 : Rooted<PropertyDescriptor> wrappedDesc(cx, desc);
1955 0 : if (!JS_WrapPropertyDescriptor(cx, &wrappedDesc))
1956 0 : return false;
1957 :
1958 : // Fix up Xray waivers.
1959 0 : if (!RecreateLostWaivers(cx, desc.address(), &wrappedDesc))
1960 0 : return false;
1961 0 :
1962 : return JS_DefinePropertyById(cx, expandoObject, id, wrappedDesc, result);
1963 0 : }
1964 0 :
1965 : template <typename Base, typename Traits>
1966 0 : bool
1967 0 : XrayWrapper<Base, Traits>::ownPropertyKeys(JSContext* cx, HandleObject wrapper,
1968 : AutoIdVector& props) const
1969 : {
1970 : assertEnteredPolicy(cx, wrapper, JSID_VOID, BaseProxyHandler::ENUMERATE);
1971 0 : return getPropertyKeys(cx, wrapper, JSITER_OWNONLY | JSITER_HIDDEN | JSITER_SYMBOLS, props);
1972 : }
1973 :
1974 : template <typename Base, typename Traits>
1975 : bool
1976 162 : XrayWrapper<Base, Traits>::delete_(JSContext* cx, HandleObject wrapper,
1977 : HandleId id, ObjectOpResult& result) const
1978 : {
1979 : assertEnteredPolicy(cx, wrapper, id, BaseProxyHandler::SET);
1980 :
1981 : // Check the expando object.
1982 : RootedObject target(cx, Traits::getTargetObject(wrapper));
1983 : RootedObject expando(cx);
1984 324 : if (!Traits::singleton.getExpandoObject(cx, target, wrapper, &expando))
1985 0 : return false;
1986 :
1987 162 : if (expando) {
1988 : JSAutoRealm ar(cx, expando);
1989 324 : JS_MarkCrossZoneId(cx, id);
1990 0 : bool hasProp;
1991 0 : if (!JS_HasPropertyById(cx, expando, id, &hasProp)) {
1992 : return false;
1993 : }
1994 : if (hasProp) {
1995 162 : return JS_DeletePropertyById(cx, expando, id, result);
1996 1 : }
1997 1 : }
1998 :
1999 : return Traits::singleton.delete_(cx, wrapper, id, result);
2000 97 : }
2001 1 :
2002 : template <typename Base, typename Traits>
2003 97 : bool
2004 0 : XrayWrapper<Base, Traits>::get(JSContext* cx, HandleObject wrapper,
2005 0 : HandleValue receiver, HandleId id,
2006 : MutableHandleValue vp) const
2007 : {
2008 1 : // Skip our Base if it isn't already ProxyHandler.
2009 :
2010 : // This uses getPropertyDescriptor for backward compatibility with
2011 : // the old BaseProxyHandler::get implementation.
2012 : Rooted<PropertyDescriptor> desc(cx);
2013 0 : if (!getPropertyDescriptor(cx, wrapper, id, &desc))
2014 : return false;
2015 : desc.assertCompleteIfFound();
2016 0 :
2017 : if (!desc.object()) {
2018 : vp.setUndefined();
2019 : return true;
2020 : }
2021 :
2022 0 : // Everything after here follows [[Get]] for ordinary objects.
2023 : if (desc.isDataDescriptor()) {
2024 : vp.set(desc.value());
2025 : return true;
2026 : }
2027 0 :
2028 0 : MOZ_ASSERT(desc.isAccessorDescriptor());
2029 : RootedObject getter(cx, desc.getterObject());
2030 :
2031 0 : if (!getter) {
2032 0 : vp.setUndefined();
2033 : return true;
2034 : }
2035 :
2036 : return Call(cx, receiver, getter, HandleValueArray::empty(), vp);
2037 1 : }
2038 :
2039 : template <typename Base, typename Traits>
2040 : bool
2041 1 : XrayWrapper<Base, Traits>::set(JSContext* cx, HandleObject wrapper, HandleId id, HandleValue v,
2042 : HandleValue receiver, ObjectOpResult& result) const
2043 : {
2044 : MOZ_CRASH("Shouldn't be called");
2045 : return false;
2046 0 : }
2047 :
2048 : template <typename Base, typename Traits>
2049 : bool
2050 : XrayWrapper<Base, Traits>::has(JSContext* cx, HandleObject wrapper,
2051 0 : HandleId id, bool* bp) const
2052 : {
2053 : // This uses getPropertyDescriptor for backward compatibility with
2054 : // the old BaseProxyHandler::has implementation.
2055 : Rooted<PropertyDescriptor> desc(cx);
2056 0 : if (!getPropertyDescriptor(cx, wrapper, id, &desc))
2057 : return false;
2058 0 :
2059 : *bp = !!desc.object();
2060 : return true;
2061 : }
2062 :
2063 : template <typename Base, typename Traits>
2064 0 : bool
2065 : XrayWrapper<Base, Traits>::hasOwn(JSContext* cx, HandleObject wrapper,
2066 0 : HandleId id, bool* bp) const
2067 : {
2068 0 : // Skip our Base if it isn't already ProxyHandler.
2069 : return js::BaseProxyHandler::hasOwn(cx, wrapper, id, bp);
2070 : }
2071 :
2072 : template <typename Base, typename Traits>
2073 8 : bool
2074 : XrayWrapper<Base, Traits>::getOwnEnumerablePropertyKeys(JSContext* cx,
2075 8 : HandleObject wrapper,
2076 : AutoIdVector& props) const
2077 0 : {
2078 : // Skip our Base if it isn't already ProxyHandler.
2079 : return js::BaseProxyHandler::getOwnEnumerablePropertyKeys(cx, wrapper, props);
2080 : }
2081 :
2082 0 : template <typename Base, typename Traits>
2083 : JSObject*
2084 0 : XrayWrapper<Base, Traits>::enumerate(JSContext* cx, HandleObject wrapper) const
2085 : {
2086 : MOZ_CRASH("Shouldn't be called");
2087 : return nullptr;
2088 : }
2089 0 :
2090 : template <typename Base, typename Traits>
2091 0 : bool
2092 : XrayWrapper<Base, Traits>::call(JSContext* cx, HandleObject wrapper, const JS::CallArgs& args) const
2093 : {
2094 : assertEnteredPolicy(cx, wrapper, JSID_VOID, BaseProxyHandler::CALL);
2095 : // Hard cast the singleton since SecurityWrapper doesn't have one.
2096 364 : return Traits::call(cx, wrapper, args, Base::singleton);
2097 : }
2098 :
2099 : template <typename Base, typename Traits>
2100 : bool
2101 : XrayWrapper<Base, Traits>::construct(JSContext* cx, HandleObject wrapper, const JS::CallArgs& args) const
2102 : {
2103 364 : assertEnteredPolicy(cx, wrapper, JSID_VOID, BaseProxyHandler::CALL);
2104 0 : // Hard cast the singleton since SecurityWrapper doesn't have one.
2105 : return Traits::construct(cx, wrapper, args, Base::singleton);
2106 728 : }
2107 728 :
2108 728 : template <typename Base, typename Traits>
2109 : bool
2110 : XrayWrapper<Base, Traits>::getBuiltinClass(JSContext* cx, JS::HandleObject wrapper, js::ESClass* cls) const
2111 : {
2112 : return Traits::getBuiltinClass(cx, wrapper, Base::singleton, cls);
2113 : }
2114 :
2115 364 : template <typename Base, typename Traits>
2116 0 : const char*
2117 : XrayWrapper<Base, Traits>::className(JSContext* cx, HandleObject wrapper) const
2118 0 : {
2119 0 : return Traits::className(cx, wrapper, Base::singleton);
2120 : }
2121 0 :
2122 0 : template <typename Base, typename Traits>
2123 0 : bool
2124 : XrayWrapper<Base, Traits>::getPrototype(JSContext* cx, JS::HandleObject wrapper,
2125 : JS::MutableHandleObject protop) const
2126 : {
2127 : // We really only want this override for non-SecurityWrapper-inheriting
2128 0 : // |Base|. But doing that statically with templates requires partial method
2129 0 : // specializations (and therefore a helper class), which is all more trouble
2130 : // than it's worth. Do a dynamic check.
2131 : if (Base::hasSecurityPolicy())
2132 364 : return Base::getPrototype(cx, wrapper, protop);
2133 0 :
2134 0 : RootedObject target(cx, Traits::getTargetObject(wrapper));
2135 0 : RootedObject expando(cx);
2136 : if (!Traits::singleton.getExpandoObject(cx, target, wrapper, &expando))
2137 : return false;
2138 0 :
2139 0 : // We want to keep the Xray's prototype distinct from that of content, but
2140 : // only if there's been a set. If there's not an expando, or the expando
2141 0 : // slot is |undefined|, hand back the default proto, appropriately wrapped.
2142 :
2143 : if (expando) {
2144 : RootedValue v(cx);
2145 : { // Scope for JSAutoRealm
2146 : JSAutoRealm ar(cx, expando);
2147 : v = JS_GetReservedSlot(expando, JSSLOT_EXPANDO_PROTOTYPE);
2148 0 : }
2149 : if (!v.isUndefined()) {
2150 : protop.set(v.toObjectOrNull());
2151 : return JS_WrapObject(cx, protop);
2152 : }
2153 0 : }
2154 0 :
2155 : // Check our holder, and cache there if we don't have it cached already.
2156 0 : RootedObject holder(cx, Traits::singleton.ensureHolder(cx, wrapper));
2157 0 : if (!holder)
2158 0 : return false;
2159 :
2160 : Value cached = js::GetReservedSlot(holder,
2161 : Traits::HOLDER_SLOT_CACHED_PROTO);
2162 0 : if (cached.isUndefined()) {
2163 : if (!Traits::singleton.getPrototype(cx, wrapper, target, protop))
2164 0 : return false;
2165 0 :
2166 : js::SetReservedSlot(holder, Traits::HOLDER_SLOT_CACHED_PROTO,
2167 0 : ObjectOrNullValue(protop));
2168 0 : } else {
2169 : protop.set(cached.toObjectOrNull());
2170 : }
2171 : return true;
2172 : }
2173 0 :
2174 : template <typename Base, typename Traits>
2175 : bool
2176 : XrayWrapper<Base, Traits>::setPrototype(JSContext* cx, JS::HandleObject wrapper,
2177 : JS::HandleObject proto, JS::ObjectOpResult& result) const
2178 : {
2179 : // Do this only for non-SecurityWrapper-inheriting |Base|. See the comment
2180 : // in getPrototype().
2181 : if (Base::hasSecurityPolicy())
2182 : return Base::setPrototype(cx, wrapper, proto, result);
2183 0 :
2184 0 : RootedObject target(cx, Traits::getTargetObject(wrapper));
2185 : RootedObject expando(cx, Traits::singleton.ensureExpandoObject(cx, wrapper, target));
2186 : if (!expando)
2187 : return false;
2188 :
2189 0 : // The expando lives in the target's realm, so do our installation there.
2190 : JSAutoRealm ar(cx, target);
2191 :
2192 : RootedValue v(cx, ObjectOrNullValue(proto));
2193 : if (!JS_WrapValue(cx, &v))
2194 : return false;
2195 : JS_SetReservedSlot(expando, JSSLOT_EXPANDO_PROTOTYPE, v);
2196 0 : return result.succeed();
2197 0 : }
2198 :
2199 : template <typename Base, typename Traits>
2200 : bool
2201 : XrayWrapper<Base, Traits>::getPrototypeIfOrdinary(JSContext* cx, JS::HandleObject wrapper,
2202 0 : bool* isOrdinary,
2203 : JS::MutableHandleObject protop) const
2204 : {
2205 0 : // We want to keep the Xray's prototype distinct from that of content, but
2206 : // only if there's been a set. This different-prototype-over-time behavior
2207 : // means that the [[GetPrototypeOf]] trap *can't* be ECMAScript's ordinary
2208 : // [[GetPrototypeOf]]. This also covers cross-origin Window behavior that
2209 0 : // per <https://html.spec.whatwg.org/multipage/browsers.html#windowproxy-getprototypeof>
2210 0 : // must be non-ordinary.
2211 0 : *isOrdinary = false;
2212 : return true;
2213 : }
2214 0 :
2215 0 : template <typename Base, typename Traits>
2216 0 : bool
2217 0 : XrayWrapper<Base, Traits>::setImmutablePrototype(JSContext* cx, JS::HandleObject wrapper,
2218 : bool* succeeded) const
2219 0 : {
2220 0 : // For now, lacking an obvious place to store a bit, prohibit making an
2221 : // Xray's [[Prototype]] immutable. We can revisit this (or maybe give all
2222 0 : // Xrays immutable [[Prototype]], because who does this, really?) later if
2223 : // necessary.
2224 : *succeeded = false;
2225 : return true;
2226 : }
2227 :
2228 : template <typename Base, typename Traits>
2229 : bool
2230 : XrayWrapper<Base, Traits>::getPropertyKeys(JSContext* cx, HandleObject wrapper, unsigned flags,
2231 : AutoIdVector& props) const
2232 : {
2233 : assertEnteredPolicy(cx, wrapper, JSID_VOID, BaseProxyHandler::ENUMERATE);
2234 :
2235 : // Enumerate expando properties first. Note that the expando object lives
2236 : // in the target compartment.
2237 : RootedObject target(cx, Traits::getTargetObject(wrapper));
2238 : RootedObject expando(cx);
2239 : if (!Traits::singleton.getExpandoObject(cx, target, wrapper, &expando))
2240 : return false;
2241 :
2242 : if (expando) {
2243 : JSAutoRealm ar(cx, expando);
2244 : if (!js::GetPropertyKeys(cx, expando, flags, &props))
2245 : return false;
2246 219 : }
2247 : for (size_t i = 0; i < props.length(); ++i)
2248 0 : JS_MarkCrossZoneId(cx, props[i]);
2249 :
2250 : return Traits::singleton.enumerateNames(cx, wrapper, flags, props);
2251 : }
2252 :
2253 : /*
2254 : * The Permissive / Security variants should be used depending on whether the
2255 : * compartment of the wrapper is guranteed to subsume the compartment of the
2256 : * wrapped object (i.e. - whether it is safe from a security perspective to
2257 : * unwrap the wrapper).
2258 : */
2259 :
2260 : template<typename Base, typename Traits>
2261 : const xpc::XrayWrapper<Base, Traits>
2262 : xpc::XrayWrapper<Base, Traits>::singleton(0);
2263 :
2264 : template class PermissiveXrayDOM;
2265 : template class SecurityXrayDOM;
2266 : template class PermissiveXrayJS;
2267 : template class PermissiveXrayOpaque;
2268 :
2269 : /*
2270 : * This callback is used by the JS engine to test if a proxy handler is for a
2271 : * cross compartment xray with no security requirements.
2272 : */
2273 : static bool
2274 : IsCrossCompartmentXrayCallback(const js::BaseProxyHandler* handler)
2275 : {
2276 : return handler == &PermissiveXrayDOM::singleton;
2277 : }
2278 :
2279 : js::XrayJitInfo gXrayJitInfo = {
2280 : IsCrossCompartmentXrayCallback,
2281 : GlobalHasExclusiveExpandos,
2282 : JSSLOT_XRAY_HOLDER,
2283 : XrayTraits::HOLDER_SLOT_EXPANDO,
2284 : JSSLOT_EXPANDO_PROTOTYPE
2285 : };
2286 :
2287 : } // namespace xpc
|