dom/base/WindowNamedPropertiesHandler.cpp

Thu, 22 Jan 2015 13:21:57 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Thu, 22 Jan 2015 13:21:57 +0100
branch
TOR_BUG_9701
changeset 15
b8a032363ba2
permissions
-rw-r--r--

Incorporate requested changes from Mozilla in review:
https://bugzilla.mozilla.org/show_bug.cgi?id=1123480#c6

michael@0 1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
michael@0 2 /* vim: set ts=2 sw=2 et tw=78: */
michael@0 3 /* This Source Code Form is subject to the terms of the Mozilla Public
michael@0 4 * License, v. 2.0. If a copy of the MPL was not distributed with this
michael@0 5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
michael@0 6
michael@0 7 #include "WindowNamedPropertiesHandler.h"
michael@0 8 #include "mozilla/dom/WindowBinding.h"
michael@0 9 #include "nsDOMClassInfo.h"
michael@0 10 #include "nsGlobalWindow.h"
michael@0 11 #include "nsHTMLDocument.h"
michael@0 12 #include "nsJSUtils.h"
michael@0 13 #include "xpcprivate.h"
michael@0 14
michael@0 15 namespace mozilla {
michael@0 16 namespace dom {
michael@0 17
michael@0 18 static bool
michael@0 19 ShouldExposeChildWindow(nsString& aNameBeingResolved, nsIDOMWindow *aChild)
michael@0 20 {
michael@0 21 // If we're same-origin with the child, go ahead and expose it.
michael@0 22 nsCOMPtr<nsIScriptObjectPrincipal> sop = do_QueryInterface(aChild);
michael@0 23 NS_ENSURE_TRUE(sop, false);
michael@0 24 if (nsContentUtils::GetSubjectPrincipal()->Equals(sop->GetPrincipal())) {
michael@0 25 return true;
michael@0 26 }
michael@0 27
michael@0 28 // If we're not same-origin, expose it _only_ if the name of the browsing
michael@0 29 // context matches the 'name' attribute of the frame element in the parent.
michael@0 30 // The motivations behind this heuristic are worth explaining here.
michael@0 31 //
michael@0 32 // Historically, all UAs supported global named access to any child browsing
michael@0 33 // context (that is to say, window.dolske returns a child frame where either
michael@0 34 // the "name" attribute on the frame element was set to "dolske", or where
michael@0 35 // the child explicitly set window.name = "dolske").
michael@0 36 //
michael@0 37 // This is problematic because it allows possibly-malicious and unrelated
michael@0 38 // cross-origin subframes to pollute the global namespace of their parent in
michael@0 39 // unpredictable ways (see bug 860494). This is also problematic for browser
michael@0 40 // engines like Servo that want to run cross-origin script on different
michael@0 41 // threads.
michael@0 42 //
michael@0 43 // The naive solution here would be to filter out any cross-origin subframes
michael@0 44 // obtained when doing named lookup in global scope. But that is unlikely to
michael@0 45 // be web-compatible, since it will break named access for consumers that do
michael@0 46 // <iframe name="dolske" src="http://cross-origin.com/sadtrombone.html"> and
michael@0 47 // expect to be able to access the cross-origin subframe via named lookup on
michael@0 48 // the global.
michael@0 49 //
michael@0 50 // The optimal behavior would be to do the following:
michael@0 51 // (a) Look for any child browsing context with name="dolske".
michael@0 52 // (b) If the result is cross-origin, null it out.
michael@0 53 // (c) If we have null, look for a frame element whose 'name' attribute is
michael@0 54 // "dolske".
michael@0 55 //
michael@0 56 // Unfortunately, (c) would require some engineering effort to be performant
michael@0 57 // in Gecko, and probably in other UAs as well. So we go with a simpler
michael@0 58 // approximation of the above. This approximation will only break sites that
michael@0 59 // rely on their cross-origin subframes setting window.name to a known value,
michael@0 60 // which is unlikely to be very common. And while it does introduce a
michael@0 61 // dependency on cross-origin state when doing global lookups, it doesn't
michael@0 62 // allow the child to arbitrarily pollute the parent namespace, and requires
michael@0 63 // cross-origin communication only in a limited set of cases that can be
michael@0 64 // computed independently by the parent.
michael@0 65 nsCOMPtr<nsPIDOMWindow> piWin = do_QueryInterface(aChild);
michael@0 66 NS_ENSURE_TRUE(piWin, false);
michael@0 67 Element* e = piWin->GetFrameElementInternal();
michael@0 68 return e && e->AttrValueIs(kNameSpaceID_None, nsGkAtoms::name,
michael@0 69 aNameBeingResolved, eCaseMatters);
michael@0 70 }
michael@0 71
michael@0 72 static nsGlobalWindow*
michael@0 73 GetWindowFromGlobal(JSObject* aGlobal)
michael@0 74 {
michael@0 75 nsGlobalWindow* win;
michael@0 76 if (NS_SUCCEEDED(UNWRAP_OBJECT(Window, aGlobal, win))) {
michael@0 77 return win;
michael@0 78 }
michael@0 79 XPCWrappedNative* wrapper = XPCWrappedNative::Get(aGlobal);
michael@0 80 nsCOMPtr<nsPIDOMWindow> piWin = do_QueryWrappedNative(wrapper);
michael@0 81 MOZ_ASSERT(piWin);
michael@0 82 return static_cast<nsGlobalWindow*>(piWin.get());
michael@0 83 }
michael@0 84
michael@0 85 bool
michael@0 86 WindowNamedPropertiesHandler::getOwnPropDescriptor(JSContext* aCx,
michael@0 87 JS::Handle<JSObject*> aProxy,
michael@0 88 JS::Handle<jsid> aId,
michael@0 89 bool /* unused */,
michael@0 90 JS::MutableHandle<JSPropertyDescriptor> aDesc)
michael@0 91 {
michael@0 92 if (!JSID_IS_STRING(aId)) {
michael@0 93 // Nothing to do if we're resolving a non-string property.
michael@0 94 return true;
michael@0 95 }
michael@0 96
michael@0 97 JS::Rooted<JSObject*> global(aCx, JS_GetGlobalForObject(aCx, aProxy));
michael@0 98 if (HasPropertyOnPrototype(aCx, aProxy, aId)) {
michael@0 99 return true;
michael@0 100 }
michael@0 101
michael@0 102 nsDependentJSString str(aId);
michael@0 103
michael@0 104 // Grab the DOM window.
michael@0 105 nsGlobalWindow* win = GetWindowFromGlobal(global);
michael@0 106 if (win->Length() > 0) {
michael@0 107 nsCOMPtr<nsIDOMWindow> childWin = win->GetChildWindow(str);
michael@0 108 if (childWin && ShouldExposeChildWindow(str, childWin)) {
michael@0 109 // We found a subframe of the right name. Shadowing via |var foo| in
michael@0 110 // global scope is still allowed, since |var| only looks up |own|
michael@0 111 // properties. But unqualified shadowing will fail, per-spec.
michael@0 112 JS::Rooted<JS::Value> v(aCx);
michael@0 113 if (!WrapObject(aCx, childWin, &v)) {
michael@0 114 return false;
michael@0 115 }
michael@0 116 aDesc.object().set(aProxy);
michael@0 117 aDesc.value().set(v);
michael@0 118 aDesc.setAttributes(JSPROP_ENUMERATE);
michael@0 119 return true;
michael@0 120 }
michael@0 121 }
michael@0 122
michael@0 123 // The rest of this function is for HTML documents only.
michael@0 124 nsCOMPtr<nsIHTMLDocument> htmlDoc = do_QueryInterface(win->GetExtantDoc());
michael@0 125 if (!htmlDoc) {
michael@0 126 return true;
michael@0 127 }
michael@0 128 nsHTMLDocument* document = static_cast<nsHTMLDocument*>(htmlDoc.get());
michael@0 129
michael@0 130 Element* element = document->GetElementById(str);
michael@0 131 if (element) {
michael@0 132 JS::Rooted<JS::Value> v(aCx);
michael@0 133 if (!WrapObject(aCx, element, &v)) {
michael@0 134 return false;
michael@0 135 }
michael@0 136 aDesc.object().set(aProxy);
michael@0 137 aDesc.value().set(v);
michael@0 138 aDesc.setAttributes(JSPROP_ENUMERATE);
michael@0 139 return true;
michael@0 140 }
michael@0 141
michael@0 142 nsWrapperCache* cache;
michael@0 143 nsISupports* result = document->ResolveName(str, &cache);
michael@0 144 if (!result) {
michael@0 145 return true;
michael@0 146 }
michael@0 147
michael@0 148 JS::Rooted<JS::Value> v(aCx);
michael@0 149 if (!WrapObject(aCx, result, cache, nullptr, &v)) {
michael@0 150 return false;
michael@0 151 }
michael@0 152 aDesc.object().set(aProxy);
michael@0 153 aDesc.value().set(v);
michael@0 154 aDesc.setAttributes(JSPROP_ENUMERATE);
michael@0 155 return true;
michael@0 156 }
michael@0 157
michael@0 158 bool
michael@0 159 WindowNamedPropertiesHandler::defineProperty(JSContext* aCx,
michael@0 160 JS::Handle<JSObject*> aProxy,
michael@0 161 JS::Handle<jsid> aId,
michael@0 162 JS::MutableHandle<JSPropertyDescriptor> aDesc)
michael@0 163 {
michael@0 164 ErrorResult rv;
michael@0 165 rv.ThrowTypeError(MSG_DEFINEPROPERTY_ON_GSP);
michael@0 166 rv.ReportTypeError(aCx);
michael@0 167 return false;
michael@0 168 }
michael@0 169
michael@0 170 bool
michael@0 171 WindowNamedPropertiesHandler::ownPropNames(JSContext* aCx,
michael@0 172 JS::Handle<JSObject*> aProxy,
michael@0 173 unsigned flags,
michael@0 174 JS::AutoIdVector& aProps)
michael@0 175 {
michael@0 176 // Grab the DOM window.
michael@0 177 nsGlobalWindow* win = GetWindowFromGlobal(JS_GetGlobalForObject(aCx, aProxy));
michael@0 178 nsTArray<nsString> names;
michael@0 179 win->GetSupportedNames(names);
michael@0 180 // Filter out the ones we wouldn't expose from getOwnPropertyDescriptor.
michael@0 181 // We iterate backwards so we can remove things from the list easily.
michael@0 182 for (size_t i = names.Length(); i > 0; ) {
michael@0 183 --i; // Now we're pointing at the next name we want to look at
michael@0 184 nsCOMPtr<nsIDOMWindow> childWin = win->GetChildWindow(names[i]);
michael@0 185 if (!childWin || !ShouldExposeChildWindow(names[i], childWin)) {
michael@0 186 names.RemoveElementAt(i);
michael@0 187 }
michael@0 188 }
michael@0 189 if (!AppendNamedPropertyIds(aCx, aProxy, names, false, aProps)) {
michael@0 190 return false;
michael@0 191 }
michael@0 192
michael@0 193 names.Clear();
michael@0 194 nsCOMPtr<nsIHTMLDocument> htmlDoc = do_QueryInterface(win->GetExtantDoc());
michael@0 195 if (!htmlDoc) {
michael@0 196 return true;
michael@0 197 }
michael@0 198 nsHTMLDocument* document = static_cast<nsHTMLDocument*>(htmlDoc.get());
michael@0 199 document->GetSupportedNames(flags, names);
michael@0 200
michael@0 201 JS::AutoIdVector docProps(aCx);
michael@0 202 if (!AppendNamedPropertyIds(aCx, aProxy, names, false, docProps)) {
michael@0 203 return false;
michael@0 204 }
michael@0 205
michael@0 206 return js::AppendUnique(aCx, aProps, docProps);
michael@0 207 }
michael@0 208
michael@0 209 bool
michael@0 210 WindowNamedPropertiesHandler::delete_(JSContext* aCx,
michael@0 211 JS::Handle<JSObject*> aProxy,
michael@0 212 JS::Handle<jsid> aId, bool* aBp)
michael@0 213 {
michael@0 214 *aBp = false;
michael@0 215 return true;
michael@0 216 }
michael@0 217
michael@0 218 // static
michael@0 219 void
michael@0 220 WindowNamedPropertiesHandler::Install(JSContext* aCx,
michael@0 221 JS::Handle<JSObject*> aProto)
michael@0 222 {
michael@0 223 JS::Rooted<JSObject*> protoProto(aCx);
michael@0 224 if (!::JS_GetPrototype(aCx, aProto, &protoProto)) {
michael@0 225 return;
michael@0 226 }
michael@0 227
michael@0 228 // Note: since the scope polluter proxy lives on the window's prototype
michael@0 229 // chain, it needs a singleton type to avoid polluting type information
michael@0 230 // for properties on the window.
michael@0 231 JS::Rooted<JSObject*> gsp(aCx);
michael@0 232 js::ProxyOptions options;
michael@0 233 options.setSingleton(true);
michael@0 234 gsp = js::NewProxyObject(aCx, WindowNamedPropertiesHandler::getInstance(),
michael@0 235 JS::NullHandleValue, protoProto,
michael@0 236 js::GetGlobalForObjectCrossCompartment(aProto),
michael@0 237 options);
michael@0 238 if (!gsp) {
michael@0 239 return;
michael@0 240 }
michael@0 241
michael@0 242 // And then set the prototype of the interface prototype object to be the
michael@0 243 // global scope polluter.
michael@0 244 ::JS_SplicePrototype(aCx, aProto, gsp);
michael@0 245 }
michael@0 246
michael@0 247 } // namespace dom
michael@0 248 } // namespace mozilla

mercurial