Bug 394485: Prevent loading blocklisted urls loaded from <object> tags. r=jonas, sr=biesi, blocking1.9=stuart
authordcamp@mozilla.com
Wed, 12 Mar 2008 14:52:47 -0700
changeset 12968 c591c1b71935ef631cd9ec64d5ae2a6946f5bafc
parent 12967 188b3f5bd6c132183207d1c03a1c9805e2eb77cf
child 12969 dccdeb77e86101e0a1fdebc542219afd13a5989e
push idunknown
push userunknown
push dateunknown
reviewersjonas, biesi
bugs394485
milestone1.9b5pre
Bug 394485: Prevent loading blocklisted urls loaded from <object> tags. r=jonas, sr=biesi, blocking1.9=stuart
content/base/src/nsObjectLoadingContent.cpp
content/base/src/nsObjectLoadingContent.h
docshell/base/Makefile.in
docshell/base/nsDocShell.cpp
docshell/base/nsDocShell.h
docshell/base/nsIChannelClassifier.idl
docshell/build/nsDocShellCID.h
docshell/build/nsDocShellModule.cpp
--- a/content/base/src/nsObjectLoadingContent.cpp
+++ b/content/base/src/nsObjectLoadingContent.cpp
@@ -354,16 +354,19 @@ NS_IMETHODIMP
 nsObjectLoadingContent::OnStartRequest(nsIRequest *aRequest, nsISupports *aContext)
 {
   if (aRequest != mChannel) {
     // This is a bit of an edge case - happens when a new load starts before the
     // previous one got here
     return NS_BINDING_ABORTED;
   }
 
+  // We're done with the classifier
+  mClassifier = nsnull;
+
   AutoNotifier notifier(this, PR_TRUE);
 
   if (!IsSuccessfulRequest(aRequest)) {
     LOG(("OBJLC [%p]: OnStartRequest: Request failed\n", this));
     Fallback(PR_FALSE);
     return NS_BINDING_ABORTED;
   }
 
@@ -799,16 +802,20 @@ nsObjectLoadingContent::OnChannelRedirec
                                           nsIChannel *aNewChannel,
                                           PRUint32    aFlags)
 {
   // If we're already busy with a new load, cancel the redirect
   if (aOldChannel != mChannel) {
     return NS_BINDING_ABORTED;
   }
 
+  if (mClassifier) {
+    mClassifier->OnRedirect(aOldChannel, aNewChannel);
+  }
+
   mChannel = aNewChannel;
   return NS_OK;
 }
 
 // <public>
 PRInt32
 nsObjectLoadingContent::ObjectState() const
 {
@@ -959,16 +966,22 @@ nsObjectLoadingContent::LoadObject(nsIUR
   if (!doc) {
     return NS_OK;
   }
 
   // From here on, we will always change the content. This means that a
   // possibly-loading channel should be aborted.
   if (mChannel) {
     LOG(("OBJLC [%p]: Cancelling existing load\n", this));
+
+    if (mClassifier) {
+      mClassifier->Cancel();
+      mClassifier = nsnull;
+    }
+
     // These three statements are carefully ordered:
     // - onStopRequest should get a channel whose status is the same as the
     //   status argument
     // - onStopRequest must get a non-null channel
     mChannel->Cancel(NS_BINDING_ABORTED);
     if (mFinalListener) {
       // NOTE: Since mFinalListener is only set in onStartRequest, which takes
       // care of calling mFinalListener->OnStartRequest, mFinalListener is only
@@ -1219,16 +1232,23 @@ nsObjectLoadingContent::LoadObject(nsIUR
       SetExecutionPolicy(nsIScriptChannel::EXECUTE_NORMAL);
   }
 
   // AsyncOpen can fail if a file does not exist.
   // Show fallback content in that case.
   rv = chan->AsyncOpen(this, nsnull);
   if (NS_SUCCEEDED(rv)) {
     LOG(("OBJLC [%p]: Channel opened.\n", this));
+
+    rv = CheckClassifier(chan);
+    if (NS_FAILED(rv)) {
+      chan->Cancel(rv);
+      return rv;
+    }
+
     mChannel = chan;
     mType = eType_Loading;
   }
   return NS_OK;
 }
 
 PRUint32
 nsObjectLoadingContent::GetCapabilities() const
@@ -1630,16 +1650,36 @@ nsObjectLoadingContent::Instantiate(nsIO
 
   // We'll always have a type or a URI by the time we get here
   NS_ASSERTION(aURI || !typeToUse.IsEmpty(), "Need a URI or a type");
   LOG(("OBJLC [%p]: Calling [%p]->Instantiate(<%s>, %p)\n", this, aFrame,
        typeToUse.get(), aURI));
   return aFrame->Instantiate(typeToUse.get(), aURI);
 }
 
+nsresult
+nsObjectLoadingContent::CheckClassifier(nsIChannel *aChannel)
+{
+  nsresult rv;
+  nsCOMPtr<nsIChannelClassifier> classifier =
+    do_CreateInstance(NS_CHANNELCLASSIFIER_CONTRACTID, &rv);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  rv = classifier->Start(aChannel);
+  if (rv == NS_ERROR_FACTORY_NOT_REGISTERED) {
+    // no URI classifier, ignore this
+    return NS_OK;
+  }
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  mClassifier = classifier;
+
+  return NS_OK;
+}
+
 /* static */ PRBool
 nsObjectLoadingContent::ShouldShowDefaultPlugin(nsIContent* aContent,
                                                 const nsCString& aContentType)
 {
   if (nsContentUtils::GetBoolPref("plugin.default_plugin_disabled", PR_FALSE)) {
     return PR_FALSE;
   }
 
--- a/content/base/src/nsObjectLoadingContent.h
+++ b/content/base/src/nsObjectLoadingContent.h
@@ -47,16 +47,17 @@
 
 #include "nsImageLoadingContent.h"
 #include "nsIStreamListener.h"
 #include "nsFrameLoader.h"
 #include "nsIInterfaceRequestor.h"
 #include "nsIChannelEventSink.h"
 #include "nsIObjectLoadingContent.h"
 #include "nsIRunnable.h"
+#include "nsIChannelClassifier.h"
 
 struct nsAsyncInstantiateEvent;
 class  AutoNotifier;
 class  AutoFallback;
 class  AutoSetInstantiatingToFalse;
 
 /**
  * INVARIANTS OF THIS CLASS
@@ -299,16 +300,23 @@ class nsObjectLoadingContent : public ns
      * GetFrame()->Instantiate() in that it ensures that the URI will
      * be non-null, and that a MIME type will be passed. Note that
      * this can cause the frame to be deleted while we're
      * instantiating the plugin.
      */
     nsresult Instantiate(nsIObjectFrame* aFrame, const nsACString& aMIMEType, nsIURI* aURI);
 
     /**
+     * Check the channel load against the URI classifier service (if it
+     * exists).  The channel will be suspended until the classification is
+     * complete.
+     */
+    nsresult CheckClassifier(nsIChannel *aChannel);
+
+    /**
      * Whether to treat this content as a plugin, even though we can't handle
      * the type. This function impl should match the checks in the plugin host.
      * aContentType is the MIME type we ended up with.
      */
     static PRBool ShouldShowDefaultPlugin(nsIContent* aContent,
                                           const nsCString& aContentType);
 
     enum PluginSupportState {
@@ -375,16 +383,21 @@ class nsObjectLoadingContent : public ns
      * Non-null between asyncOpen and onStopRequest.
      */
     nsIChannel*                 mChannel;
 
     // The data we were last asked to load
     nsCOMPtr<nsIURI>            mURI;
 
     /**
+     * Suspends/resumes channels based on the URI classifier.
+     */
+    nsCOMPtr<nsIChannelClassifier> mClassifier;
+
+    /**
      * Type of the currently-loaded content.
      */
     ObjectType                  mType          : 16;
 
     /**
      * Whether we are about to call instantiate on our frame. If we aren't,
      * SetFrame needs to asynchronously call Instantiate.
      */
--- a/docshell/base/Makefile.in
+++ b/docshell/base/Makefile.in
@@ -109,16 +109,17 @@ XPIDLSRCS	= \
 		nsIWebNavigationInfo.idl        \
 		nsIContentViewer.idl		\
 		nsIContentViewerEdit.idl	\
 		nsIContentViewerFile.idl	\
 		nsIURIFixup.idl			\
 		nsIEditorDocShell.idl		\
 		nsIWebPageDescriptor.idl	\
 		nsIURIClassifier.idl		\
+		nsIChannelClassifier.idl	\
 		nsIDownloadHistory.idl \
 		$(NULL)
 
 EXPORTS		= nsDocShellLoadTypes.h
 
 CPPSRCS		= \
 		nsDocShell.cpp		\
 		nsWebShell.cpp		\
--- a/docshell/base/nsDocShell.cpp
+++ b/docshell/base/nsDocShell.cpp
@@ -4953,22 +4953,17 @@ nsDocShell::OnRedirectStateChange(nsICha
     NS_ASSERTION(aStateFlags & STATE_REDIRECTING,
                  "Calling OnRedirectStateChange when there is no redirect");
     if (!(aStateFlags & STATE_IS_DOCUMENT))
         return; // not a toplevel document
 
     // If this load is being checked by the URI classifier, we need to
     // query the classifier again for the new URI.
     if (mClassifier) {
-        mClassifier->SetChannel(aNewChannel);
-
-        // we call the nsClassifierCallback:Run() from the main loop to
-        // give the channel a chance to AsyncOpen() the channel before
-        // we suspend it.
-        NS_DispatchToCurrentThread(mClassifier);
+        mClassifier->OnRedirect(aOldChannel, aNewChannel);
     }
 
     nsCOMPtr<nsIGlobalHistory3> history3(do_QueryInterface(mGlobalHistory));
     nsresult result = NS_ERROR_NOT_IMPLEMENTED;
     if (history3) {
         // notify global history of this redirect
         result = history3->AddDocumentRedirect(aOldChannel, aNewChannel,
                                                aRedirectFlags, !IsFrame());
@@ -7498,18 +7493,17 @@ nsresult nsDocShell::DoChannelLoad(nsICh
 }
 
 nsresult
 nsDocShell::CheckClassifier(nsIChannel *aChannel)
 {
     nsRefPtr<nsClassifierCallback> classifier = new nsClassifierCallback();
     if (!classifier) return NS_ERROR_OUT_OF_MEMORY;
 
-    classifier->SetChannel(aChannel);
-    nsresult rv = classifier->Run();
+    nsresult rv = classifier->Start(aChannel);
     if (rv == NS_ERROR_FACTORY_NOT_REGISTERED ||
         rv == NS_ERROR_NOT_AVAILABLE) {
         // no URI classifier => ignored cases
         return NS_OK;
     }
     NS_ENSURE_SUCCESS(rv, rv);
 
     mClassifier = classifier;
@@ -9321,19 +9315,20 @@ nsDocShell::IsOKToLoadURI(nsIURI* aURI)
         secMan &&
         NS_SUCCEEDED(secMan->CheckSameOriginURI(aURI, mLoadingURI, PR_FALSE));
 }
 
 //*****************************************************************************
 // nsClassifierCallback
 //*****************************************************************************
 
-NS_IMPL_THREADSAFE_ISUPPORTS2(nsClassifierCallback,
-                              nsIURIClassifierCallback,
-                              nsIRunnable)
+NS_IMPL_ISUPPORTS3(nsClassifierCallback,
+                   nsIChannelClassifier,
+                   nsIURIClassifierCallback,
+                   nsIRunnable)
 
 NS_IMETHODIMP
 nsClassifierCallback::Run()
 {
     if (!mChannel) {
         return NS_OK;
     }
 
@@ -9492,25 +9487,47 @@ nsClassifierCallback::OnClassifyComplete
 #endif
         mSuspendedChannel->Resume();
         mSuspendedChannel = nsnull;
     }
 
     return NS_OK;
 }
 
-void
+NS_IMETHODIMP
+nsClassifierCallback::Start(nsIChannel *aChannel)
+{
+    mChannel = aChannel;
+    return Run();
+}
+
+NS_IMETHODIMP
+nsClassifierCallback::OnRedirect(nsIChannel *aOldChannel,
+                                nsIChannel *aNewChannel)
+{
+    mChannel = aNewChannel;
+
+    // we call the Run() from the main loop to give the channel a
+    // chance to AsyncOpen() before we suspend it.
+    NS_DispatchToCurrentThread(this);
+
+    return NS_OK;
+}
+
+NS_IMETHODIMP
 nsClassifierCallback::Cancel()
 {
     if (mSuspendedChannel) {
 #ifdef DEBUG
         PR_LOG(gDocShellLog, PR_LOG_DEBUG,
                ("nsClassifierCallback[%p]: resuming channel %p from Cancel()",
                 this, mSuspendedChannel.get()));
 #endif
         mSuspendedChannel->Resume();
         mSuspendedChannel = nsnull;
     }
 
     if (mChannel) {
         mChannel = nsnull;
     }
-}
+
+    return NS_OK;
+}
--- a/docshell/base/nsDocShell.h
+++ b/docshell/base/nsDocShell.h
@@ -101,16 +101,17 @@
 #include "nsIHttpChannel.h"
 #include "nsDocShellTransferableHooks.h"
 #include "nsIAuthPromptProvider.h"
 #include "nsISecureBrowserUI.h"
 #include "nsIObserver.h"
 #include "nsDocShellLoadTypes.h"
 #include "nsPIDOMEventTarget.h"
 #include "nsIURIClassifier.h"
+#include "nsIChannelClassifier.h"
 
 class nsIScrollableView;
 
 /* load commands were moved to nsIDocShell.h */
 /* load types were moved to nsDocShellLoadTypes.h */
 
 /* internally used ViewMode types */
 enum ViewMode {
@@ -137,31 +138,29 @@ public:
     PRInt32               mDelay;
     PRPackedBool          mRepeat;
     PRPackedBool          mMetaRefresh;
     
 protected:
     virtual ~nsRefreshTimer();
 };
 
-class nsClassifierCallback : public nsIURIClassifierCallback
+class nsClassifierCallback : public nsIChannelClassifier
+                           , public nsIURIClassifierCallback
                            , public nsIRunnable
 {
 public:
     nsClassifierCallback() {}
     ~nsClassifierCallback() {}
 
     NS_DECL_ISUPPORTS
+    NS_DECL_NSICHANNELCLASSIFIER
     NS_DECL_NSIURICLASSIFIERCALLBACK
     NS_DECL_NSIRUNNABLE
 
-    void SetChannel(nsIChannel * aChannel)
-        { mChannel = aChannel; }
-
-    void Cancel();
 private:
     nsCOMPtr<nsIChannel> mChannel;
     nsCOMPtr<nsIChannel> mSuspendedChannel;
 
     void MarkEntryClassified(nsresult status);
     PRBool HasBeenClassified();
 };
 
new file mode 100644
--- /dev/null
+++ b/docshell/base/nsIChannelClassifier.idl
@@ -0,0 +1,83 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ * vim: ft=cpp tw=78 sw=4 et ts=4 sts=4 cin
+ * ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is mozilla.org code.
+ *
+ * The Initial Developer of the Original Code is
+ * Mozilla Corporation.
+ * Portions created by the Initial Developer are Copyright (C) 2007
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ *   Dave Camp <dcamp@mozilla.com>
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+#include "nsISupports.idl"
+
+interface nsIChannel;
+
+/**
+ * An nsIChannelClassifier object checks a channel's URI against the
+ * URI classifier service, and cancels the channel before OnStartRequest
+ * if it is found on a blacklist.
+ */
+[scriptable, uuid(d17f8f74-d403-4dea-b124-3ace5dbe44dc)]
+interface nsIChannelClassifier : nsISupports
+{
+  /**
+   * Checks a channel against the URI classifier service (if it exists).
+   *
+   * The channel will be suspended while the classifier is checked.  The
+   * channel may be cancelled with an error code determined by the classifier
+   * before it is resumed.
+   *
+   * If there is no URI classifier service, NS_ERROR_FACTORY_NOT_REGISTERED
+   * will be returned.
+   *
+   * @param aChannel
+   *        The channel to suspend.
+   */
+  void start(in nsIChannel aChannel);
+
+  /**
+   * Notify the classifier that the channel was redirected.  The new channel
+   * will be suspended pending a new classifier lookup.
+   *
+   * @param aOldChannel
+   *        The channel that's being redirected.
+   * @param aNewChannel
+   *        The new channel. This channel is not opened yet.
+   */
+  void onRedirect(in nsIChannel aOldChannel, in nsIChannel aNewChannel);
+
+  /**
+   * Cancel an existing query.  If a channel has been suspended, it will
+   * be resumed.
+   */
+  void cancel();
+};
--- a/docshell/build/nsDocShellCID.h
+++ b/docshell/build/nsDocShellCID.h
@@ -62,16 +62,25 @@
 
 /**
  * Contract ID for a service implementing nsIURIClassifier that identifies
  * phishing and malware sites.
  */
 #define NS_URICLASSIFIERSERVICE_CONTRACTID "@mozilla.org/uriclassifierservice"
 
 /**
+ * Class and contract ID for an nsIChannelClassifier implementation for
+ * checking a channel load against the URI classifier service.
+ */
+#define NS_CHANNELCLASSIFIER_CID \
+ { 0xce02d538, 0x0217, 0x47a3,{0xa5, 0x89, 0xb5, 0x17, 0x90, 0xfd, 0xd8, 0xce}}
+
+#define NS_CHANNELCLASSIFIER_CONTRACTID "@mozilla.org/channelclassifier"
+
+/**
  * An observer service topic that can be listened to to catch creation
  * of content browsing areas (both toplevel ones and subframes).  The
  * subject of the notification will be the nsIWebNavigation being
  * created.  At this time the additional data wstring is not defined
  * to be anything in particular.
  */
 #define NS_WEBNAVIGATION_CREATE "webnavigation-create"
 
--- a/docshell/build/nsDocShellModule.cpp
+++ b/docshell/build/nsDocShellModule.cpp
@@ -95,16 +95,17 @@ Shutdown(nsIModule* aSelf)
   nsSHEntry::Shutdown();
   gInitialized = PR_FALSE;
 }
 
 // docshell
 NS_GENERIC_FACTORY_CONSTRUCTOR_INIT(nsWebShell, Init)
 NS_GENERIC_FACTORY_CONSTRUCTOR(nsDefaultURIFixup)
 NS_GENERIC_FACTORY_CONSTRUCTOR_INIT(nsWebNavigationInfo, Init)
+NS_GENERIC_FACTORY_CONSTRUCTOR(nsClassifierCallback)
 
 // uriloader
 NS_GENERIC_FACTORY_CONSTRUCTOR(nsURILoader)
 NS_GENERIC_FACTORY_CONSTRUCTOR_INIT(nsDocLoader, Init)
 NS_GENERIC_FACTORY_CONSTRUCTOR_INIT(nsOSHelperAppService, Init)
 NS_GENERIC_FACTORY_CONSTRUCTOR(nsExternalProtocolHandler)
 NS_GENERIC_FACTORY_CONSTRUCTOR_INIT(nsPrefetchService, Init)
 NS_GENERIC_FACTORY_SINGLETON_CONSTRUCTOR(nsOfflineCacheUpdateService,
@@ -144,16 +145,22 @@ static const nsModuleComponentInfo gDocS
       NS_URIFIXUP_CONTRACTID,
       nsDefaultURIFixupConstructor
     },
     { "Webnavigation info service",
       NS_WEBNAVIGATION_INFO_CID,
       NS_WEBNAVIGATION_INFO_CONTRACTID,
       nsWebNavigationInfoConstructor
     },
+    {
+      "Channel classifier",
+      NS_CHANNELCLASSIFIER_CID,
+      NS_CHANNELCLASSIFIER_CONTRACTID,
+      nsClassifierCallbackConstructor
+    },
 
     // about redirector
     { "about:config",
       NS_ABOUT_REDIRECTOR_MODULE_CID,
       NS_ABOUT_MODULE_CONTRACTID_PREFIX "config",
       nsAboutRedirector::Create
     },
 #ifdef MOZ_CRASHREPORTER