1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 1.2 +++ b/dom/base/WindowNamedPropertiesHandler.cpp Wed Dec 31 06:09:35 2014 +0100 1.3 @@ -0,0 +1,248 @@ 1.4 +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ 1.5 +/* vim: set ts=2 sw=2 et tw=78: */ 1.6 +/* This Source Code Form is subject to the terms of the Mozilla Public 1.7 + * License, v. 2.0. If a copy of the MPL was not distributed with this 1.8 + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 1.9 + 1.10 +#include "WindowNamedPropertiesHandler.h" 1.11 +#include "mozilla/dom/WindowBinding.h" 1.12 +#include "nsDOMClassInfo.h" 1.13 +#include "nsGlobalWindow.h" 1.14 +#include "nsHTMLDocument.h" 1.15 +#include "nsJSUtils.h" 1.16 +#include "xpcprivate.h" 1.17 + 1.18 +namespace mozilla { 1.19 +namespace dom { 1.20 + 1.21 +static bool 1.22 +ShouldExposeChildWindow(nsString& aNameBeingResolved, nsIDOMWindow *aChild) 1.23 +{ 1.24 + // If we're same-origin with the child, go ahead and expose it. 1.25 + nsCOMPtr<nsIScriptObjectPrincipal> sop = do_QueryInterface(aChild); 1.26 + NS_ENSURE_TRUE(sop, false); 1.27 + if (nsContentUtils::GetSubjectPrincipal()->Equals(sop->GetPrincipal())) { 1.28 + return true; 1.29 + } 1.30 + 1.31 + // If we're not same-origin, expose it _only_ if the name of the browsing 1.32 + // context matches the 'name' attribute of the frame element in the parent. 1.33 + // The motivations behind this heuristic are worth explaining here. 1.34 + // 1.35 + // Historically, all UAs supported global named access to any child browsing 1.36 + // context (that is to say, window.dolske returns a child frame where either 1.37 + // the "name" attribute on the frame element was set to "dolske", or where 1.38 + // the child explicitly set window.name = "dolske"). 1.39 + // 1.40 + // This is problematic because it allows possibly-malicious and unrelated 1.41 + // cross-origin subframes to pollute the global namespace of their parent in 1.42 + // unpredictable ways (see bug 860494). This is also problematic for browser 1.43 + // engines like Servo that want to run cross-origin script on different 1.44 + // threads. 1.45 + // 1.46 + // The naive solution here would be to filter out any cross-origin subframes 1.47 + // obtained when doing named lookup in global scope. But that is unlikely to 1.48 + // be web-compatible, since it will break named access for consumers that do 1.49 + // <iframe name="dolske" src="http://cross-origin.com/sadtrombone.html"> and 1.50 + // expect to be able to access the cross-origin subframe via named lookup on 1.51 + // the global. 1.52 + // 1.53 + // The optimal behavior would be to do the following: 1.54 + // (a) Look for any child browsing context with name="dolske". 1.55 + // (b) If the result is cross-origin, null it out. 1.56 + // (c) If we have null, look for a frame element whose 'name' attribute is 1.57 + // "dolske". 1.58 + // 1.59 + // Unfortunately, (c) would require some engineering effort to be performant 1.60 + // in Gecko, and probably in other UAs as well. So we go with a simpler 1.61 + // approximation of the above. This approximation will only break sites that 1.62 + // rely on their cross-origin subframes setting window.name to a known value, 1.63 + // which is unlikely to be very common. And while it does introduce a 1.64 + // dependency on cross-origin state when doing global lookups, it doesn't 1.65 + // allow the child to arbitrarily pollute the parent namespace, and requires 1.66 + // cross-origin communication only in a limited set of cases that can be 1.67 + // computed independently by the parent. 1.68 + nsCOMPtr<nsPIDOMWindow> piWin = do_QueryInterface(aChild); 1.69 + NS_ENSURE_TRUE(piWin, false); 1.70 + Element* e = piWin->GetFrameElementInternal(); 1.71 + return e && e->AttrValueIs(kNameSpaceID_None, nsGkAtoms::name, 1.72 + aNameBeingResolved, eCaseMatters); 1.73 +} 1.74 + 1.75 +static nsGlobalWindow* 1.76 +GetWindowFromGlobal(JSObject* aGlobal) 1.77 +{ 1.78 + nsGlobalWindow* win; 1.79 + if (NS_SUCCEEDED(UNWRAP_OBJECT(Window, aGlobal, win))) { 1.80 + return win; 1.81 + } 1.82 + XPCWrappedNative* wrapper = XPCWrappedNative::Get(aGlobal); 1.83 + nsCOMPtr<nsPIDOMWindow> piWin = do_QueryWrappedNative(wrapper); 1.84 + MOZ_ASSERT(piWin); 1.85 + return static_cast<nsGlobalWindow*>(piWin.get()); 1.86 +} 1.87 + 1.88 +bool 1.89 +WindowNamedPropertiesHandler::getOwnPropDescriptor(JSContext* aCx, 1.90 + JS::Handle<JSObject*> aProxy, 1.91 + JS::Handle<jsid> aId, 1.92 + bool /* unused */, 1.93 + JS::MutableHandle<JSPropertyDescriptor> aDesc) 1.94 +{ 1.95 + if (!JSID_IS_STRING(aId)) { 1.96 + // Nothing to do if we're resolving a non-string property. 1.97 + return true; 1.98 + } 1.99 + 1.100 + JS::Rooted<JSObject*> global(aCx, JS_GetGlobalForObject(aCx, aProxy)); 1.101 + if (HasPropertyOnPrototype(aCx, aProxy, aId)) { 1.102 + return true; 1.103 + } 1.104 + 1.105 + nsDependentJSString str(aId); 1.106 + 1.107 + // Grab the DOM window. 1.108 + nsGlobalWindow* win = GetWindowFromGlobal(global); 1.109 + if (win->Length() > 0) { 1.110 + nsCOMPtr<nsIDOMWindow> childWin = win->GetChildWindow(str); 1.111 + if (childWin && ShouldExposeChildWindow(str, childWin)) { 1.112 + // We found a subframe of the right name. Shadowing via |var foo| in 1.113 + // global scope is still allowed, since |var| only looks up |own| 1.114 + // properties. But unqualified shadowing will fail, per-spec. 1.115 + JS::Rooted<JS::Value> v(aCx); 1.116 + if (!WrapObject(aCx, childWin, &v)) { 1.117 + return false; 1.118 + } 1.119 + aDesc.object().set(aProxy); 1.120 + aDesc.value().set(v); 1.121 + aDesc.setAttributes(JSPROP_ENUMERATE); 1.122 + return true; 1.123 + } 1.124 + } 1.125 + 1.126 + // The rest of this function is for HTML documents only. 1.127 + nsCOMPtr<nsIHTMLDocument> htmlDoc = do_QueryInterface(win->GetExtantDoc()); 1.128 + if (!htmlDoc) { 1.129 + return true; 1.130 + } 1.131 + nsHTMLDocument* document = static_cast<nsHTMLDocument*>(htmlDoc.get()); 1.132 + 1.133 + Element* element = document->GetElementById(str); 1.134 + if (element) { 1.135 + JS::Rooted<JS::Value> v(aCx); 1.136 + if (!WrapObject(aCx, element, &v)) { 1.137 + return false; 1.138 + } 1.139 + aDesc.object().set(aProxy); 1.140 + aDesc.value().set(v); 1.141 + aDesc.setAttributes(JSPROP_ENUMERATE); 1.142 + return true; 1.143 + } 1.144 + 1.145 + nsWrapperCache* cache; 1.146 + nsISupports* result = document->ResolveName(str, &cache); 1.147 + if (!result) { 1.148 + return true; 1.149 + } 1.150 + 1.151 + JS::Rooted<JS::Value> v(aCx); 1.152 + if (!WrapObject(aCx, result, cache, nullptr, &v)) { 1.153 + return false; 1.154 + } 1.155 + aDesc.object().set(aProxy); 1.156 + aDesc.value().set(v); 1.157 + aDesc.setAttributes(JSPROP_ENUMERATE); 1.158 + return true; 1.159 +} 1.160 + 1.161 +bool 1.162 +WindowNamedPropertiesHandler::defineProperty(JSContext* aCx, 1.163 + JS::Handle<JSObject*> aProxy, 1.164 + JS::Handle<jsid> aId, 1.165 + JS::MutableHandle<JSPropertyDescriptor> aDesc) 1.166 +{ 1.167 + ErrorResult rv; 1.168 + rv.ThrowTypeError(MSG_DEFINEPROPERTY_ON_GSP); 1.169 + rv.ReportTypeError(aCx); 1.170 + return false; 1.171 +} 1.172 + 1.173 +bool 1.174 +WindowNamedPropertiesHandler::ownPropNames(JSContext* aCx, 1.175 + JS::Handle<JSObject*> aProxy, 1.176 + unsigned flags, 1.177 + JS::AutoIdVector& aProps) 1.178 +{ 1.179 + // Grab the DOM window. 1.180 + nsGlobalWindow* win = GetWindowFromGlobal(JS_GetGlobalForObject(aCx, aProxy)); 1.181 + nsTArray<nsString> names; 1.182 + win->GetSupportedNames(names); 1.183 + // Filter out the ones we wouldn't expose from getOwnPropertyDescriptor. 1.184 + // We iterate backwards so we can remove things from the list easily. 1.185 + for (size_t i = names.Length(); i > 0; ) { 1.186 + --i; // Now we're pointing at the next name we want to look at 1.187 + nsCOMPtr<nsIDOMWindow> childWin = win->GetChildWindow(names[i]); 1.188 + if (!childWin || !ShouldExposeChildWindow(names[i], childWin)) { 1.189 + names.RemoveElementAt(i); 1.190 + } 1.191 + } 1.192 + if (!AppendNamedPropertyIds(aCx, aProxy, names, false, aProps)) { 1.193 + return false; 1.194 + } 1.195 + 1.196 + names.Clear(); 1.197 + nsCOMPtr<nsIHTMLDocument> htmlDoc = do_QueryInterface(win->GetExtantDoc()); 1.198 + if (!htmlDoc) { 1.199 + return true; 1.200 + } 1.201 + nsHTMLDocument* document = static_cast<nsHTMLDocument*>(htmlDoc.get()); 1.202 + document->GetSupportedNames(flags, names); 1.203 + 1.204 + JS::AutoIdVector docProps(aCx); 1.205 + if (!AppendNamedPropertyIds(aCx, aProxy, names, false, docProps)) { 1.206 + return false; 1.207 + } 1.208 + 1.209 + return js::AppendUnique(aCx, aProps, docProps); 1.210 +} 1.211 + 1.212 +bool 1.213 +WindowNamedPropertiesHandler::delete_(JSContext* aCx, 1.214 + JS::Handle<JSObject*> aProxy, 1.215 + JS::Handle<jsid> aId, bool* aBp) 1.216 +{ 1.217 + *aBp = false; 1.218 + return true; 1.219 +} 1.220 + 1.221 +// static 1.222 +void 1.223 +WindowNamedPropertiesHandler::Install(JSContext* aCx, 1.224 + JS::Handle<JSObject*> aProto) 1.225 +{ 1.226 + JS::Rooted<JSObject*> protoProto(aCx); 1.227 + if (!::JS_GetPrototype(aCx, aProto, &protoProto)) { 1.228 + return; 1.229 + } 1.230 + 1.231 + // Note: since the scope polluter proxy lives on the window's prototype 1.232 + // chain, it needs a singleton type to avoid polluting type information 1.233 + // for properties on the window. 1.234 + JS::Rooted<JSObject*> gsp(aCx); 1.235 + js::ProxyOptions options; 1.236 + options.setSingleton(true); 1.237 + gsp = js::NewProxyObject(aCx, WindowNamedPropertiesHandler::getInstance(), 1.238 + JS::NullHandleValue, protoProto, 1.239 + js::GetGlobalForObjectCrossCompartment(aProto), 1.240 + options); 1.241 + if (!gsp) { 1.242 + return; 1.243 + } 1.244 + 1.245 + // And then set the prototype of the interface prototype object to be the 1.246 + // global scope polluter. 1.247 + ::JS_SplicePrototype(aCx, aProto, gsp); 1.248 +} 1.249 + 1.250 +} // namespace dom 1.251 +} // namespace mozilla