LCOV - code coverage report
Current view: top level - dom/base - WindowNamedPropertiesHandler.cpp (source / functions) Hit Total Coverage
Test: output.info Lines: 51 132 38.6 %
Date: 2017-07-14 16:53:18 Functions: 5 10 50.0 %
Legend: Lines: hit not hit

          Line data    Source code
       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 "WindowNamedPropertiesHandler.h"
       8             : #include "mozilla/dom/EventTargetBinding.h"
       9             : #include "mozilla/dom/WindowBinding.h"
      10             : #include "mozilla/Preferences.h"
      11             : #include "nsContentUtils.h"
      12             : #include "nsDOMClassInfo.h"
      13             : #include "nsDOMWindowList.h"
      14             : #include "nsGlobalWindow.h"
      15             : #include "nsHTMLDocument.h"
      16             : #include "nsJSUtils.h"
      17             : #include "xpcprivate.h"
      18             : 
      19             : namespace mozilla {
      20             : namespace dom {
      21             : 
      22             : static bool
      23           0 : ShouldExposeChildWindow(nsString& aNameBeingResolved, nsPIDOMWindowOuter* aChild)
      24             : {
      25           0 :   Element* e = aChild->GetFrameElementInternal();
      26           0 :   if (e && e->IsInShadowTree()) {
      27           0 :     return false;
      28             :   }
      29             : 
      30             :   // If we're same-origin with the child, go ahead and expose it.
      31           0 :   nsCOMPtr<nsIScriptObjectPrincipal> sop = do_QueryInterface(aChild);
      32           0 :   NS_ENSURE_TRUE(sop, false);
      33           0 :   if (nsContentUtils::SubjectPrincipal()->Equals(sop->GetPrincipal())) {
      34           0 :     return true;
      35             :   }
      36             : 
      37             :   // If we're not same-origin, expose it _only_ if the name of the browsing
      38             :   // context matches the 'name' attribute of the frame element in the parent.
      39             :   // The motivations behind this heuristic are worth explaining here.
      40             :   //
      41             :   // Historically, all UAs supported global named access to any child browsing
      42             :   // context (that is to say, window.dolske returns a child frame where either
      43             :   // the "name" attribute on the frame element was set to "dolske", or where
      44             :   // the child explicitly set window.name = "dolske").
      45             :   //
      46             :   // This is problematic because it allows possibly-malicious and unrelated
      47             :   // cross-origin subframes to pollute the global namespace of their parent in
      48             :   // unpredictable ways (see bug 860494). This is also problematic for browser
      49             :   // engines like Servo that want to run cross-origin script on different
      50             :   // threads.
      51             :   //
      52             :   // The naive solution here would be to filter out any cross-origin subframes
      53             :   // obtained when doing named lookup in global scope. But that is unlikely to
      54             :   // be web-compatible, since it will break named access for consumers that do
      55             :   // <iframe name="dolske" src="http://cross-origin.com/sadtrombone.html"> and
      56             :   // expect to be able to access the cross-origin subframe via named lookup on
      57             :   // the global.
      58             :   //
      59             :   // The optimal behavior would be to do the following:
      60             :   // (a) Look for any child browsing context with name="dolske".
      61             :   // (b) If the result is cross-origin, null it out.
      62             :   // (c) If we have null, look for a frame element whose 'name' attribute is
      63             :   //     "dolske".
      64             :   //
      65             :   // Unfortunately, (c) would require some engineering effort to be performant
      66             :   // in Gecko, and probably in other UAs as well. So we go with a simpler
      67             :   // approximation of the above. This approximation will only break sites that
      68             :   // rely on their cross-origin subframes setting window.name to a known value,
      69             :   // which is unlikely to be very common. And while it does introduce a
      70             :   // dependency on cross-origin state when doing global lookups, it doesn't
      71             :   // allow the child to arbitrarily pollute the parent namespace, and requires
      72             :   // cross-origin communication only in a limited set of cases that can be
      73             :   // computed independently by the parent.
      74           0 :   return e && e->AttrValueIs(kNameSpaceID_None, nsGkAtoms::name,
      75           0 :                              aNameBeingResolved, eCaseMatters);
      76             : }
      77             : 
      78             : bool
      79         423 : WindowNamedPropertiesHandler::getOwnPropDescriptor(JSContext* aCx,
      80             :                                                    JS::Handle<JSObject*> aProxy,
      81             :                                                    JS::Handle<jsid> aId,
      82             :                                                    bool /* unused */,
      83             :                                                    JS::MutableHandle<JS::PropertyDescriptor> aDesc)
      84             :                                                    const
      85             : {
      86         423 :   if (!JSID_IS_STRING(aId)) {
      87             :     // Nothing to do if we're resolving a non-string property.
      88           0 :     return true;
      89             :   }
      90             : 
      91             :   bool hasOnPrototype;
      92         423 :   if (!HasPropertyOnPrototype(aCx, aProxy, aId, &hasOnPrototype)) {
      93           0 :     return false;
      94             :   }
      95         423 :   if (hasOnPrototype) {
      96          47 :     return true;
      97             :   }
      98             : 
      99         752 :   nsAutoJSString str;
     100         376 :   if (!str.init(aCx, JSID_TO_STRING(aId))) {
     101           0 :     return false;
     102             :   }
     103             : 
     104         376 :   if(str.IsEmpty()) {
     105           0 :     return true;
     106             :   }
     107             : 
     108             :   // Grab the DOM window.
     109         752 :   JS::Rooted<JSObject*> global(aCx, JS_GetGlobalForObject(aCx, aProxy));
     110         376 :   nsGlobalWindow* win = xpc::WindowOrNull(global);
     111         376 :   if (win->Length() > 0) {
     112         160 :     nsCOMPtr<nsPIDOMWindowOuter> childWin = win->GetChildWindow(str);
     113          80 :     if (childWin && ShouldExposeChildWindow(str, childWin)) {
     114             :       // We found a subframe of the right name. Shadowing via |var foo| in
     115             :       // global scope is still allowed, since |var| only looks up |own|
     116             :       // properties. But unqualified shadowing will fail, per-spec.
     117           0 :       JS::Rooted<JS::Value> v(aCx);
     118           0 :       if (!WrapObject(aCx, childWin, &v)) {
     119           0 :         return false;
     120             :       }
     121           0 :       FillPropertyDescriptor(aDesc, aProxy, 0, v);
     122           0 :       return true;
     123             :     }
     124             :   }
     125             : 
     126             :   // The rest of this function is for HTML documents only.
     127         752 :   nsCOMPtr<nsIHTMLDocument> htmlDoc = do_QueryInterface(win->GetExtantDoc());
     128         376 :   if (!htmlDoc) {
     129         357 :     return true;
     130             :   }
     131          19 :   nsHTMLDocument* document = static_cast<nsHTMLDocument*>(htmlDoc.get());
     132             : 
     133          19 :   Element* element = document->GetElementById(str);
     134          19 :   if (element) {
     135           0 :     JS::Rooted<JS::Value> v(aCx);
     136           0 :     if (!WrapObject(aCx, element, &v)) {
     137           0 :       return false;
     138             :     }
     139           0 :     FillPropertyDescriptor(aDesc, aProxy, 0, v);
     140           0 :     return true;
     141             :   }
     142             : 
     143             :   nsWrapperCache* cache;
     144          19 :   nsISupports* result = document->ResolveName(str, &cache);
     145          19 :   if (!result) {
     146          19 :     return true;
     147             :   }
     148             : 
     149           0 :   JS::Rooted<JS::Value> v(aCx);
     150           0 :   if (!WrapObject(aCx, result, cache, nullptr, &v)) {
     151           0 :     return false;
     152             :   }
     153           0 :   FillPropertyDescriptor(aDesc, aProxy, 0, v);
     154           0 :   return true;
     155             : }
     156             : 
     157             : bool
     158           0 : WindowNamedPropertiesHandler::defineProperty(JSContext* aCx,
     159             :                                              JS::Handle<JSObject*> aProxy,
     160             :                                              JS::Handle<jsid> aId,
     161             :                                              JS::Handle<JS::PropertyDescriptor> aDesc,
     162             :                                              JS::ObjectOpResult &result) const
     163             : {
     164           0 :   ErrorResult rv;
     165           0 :   rv.ThrowTypeError<MSG_DEFINEPROPERTY_ON_GSP>();
     166           0 :   MOZ_ALWAYS_TRUE(rv.MaybeSetPendingException(aCx));
     167           0 :   return false;
     168             : }
     169             : 
     170             : bool
     171           0 : WindowNamedPropertiesHandler::ownPropNames(JSContext* aCx,
     172             :                                            JS::Handle<JSObject*> aProxy,
     173             :                                            unsigned flags,
     174             :                                            JS::AutoIdVector& aProps) const
     175             : {
     176           0 :   if (!(flags & JSITER_HIDDEN)) {
     177             :     // None of our named properties are enumerable.
     178           0 :     return true;
     179             :   }
     180             : 
     181             :   // Grab the DOM window.
     182           0 :   nsGlobalWindow* win = xpc::WindowOrNull(JS_GetGlobalForObject(aCx, aProxy));
     183           0 :   nsTArray<nsString> names;
     184             :   // The names live on the outer window, which might be null
     185           0 :   nsGlobalWindow* outer = win->GetOuterWindowInternal();
     186           0 :   if (outer) {
     187           0 :     nsDOMWindowList* childWindows = outer->GetWindowList();
     188           0 :     if (childWindows) {
     189           0 :       uint32_t length = childWindows->GetLength();
     190           0 :       for (uint32_t i = 0; i < length; ++i) {
     191             :         nsCOMPtr<nsIDocShellTreeItem> item =
     192           0 :           childWindows->GetDocShellTreeItemAt(i);
     193             :         // This is a bit silly, since we could presumably just do
     194             :         // item->GetWindow().  But it's not obvious whether this does the same
     195             :         // thing as GetChildWindow() with the item's name (due to the complexity
     196             :         // of FindChildWithName).  Since GetChildWindow is what we use in
     197             :         // getOwnPropDescriptor, let's try to be consistent.
     198           0 :         nsString name;
     199           0 :         item->GetName(name);
     200           0 :         if (!names.Contains(name)) {
     201             :           // Make sure we really would expose it from getOwnPropDescriptor.
     202           0 :           nsCOMPtr<nsPIDOMWindowOuter> childWin = win->GetChildWindow(name);
     203           0 :           if (childWin && ShouldExposeChildWindow(name, childWin)) {
     204           0 :             names.AppendElement(name);
     205             :           }
     206             :         }
     207             :       }
     208             :     }
     209             :   }
     210           0 :   if (!AppendNamedPropertyIds(aCx, aProxy, names, false, aProps)) {
     211           0 :     return false;
     212             :   }
     213             : 
     214           0 :   names.Clear();
     215           0 :   nsCOMPtr<nsIHTMLDocument> htmlDoc = do_QueryInterface(win->GetExtantDoc());
     216           0 :   if (!htmlDoc) {
     217           0 :     return true;
     218             :   }
     219           0 :   nsHTMLDocument* document = static_cast<nsHTMLDocument*>(htmlDoc.get());
     220             :   // Document names are enumerable, so we want to get them no matter what flags
     221             :   // is.
     222           0 :   document->GetSupportedNames(names);
     223             : 
     224           0 :   JS::AutoIdVector docProps(aCx);
     225           0 :   if (!AppendNamedPropertyIds(aCx, aProxy, names, false, docProps)) {
     226           0 :     return false;
     227             :   }
     228             : 
     229           0 :   return js::AppendUnique(aCx, aProps, docProps);
     230             : }
     231             : 
     232             : bool
     233           0 : WindowNamedPropertiesHandler::delete_(JSContext* aCx,
     234             :                                       JS::Handle<JSObject*> aProxy,
     235             :                                       JS::Handle<jsid> aId,
     236             :                                       JS::ObjectOpResult &aResult) const
     237             : {
     238           0 :   return aResult.failCantDeleteWindowNamedProperty();
     239             : }
     240             : 
     241             : static bool
     242           1 : IsWebExtensionContentScript(JSContext* aCx)
     243             : {
     244           1 :   auto* priv = xpc::CompartmentPrivate::Get(JS::CurrentGlobalOrNull(aCx));
     245           1 :   return priv->isWebExtensionContentScript;
     246             : }
     247             : 
     248             : static const int32_t kAlwaysAllowNamedPropertiesObject = 0;
     249             : static const int32_t kDisallowNamedPropertiesObjectForContentScripts = 1;
     250             : static const int32_t kDisallowNamedPropertiesObjectForXrays = 2;
     251             : 
     252             : static bool
     253           1 : AllowNamedPropertiesObject(JSContext* aCx)
     254             : {
     255             :   static int32_t sAllowed;
     256             :   static bool sAllowedCached = false;
     257           1 :   if (!sAllowedCached) {
     258             :     Preferences::AddIntVarCache(&sAllowed,
     259             :                                 "dom.allow_named_properties_object_for_xrays",
     260           1 :                                 kDisallowNamedPropertiesObjectForContentScripts);
     261           1 :     sAllowedCached = true;
     262             :   }
     263             : 
     264           1 :   if (sAllowed == kDisallowNamedPropertiesObjectForXrays) {
     265           0 :     return false;
     266             :   }
     267             : 
     268           1 :   if (sAllowed == kAlwaysAllowNamedPropertiesObject) {
     269           0 :     return true;
     270             :   }
     271             : 
     272           1 :   if (sAllowed == kDisallowNamedPropertiesObjectForContentScripts) {
     273           1 :     return !IsWebExtensionContentScript(aCx);
     274             :   }
     275             : 
     276           0 :   NS_WARNING("Unknown value for dom.allow_named_properties_object_for_xrays");
     277             :   // Fail open for now.
     278           0 :   return true;
     279             : }
     280             : 
     281             : 
     282             : static bool
     283           1 : ResolveWindowNamedProperty(JSContext* aCx, JS::Handle<JSObject*> aWrapper,
     284             :                            JS::Handle<JSObject*> aObj, JS::Handle<jsid> aId,
     285             :                            JS::MutableHandle<JS::PropertyDescriptor> aDesc)
     286             : {
     287           1 :   if (!AllowNamedPropertiesObject(aCx)) {
     288           0 :     return true;
     289             :   }
     290             : 
     291             :   {
     292           2 :     JSAutoCompartment ac(aCx, aObj);
     293           2 :     if (!js::GetProxyHandler(aObj)->getOwnPropertyDescriptor(aCx, aObj, aId,
     294           1 :                                                              aDesc)) {
     295           0 :       return false;
     296             :     }
     297             :   }
     298             : 
     299           1 :   if (aDesc.object()) {
     300           0 :     aDesc.object().set(aWrapper);
     301             : 
     302           0 :     return JS_WrapPropertyDescriptor(aCx, aDesc);
     303             :   }
     304             : 
     305           1 :   return true;
     306             : }
     307             : 
     308             : static bool
     309           0 : EnumerateWindowNamedProperties(JSContext* aCx, JS::Handle<JSObject*> aWrapper,
     310             :                                JS::Handle<JSObject*> aObj,
     311             :                                JS::AutoIdVector& aProps)
     312             : {
     313           0 :   if (!AllowNamedPropertiesObject(aCx)) {
     314           0 :     return true;
     315             :   }
     316             : 
     317           0 :   JSAutoCompartment ac(aCx, aObj);
     318           0 :   return js::GetProxyHandler(aObj)->ownPropertyKeys(aCx, aObj, aProps);
     319             : }
     320             : 
     321             : const NativePropertyHooks sWindowNamedPropertiesNativePropertyHooks[] = { {
     322             :   ResolveWindowNamedProperty,
     323             :   EnumerateWindowNamedProperties,
     324             :   nullptr,
     325             :   { nullptr, nullptr },
     326             :   prototypes::id::_ID_Count,
     327             :   constructors::id::_ID_Count,
     328             :   nullptr
     329             : } };
     330             : 
     331             : // Note that this class doesn't need any reserved slots, but SpiderMonkey
     332             : // asserts all proxy classes have at least one reserved slot.
     333             : static const DOMIfaceAndProtoJSClass WindowNamedPropertiesClass = {
     334             :   PROXY_CLASS_DEF("WindowProperties",
     335             :                   JSCLASS_IS_DOMIFACEANDPROTOJSCLASS |
     336             :                   JSCLASS_HAS_RESERVED_SLOTS(1)),
     337             :   eNamedPropertiesObject,
     338             :   false,
     339             :   prototypes::id::_ID_Count,
     340             :   0,
     341             :   sWindowNamedPropertiesNativePropertyHooks,
     342             :   "[object WindowProperties]",
     343             :   EventTargetBinding::GetProtoObject
     344             : };
     345             : 
     346             : // static
     347             : JSObject*
     348           7 : WindowNamedPropertiesHandler::Create(JSContext* aCx,
     349             :                                      JS::Handle<JSObject*> aProto)
     350             : {
     351             :   // Note: since the scope polluter proxy lives on the window's prototype
     352             :   // chain, it needs a singleton type to avoid polluting type information
     353             :   // for properties on the window.
     354           7 :   js::ProxyOptions options;
     355           7 :   options.setSingleton(true);
     356           7 :   options.setClass(&WindowNamedPropertiesClass.mBase);
     357             : 
     358          14 :   JS::Rooted<JSObject*> gsp(aCx);
     359          14 :   gsp = js::NewProxyObject(aCx, WindowNamedPropertiesHandler::getInstance(),
     360             :                            JS::NullHandleValue, aProto,
     361          14 :                            options);
     362           7 :   if (!gsp) {
     363           0 :     return nullptr;
     364             :   }
     365             : 
     366             :   bool succeeded;
     367           7 :   if (!JS_SetImmutablePrototype(aCx, gsp, &succeeded)) {
     368           0 :     return nullptr;
     369             :   }
     370           7 :   MOZ_ASSERT(succeeded,
     371             :              "errors making the [[Prototype]] of the named properties object "
     372             :              "immutable should have been JSAPI failures, not !succeeded");
     373             : 
     374           7 :   return gsp;
     375             : }
     376             : 
     377             : } // namespace dom
     378             : } // namespace mozilla

Generated by: LCOV version 1.13