Bug 1370843 - Provide correct triggeringPrincipal for dropLinks. r=smaug,tooru
authorChristoph Kerschbaumer <ckerschb@christophkerschbaumer.com>
Tue, 22 Aug 2017 10:07:03 +0200
changeset 376154 46d5697dc9e0b8ce53d5d02f7c5f228a5dab4914
parent 376153 7e6cd9dc49d54e193186c28fbe000da3630ca7e9
child 376155 2e4748827cda73771b02c8e9316e257505518a36
push id32376
push userkwierso@gmail.com
push dateWed, 23 Aug 2017 00:07:40 +0000
treeherdermozilla-central@64a45ee1731c [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerssmaug, tooru
bugs1370843
milestone57.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1370843 - Provide correct triggeringPrincipal for dropLinks. r=smaug,tooru
docshell/base/nsDocShellTreeOwner.cpp
dom/base/contentAreaDropListener.js
dom/base/nsIDroppedLinkHandler.idl
dom/interfaces/base/nsIBrowser.idl
dom/ipc/TabParent.cpp
dom/ipc/TabParent.h
toolkit/content/widgets/browser.xml
--- a/docshell/base/nsDocShellTreeOwner.cpp
+++ b/docshell/base/nsDocShellTreeOwner.cpp
@@ -987,17 +987,16 @@ nsDocShellTreeOwner::HandleEvent(nsIDOME
       nsIDroppedLinkItem** links;
       if (webnav &&
           NS_SUCCEEDED(handler->DropLinks(dragEvent, true, &linksCount, &links))) {
         if (linksCount >= 1) {
           nsCOMPtr<nsIWebBrowserChrome> webBrowserChrome = GetWebBrowserChrome();
           if (webBrowserChrome) {
             nsCOMPtr<nsITabChild> tabChild = do_QueryInterface(webBrowserChrome);
             if (tabChild) {
-              // Bug 1370843 - Explicitly pass triggeringPrincipal
               nsresult rv = tabChild->RemoteDropLinks(linksCount, links);
               for (uint32_t i = 0; i < linksCount; i++) {
                 NS_RELEASE(links[i]);
               }
               free(links);
               return rv;
             }
           }
--- a/dom/base/contentAreaDropListener.js
+++ b/dom/base/contentAreaDropListener.js
@@ -233,16 +233,25 @@ ContentAreaDropListener.prototype =
       }
     }
     if (aCount)
       aCount.value = links.length;
 
     return links;
   },
 
+  queryLinks: function(aDataTransfer, aCount)
+  {
+    let links = this._getDropLinks(aDataTransfer);
+    if (aCount) {
+      aCount.value = links.length;
+    }
+    return links;
+  },
+
   _eventTargetIsDisabled: function(aEvent)
   {
     let ownerDoc = aEvent.originalTarget.ownerDocument;
     if (!ownerDoc || !ownerDoc.defaultView)
       return false;
 
     return ownerDoc.defaultView
                    .QueryInterface(Components.interfaces.nsIInterfaceRequestor)
--- a/dom/base/nsIDroppedLinkHandler.idl
+++ b/dom/base/nsIDroppedLinkHandler.idl
@@ -1,15 +1,16 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "nsISupports.idl"
 #include "nsIPrincipal.idl"
 
+interface nsIDOMDataTransfer;
 interface nsIDOMDragEvent;
 
 [scriptable, uuid(69E14F91-2E09-4CA6-A511-A715C99A2804)]
 interface nsIDroppedLinkItem : nsISupports
 {
   /**
    * Returns the URL of the link.
    */
@@ -75,13 +76,23 @@ interface nsIDroppedLinkHandler : nsISup
    *    current document's security context (URI_INHERITS_SECURITY_CONTEXT).
    */
   void dropLinks(in nsIDOMDragEvent aEvent,
                  [optional] in boolean aDisallowInherit,
                  [optional] out unsigned long aCount,
                  [retval, array, size_is(aCount)] out nsIDroppedLinkItem aLinks);
 
   /**
+   * Given a dataTransfer, allows caller to determine and verify links being
+   * dragged. Since drag/drop performs a roundtrip of parent, child, parent,
+   * it allows the parent to verify that the child did not modify links
+   * being dropped.
+   */
+  void queryLinks(in nsIDOMDataTransfer aDataTransfer,
+                  [optional] out unsigned long aCount,
+                  [retval, array, size_is(aCount)] out nsIDroppedLinkItem aLinks);
+
+  /**
    * Given a drop event aEvent, determines the triggering principal for the
    * event and returns it.
    */
   nsIPrincipal getTriggeringPrincipal(in nsIDOMDragEvent aEvent);
 };
--- a/dom/interfaces/base/nsIBrowser.idl
+++ b/dom/interfaces/base/nsIBrowser.idl
@@ -1,14 +1,15 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 #include "nsISupports.idl"
 
 interface nsIFrameLoader;
+interface nsIPrincipal;
 
 [scriptable, uuid(14e5a0cb-e223-4202-95e8-fe53275193ea)]
 interface nsIBrowser : nsISupports
 {
   /**
    * Gets an optional frame loader that is "related" to this browser.
    * If this exists, then we should attempt to use the same content parent as
    * this frame loader for any new tab parents.  For example, view source
@@ -22,17 +23,18 @@ interface nsIBrowser : nsISupports
    * content area.
    *
    * @param linksCount length of links
    * @param links a flat array of url, name, and type for each link
    * @param triggeringPrincipal a principal that initiated loading
    *                            of the dropped links
    */
   void dropLinks(in unsigned long linksCount,
-                 [array, size_is(linksCount)] in wstring links);
+                 [array, size_is(linksCount)] in wstring links,
+                 in nsIPrincipal aTriggeringPrincipal);
 
   /**
    * Flags for controlling the behavior of swapBrowsers
    */
 
   /**
    * The default options. This is used for swapping browsers between windows
    */
--- a/dom/ipc/TabParent.cpp
+++ b/dom/ipc/TabParent.cpp
@@ -11,16 +11,17 @@
 #ifdef ACCESSIBILITY
 #include "mozilla/a11y/DocAccessibleParent.h"
 #include "nsAccessibilityService.h"
 #endif
 #include "mozilla/BrowserElementParent.h"
 #include "mozilla/dom/ContentBridgeParent.h"
 #include "mozilla/dom/ContentParent.h"
 #include "mozilla/dom/DataTransfer.h"
+#include "mozilla/dom/DataTransferItemList.h"
 #include "mozilla/dom/Event.h"
 #include "mozilla/dom/indexedDB/ActorsParent.h"
 #include "mozilla/dom/IPCBlobUtils.h"
 #include "mozilla/dom/PaymentRequestParent.h"
 #include "mozilla/EventStateManager.h"
 #include "mozilla/gfx/2D.h"
 #include "mozilla/gfx/DataSurfaceHelpers.h"
 #include "mozilla/gfx/GPUProcessManager.h"
@@ -96,16 +97,17 @@
 #include "ImageOps.h"
 #include "UnitTransforms.h"
 #include <algorithm>
 #include "mozilla/WebBrowserPersistDocumentParent.h"
 #include "nsIGroupedSHistory.h"
 #include "PartialSHistory.h"
 #include "ProcessPriorityManager.h"
 #include "nsString.h"
+#include "NullPrincipal.h"
 
 #ifdef XP_WIN
 #include "mozilla/plugins/PluginWidgetParent.h"
 #endif
 
 #if defined(XP_WIN) && defined(ACCESSIBILITY)
 #include "mozilla/a11y/AccessibleWrap.h"
 #include "mozilla/a11y/nsWinUtils.h"
@@ -533,22 +535,42 @@ TabParent::RecvSizeShellTo(const uint32_
   return IPC_OK();
 }
 
 mozilla::ipc::IPCResult
 TabParent::RecvDropLinks(nsTArray<nsString>&& aLinks)
 {
   nsCOMPtr<nsIBrowser> browser = do_QueryInterface(mFrameElement);
   if (browser) {
+    // Verify that links have not been modified by the child. If links have
+    // not been modified then it's safe to load those links using the
+    // SystemPrincipal. If they have been modified by web content, then
+    // we use a NullPrincipal which still allows to load web links.
+    bool loadUsingSystemPrincipal = true;
+    if (aLinks.Length() != mVerifyDropLinks.Length()) {
+      loadUsingSystemPrincipal = false;
+    }
     UniquePtr<const char16_t*[]> links;
     links = MakeUnique<const char16_t*[]>(aLinks.Length());
     for (uint32_t i = 0; i < aLinks.Length(); i++) {
+      if (loadUsingSystemPrincipal) {
+        if (!aLinks[i].Equals(mVerifyDropLinks[i])) {
+          loadUsingSystemPrincipal = false;
+        }
+      }
       links[i] = aLinks[i].get();
     }
-    browser->DropLinks(aLinks.Length(), links.get());
+    mVerifyDropLinks.Clear();
+    nsCOMPtr<nsIPrincipal> triggeringPrincipal;
+    if (loadUsingSystemPrincipal) {
+      triggeringPrincipal = nsContentUtils::GetSystemPrincipal();
+    } else {
+      triggeringPrincipal = NullPrincipal::Create();
+    }
+    browser->DropLinks(aLinks.Length(), links.get(), triggeringPrincipal);
   }
   return IPC_OK();
 }
 
 mozilla::ipc::IPCResult
 TabParent::RecvEvent(const RemoteDOMEvent& aEvent)
 {
   nsCOMPtr<nsIDOMEvent> event = do_QueryInterface(aEvent.mEvent);
@@ -1169,24 +1191,101 @@ TabParent::GetLayoutDeviceToCSSScale()
   nsIDocument* doc = (content ? content->OwnerDoc() : nullptr);
   nsIPresShell* shell = (doc ? doc->GetShell() : nullptr);
   nsPresContext* ctx = (shell ? shell->GetPresContext() : nullptr);
   return LayoutDeviceToCSSScale(ctx
     ? (float)ctx->AppUnitsPerDevPixel() / nsPresContext::AppUnitsPerCSSPixel()
     : 0.0f);
 }
 
+bool
+TabParent::QueryDropLinksForVerification()
+{
+  // Before sending the dragEvent, we query the links being dragged and
+  // store them on the parent, to make sure the child can not modify links.
+  nsCOMPtr<nsIDragSession> dragSession = nsContentUtils::GetDragSession();
+  if (!dragSession) {
+    NS_WARNING("No dragSession to query links for verification");
+    return false;
+  }
+
+  nsCOMPtr<nsIDOMDataTransfer> initialDataTransfer;
+  dragSession->GetDataTransfer(getter_AddRefs(initialDataTransfer));
+  if (!initialDataTransfer) {
+    NS_WARNING("No initialDataTransfer to query links for verification");
+    return false;
+  }
+
+  nsCOMPtr<nsIDroppedLinkHandler> dropHandler =
+    do_GetService("@mozilla.org/content/dropped-link-handler;1");
+  if (!dropHandler) {
+    NS_WARNING("No dropHandler to query links for verification");
+    return false;
+  }
+
+  // No more than one drop event can happen simultaneously; reset the link
+  // verification array and store all links that are being dragged.
+  mVerifyDropLinks.Clear();
+
+  uint32_t linksCount = 0;
+  nsIDroppedLinkItem** droppedLinkedItems = nullptr;
+  dropHandler->QueryLinks(initialDataTransfer,
+                          &linksCount, &droppedLinkedItems);
+
+  // Since the entire event is cancelled if one of the links is invalid,
+  // we can store all links on the parent side without any prior
+  // validation checks.
+  nsresult rv = NS_OK;
+  for (uint32_t i = 0; i < linksCount; i++) {
+    nsString tmp;
+    rv = droppedLinkedItems[i]->GetUrl(tmp);
+    if (NS_FAILED(rv)) {
+      NS_WARNING("Failed to query url for verification");
+      break;
+    }
+    mVerifyDropLinks.AppendElement(tmp);
+
+    rv = droppedLinkedItems[i]->GetName(tmp);
+    if (NS_FAILED(rv)) {
+      NS_WARNING("Failed to query name for verification");
+      break;
+    }
+    mVerifyDropLinks.AppendElement(tmp);
+
+    rv = droppedLinkedItems[i]->GetType(tmp);
+    if (NS_FAILED(rv)) {
+      NS_WARNING("Failed to query type for verification");
+      break;
+    }
+    mVerifyDropLinks.AppendElement(tmp);
+  }
+  for (uint32_t i = 0; i < linksCount; i++) {
+    NS_IF_RELEASE(droppedLinkedItems[i]);
+  }
+  free(droppedLinkedItems);
+  if (NS_FAILED(rv)) {
+    mVerifyDropLinks.Clear();
+    return false;
+  }
+  return true;
+}
+
 void
 TabParent::SendRealDragEvent(WidgetDragEvent& aEvent, uint32_t aDragAction,
                              uint32_t aDropEffect)
 {
   if (mIsDestroyed || !mIsReadyToHandleInputEvents) {
     return;
   }
   aEvent.mRefPoint += GetChildProcessOffset();
+  if (aEvent.mMessage == eDrop) {
+    if (!QueryDropLinksForVerification()) {
+      return;
+    }
+  }
   DebugOnly<bool> ret =
     PBrowserParent::SendRealDragEvent(aEvent, aDragAction, aDropEffect);
   NS_WARNING_ASSERTION(ret, "PBrowserParent::SendRealDragEvent() failed");
   MOZ_ASSERT(!ret || aEvent.HasBeenPostedToRemoteProcess());
 }
 
 LayoutDevicePoint
 TabParent::AdjustTapToChildWidget(const LayoutDevicePoint& aPoint)
--- a/dom/ipc/TabParent.h
+++ b/dom/ipc/TabParent.h
@@ -748,16 +748,22 @@ private:
   uint32_t mCustomCursorHotspotX, mCustomCursorHotspotY;
 
   // True if the cursor changes from the TabChild should change the widget
   // cursor.  This happens whenever the cursor is in the tab's region.
   bool mTabSetsCursor;
 
   bool mHasContentOpener;
 
+  // When dropping links we perform a roundtrip from
+  // Parent (SendRealDragEvent) -> Child -> Parent (RecvDropLinks)
+  // and have to ensure that the child did not modify links to be loaded.
+  bool QueryDropLinksForVerification();
+  nsTArray<nsString> mVerifyDropLinks;
+
 #ifdef DEBUG
   int32_t mActiveSupressDisplayportCount;
 #endif
 
   ShowInfo GetShowInfo();
 
 private:
   // This is used when APZ needs to find the TabParent associated with a layer
--- a/toolkit/content/widgets/browser.xml
+++ b/toolkit/content/widgets/browser.xml
@@ -1579,29 +1579,30 @@
                                     aPrintProgressListener);
           ]]>
         </body>
       </method>
 
       <method name="dropLinks">
         <parameter name="aLinksCount"/>
         <parameter name="aLinks"/>
+        <parameter name="aTriggeringPrincipal"/>
         <body><![CDATA[
           if (!this.droppedLinkHandler) {
             return false;
           }
           let links = [];
           for (let i = 0; i < aLinksCount; i += 3) {
             links.push({
               url: aLinks[i],
               name: aLinks[i + 1],
               type: aLinks[i + 2],
             });
           }
-          this.droppedLinkHandler(null, links);
+          this.droppedLinkHandler(null, links, aTriggeringPrincipal);
           return true;
         ]]></body>
       </method>
     </implementation>
 
     <handlers>
       <handler event="keypress" keycode="VK_F7" group="system">
         <![CDATA[