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