Bug 441359: Check script and css loads against the classifier. r+sr=jonas, b3.1=beltzner
authorDave Camp <dcamp@mozilla.com>
Tue, 13 Jan 2009 23:13:48 -0800
changeset 22842 d4858eaeb4591ae7ea7a7bd39da10357be6e4b5c
parent 22841 edabde5fc73a4283848893755cd6adf4ab766b30
child 22843 213c9c6350cc4a5a73e66c262b8a72bac404c53c
push id453
push userdcamp@mozilla.com
push dateFri, 16 Jan 2009 10:01:19 +0000
bugs441359
milestone1.9.1b3pre
Bug 441359: Check script and css loads against the classifier. r+sr=jonas, b3.1=beltzner
build/pgo/server-locations.txt
content/base/src/nsObjectLoadingContent.cpp
content/base/src/nsScriptLoader.cpp
docshell/base/nsDocShell.cpp
docshell/base/nsDocShell.h
docshell/base/nsIChannelClassifier.idl
layout/style/nsCSSLoader.cpp
toolkit/components/url-classifier/tests/Makefile.in
toolkit/components/url-classifier/tests/mochitest/Makefile.in
toolkit/components/url-classifier/tests/mochitest/classifierFrame.html
toolkit/components/url-classifier/tests/mochitest/evil.css
toolkit/components/url-classifier/tests/mochitest/evil.js
toolkit/components/url-classifier/tests/mochitest/import.css
toolkit/components/url-classifier/tests/mochitest/test_classifier.html
--- a/build/pgo/server-locations.txt
+++ b/build/pgo/server-locations.txt
@@ -142,8 +142,13 @@ http://sectest1.example.org:80       pri
 http://sub.sectest2.example.org:80   privileged
 http://sectest2.example.org:80
 http://sub.sectest1.example.org:80
 
 https://sectest1.example.org:443       privileged
 https://sub.sectest2.example.org:443   privileged
 https://sectest2.example.org:443
 https://sub.sectest1.example.org:443
+
+#
+# Used while testing the url-classifier
+#
+http://malware.com:80
--- a/content/base/src/nsObjectLoadingContent.cpp
+++ b/content/base/src/nsObjectLoadingContent.cpp
@@ -1751,17 +1751,17 @@ nsObjectLoadingContent::Instantiate(nsIO
 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);
+  rv = classifier->Start(aChannel, PR_FALSE);
   if (rv == NS_ERROR_FACTORY_NOT_REGISTERED) {
     // no URI classifier, ignore this
     return NS_OK;
   }
   NS_ENSURE_SUCCESS(rv, rv);
 
   mClassifier = classifier;
 
--- a/content/base/src/nsScriptLoader.cpp
+++ b/content/base/src/nsScriptLoader.cpp
@@ -63,16 +63,18 @@
 #include "jscntxt.h"
 #include "nsContentUtils.h"
 #include "nsUnicharUtils.h"
 #include "nsAutoPtr.h"
 #include "nsIXPConnect.h"
 #include "nsContentErrors.h"
 #include "nsIParser.h"
 #include "nsThreadUtils.h"
+#include "nsIChannelClassifier.h"
+#include "nsDocShellCID.h"
 
 //////////////////////////////////////////////////////////////
 // Per-request data structure
 //////////////////////////////////////////////////////////////
 
 class nsScriptLoadRequest : public nsISupports {
 public:
   nsScriptLoadRequest(nsIScriptElement* aElement,
@@ -261,17 +263,31 @@ nsScriptLoader::StartLoad(nsScriptLoadRe
                                   NS_LITERAL_CSTRING("*/*"),
                                   PR_FALSE);
     httpChannel->SetReferrer(mDocument->GetDocumentURI());
   }
 
   rv = NS_NewStreamLoader(getter_AddRefs(loader), this);
   NS_ENSURE_SUCCESS(rv, rv);
 
-  return channel->AsyncOpen(loader, aRequest);
+  rv = channel->AsyncOpen(loader, aRequest);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  // Check the load against the URI classifier
+  nsCOMPtr<nsIChannelClassifier> classifier =
+    do_CreateInstance(NS_CHANNELCLASSIFIER_CONTRACTID);
+  if (classifier) {
+    rv = classifier->Start(channel, PR_TRUE);
+    if (NS_FAILED(rv)) {
+      channel->Cancel(rv);
+      return rv;
+    }
+  }
+
+  return NS_OK;
 }
 
 PRBool
 nsScriptLoader::PreloadURIComparator::Equals(const PreloadInfo &aPi,
                                              nsIURI * const &aURI) const
 {
   PRBool same;
   return NS_SUCCEEDED(aPi.mRequest->mURI->Equals(aURI, &same)) &&
--- a/docshell/base/nsDocShell.cpp
+++ b/docshell/base/nsDocShell.cpp
@@ -7793,17 +7793,17 @@ nsresult nsDocShell::DoChannelLoad(nsICh
 }
 
 nsresult
 nsDocShell::CheckClassifier(nsIChannel *aChannel)
 {
     nsRefPtr<nsClassifierCallback> classifier = new nsClassifierCallback();
     if (!classifier) return NS_ERROR_OUT_OF_MEMORY;
 
-    nsresult rv = classifier->Start(aChannel);
+    nsresult rv = classifier->Start(aChannel, PR_FALSE);
     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;
@@ -9684,20 +9684,22 @@ nsDocShell::IsOKToLoadURI(nsIURI* aURI)
         secMan &&
         NS_SUCCEEDED(secMan->CheckSameOriginURI(aURI, mLoadingURI, PR_FALSE));
 }
 
 //*****************************************************************************
 // nsClassifierCallback
 //*****************************************************************************
 
-NS_IMPL_ISUPPORTS3(nsClassifierCallback,
+NS_IMPL_ISUPPORTS5(nsClassifierCallback,
                    nsIChannelClassifier,
                    nsIURIClassifierCallback,
-                   nsIRunnable)
+                   nsIRunnable,
+                   nsIChannelEventSink,
+                   nsIInterfaceRequestor)
 
 NS_IMETHODIMP
 nsClassifierCallback::Run()
 {
     if (!mChannel) {
         return NS_OK;
     }
 
@@ -9712,17 +9714,17 @@ nsClassifierCallback::Run()
     // (this might happen after a redirect)
     PRUint32 status;
     channel->GetStatus(&status);
     if (NS_FAILED(status))
         return NS_OK;
 
     // Don't bother to run the classifier on a cached load that was
     // previously classified.
-    if (HasBeenClassified()) {
+    if (HasBeenClassified(channel)) {
         return NS_OK;
     }
 
     nsCOMPtr<nsIURI> uri;
     nsresult rv = channel->GetURI(getter_AddRefs(uri));
     NS_ENSURE_SUCCESS(rv, rv);
 
     // Don't bother checking certain types of URIs.
@@ -9804,20 +9806,20 @@ nsClassifierCallback::MarkEntryClassifie
         return;
     }
 
     cacheEntry->SetMetaDataElement("docshell:classified",
                                    NS_SUCCEEDED(status) ? "1" : nsnull);
 }
 
 PRBool
-nsClassifierCallback::HasBeenClassified()
+nsClassifierCallback::HasBeenClassified(nsIChannel *aChannel)
 {
     nsCOMPtr<nsICachingChannel> cachingChannel =
-        do_QueryInterface(mSuspendedChannel);
+        do_QueryInterface(aChannel);
     if (!cachingChannel) {
         return PR_FALSE;
     }
 
     // Only check the tag if we are loading from the cache without
     // validation.
     PRBool fromCache;
     if (NS_FAILED(cachingChannel->IsFromCache(&fromCache)) || !fromCache) {
@@ -9863,25 +9865,36 @@ nsClassifierCallback::OnClassifyComplete
         mSuspendedChannel->Resume();
         mSuspendedChannel = nsnull;
     }
 
     return NS_OK;
 }
 
 NS_IMETHODIMP
-nsClassifierCallback::Start(nsIChannel *aChannel)
+nsClassifierCallback::Start(nsIChannel *aChannel, PRBool aInstallListener)
 {
     mChannel = aChannel;
+
+    if (aInstallListener) {
+        nsresult rv = aChannel->GetNotificationCallbacks
+            (getter_AddRefs(mNotificationCallbacks));
+        NS_ENSURE_SUCCESS(rv, rv);
+
+        rv = aChannel->SetNotificationCallbacks
+            (static_cast<nsIInterfaceRequestor*>(this));
+        NS_ENSURE_SUCCESS(rv, rv);
+    }
+
     return Run();
 }
 
 NS_IMETHODIMP
 nsClassifierCallback::OnRedirect(nsIChannel *aOldChannel,
-                                nsIChannel *aNewChannel)
+                                 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;
@@ -9901,8 +9914,41 @@ nsClassifierCallback::Cancel()
     }
 
     if (mChannel) {
         mChannel = nsnull;
     }
 
     return NS_OK;
 }
+
+NS_IMETHODIMP
+nsClassifierCallback::OnChannelRedirect(nsIChannel *aOldChannel,
+                                        nsIChannel *aNewChannel,
+                                        PRUint32 aFlags)
+{
+    nsresult rv = OnRedirect(aOldChannel, aNewChannel);
+    NS_ENSURE_SUCCESS(rv, rv);
+
+    if (mNotificationCallbacks) {
+        nsCOMPtr<nsIChannelEventSink> sink =
+            do_GetInterface(mNotificationCallbacks);
+        if (sink) {
+            return sink->OnChannelRedirect(aOldChannel, aNewChannel, aFlags);
+        }
+    }
+
+    return NS_OK;
+}
+
+NS_IMETHODIMP
+nsClassifierCallback::GetInterface(const nsIID &aIID, void **aResult)
+{
+    if (aIID.Equals(NS_GET_IID(nsIChannelEventSink))) {
+        NS_ADDREF_THIS();
+        *aResult = static_cast<nsIChannelEventSink *>(this);
+        return NS_OK;
+    } else if (mNotificationCallbacks) {
+        return mNotificationCallbacks->GetInterface(aIID, aResult);
+    } else {
+        return NS_ERROR_NO_INTERFACE;
+    }
+}
--- a/docshell/base/nsDocShell.h
+++ b/docshell/base/nsDocShell.h
@@ -143,32 +143,37 @@ public:
     
 protected:
     virtual ~nsRefreshTimer();
 };
 
 class nsClassifierCallback : public nsIChannelClassifier
                            , public nsIURIClassifierCallback
                            , public nsIRunnable
+                           , public nsIChannelEventSink
+                           , public nsIInterfaceRequestor
 {
 public:
     nsClassifierCallback() {}
     ~nsClassifierCallback() {}
 
     NS_DECL_ISUPPORTS
     NS_DECL_NSICHANNELCLASSIFIER
     NS_DECL_NSIURICLASSIFIERCALLBACK
     NS_DECL_NSIRUNNABLE
+    NS_DECL_NSICHANNELEVENTSINK
+    NS_DECL_NSIINTERFACEREQUESTOR
 
 private:
     nsCOMPtr<nsIChannel> mChannel;
     nsCOMPtr<nsIChannel> mSuspendedChannel;
+    nsCOMPtr<nsIInterfaceRequestor> mNotificationCallbacks;
 
     void MarkEntryClassified(nsresult status);
-    PRBool HasBeenClassified();
+    PRBool HasBeenClassified(nsIChannel *aChannel);
 };
 
 //*****************************************************************************
 //***    nsDocShell
 //*****************************************************************************
 
 class nsDocShell : public nsDocLoader,
                    public nsIDocShell,
--- a/docshell/base/nsIChannelClassifier.idl
+++ b/docshell/base/nsIChannelClassifier.idl
@@ -41,33 +41,41 @@
 
 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)]
+[scriptable, uuid(1481c5b5-9f6e-4995-8fe3-2aad5c06440d)]
 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.
    *
+   * This method must be called immediately after AsyncOpen() has been called
+   * on the channel.
+   *
    * @param aChannel
    *        The channel to suspend.
+   * @param aInstallListener
+   *        If true, the classifier will install notification
+   *        callbacks to listen for redirects.  The classifier will
+   *        pass all notifications on to the channel's existing
+   *        notification callbacks.
    */
-  void start(in nsIChannel aChannel);
+  void start(in nsIChannel aChannel, in boolean aInstallListener);
 
   /**
    * 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
--- a/layout/style/nsCSSLoader.cpp
+++ b/layout/style/nsCSSLoader.cpp
@@ -74,16 +74,18 @@
 #include "nsICSSStyleSheet.h"
 #include "nsIStyleSheetLinkingElement.h"
 #include "nsICSSLoaderObserver.h"
 #include "nsICSSLoader.h"
 #include "nsICSSParser.h"
 #include "nsICSSImportRule.h"
 #include "nsThreadUtils.h"
 #include "nsGkAtoms.h"
+#include "nsDocShellCID.h"
+#include "nsIChannelClassifier.h"
 
 #ifdef MOZ_XUL
 #include "nsXULPrototypeCache.h"
 #endif
 
 #include "nsIMediaList.h"
 #include "nsIDOMStyleSheet.h"
 #include "nsIDOMCSSStyleSheet.h"
@@ -1456,16 +1458,29 @@ CSSLoaderImpl::LoadSheet(SheetLoadData* 
 #endif
 
   if (NS_FAILED(rv)) {
     LOG_ERROR(("  Failed to create stream loader"));
     SheetComplete(aLoadData, rv);
     return rv;
   }
 
+  // Check the load against the URI classifier
+  nsCOMPtr<nsIChannelClassifier> classifier =
+    do_CreateInstance(NS_CHANNELCLASSIFIER_CONTRACTID);
+  if (classifier) {
+    rv = classifier->Start(channel, PR_TRUE);
+    if (NS_FAILED(rv)) {
+      LOG_ERROR(("  Failed to classify URI"));
+      SheetComplete(aLoadData, rv);
+      channel->Cancel(rv);
+      return rv;
+    }
+  }
+
   if (!mLoadingDatas.Put(&key, aLoadData)) {
     LOG_ERROR(("  Failed to put data in loading table"));
     aLoadData->mIsCancelled = PR_TRUE;
     channel->Cancel(NS_ERROR_OUT_OF_MEMORY);
     SheetComplete(aLoadData, NS_ERROR_OUT_OF_MEMORY);
     return NS_ERROR_OUT_OF_MEMORY;
   }
   
--- a/toolkit/components/url-classifier/tests/Makefile.in
+++ b/toolkit/components/url-classifier/tests/Makefile.in
@@ -50,16 +50,20 @@ MOZILLA_INTERNAL_API = 1
 
 REQUIRES = \
 	string \
 	url-classifier \
 	xpcom \
 	necko \
 	$(NULL)
 
+# mochitest tests
+DIRS += mochitest \
+	$(NULL)
+
 # xpcshell tests
 XPCSHELL_TESTS=unit
 
 # simple c++ tests (no xpcom)
 CPPSRCS = \
 	TestUrlClassifierUtils.cpp \
 	$(NULL)
 
new file mode 100644
--- /dev/null
+++ b/toolkit/components/url-classifier/tests/mochitest/Makefile.in
@@ -0,0 +1,57 @@
+#
+# ***** 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) 2008
+# the Initial Developer. All Rights Reserved.
+#
+# Contributor(s):
+#
+# Alternatively, the contents of this file may be used under the terms of
+# either of 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 *****
+
+DEPTH		= ../../../../..
+topsrcdir	= @top_srcdir@
+srcdir		= @srcdir@
+VPATH		= @srcdir@
+relativesrcdir  = toolkit/components/url-classifier/tests/mochitest
+
+MODULE = test_url-classifier
+
+include $(DEPTH)/config/autoconf.mk
+include $(topsrcdir)/config/rules.mk
+
+_TEST_FILES = \
+		test_classifier.html \
+		classifierFrame.html \
+		evil.js \
+		evil.css \
+		import.css \
+		$(NULL)
+
+libs:: $(_TEST_FILES)
+	$(INSTALL) $(foreach f,$^,"$f") $(DEPTH)/_tests/testing/mochitest/tests/$(relativesrcdir)
new file mode 100644
--- /dev/null
+++ b/toolkit/components/url-classifier/tests/mochitest/classifierFrame.html
@@ -0,0 +1,34 @@
+<html> <head>
+<title></title>
+</head>
+
+<script type="text/javascript">
+var scriptItem = "untouched";
+
+function checkLoads() {
+  // Make sure the javascript did not load.
+  window.parent.is(scriptItem, "untouched", "Should not load bad javascript");
+
+  // Make sure the css did not load.
+  var elt = document.getElementById("styleCheck");
+  var style = document.defaultView.getComputedStyle(elt, "");
+  window.parent.isnot(style.visibility, "hidden", "Should not load bad css");
+
+  window.parent.SimpleTest.finish();
+}
+
+</script>
+
+<!-- Try loading from a malware javascript URI -->
+<script type="text/javascript" src="http://malware.com/tests/toolkit/components/url-classifier/tests/mochitest/evil.js"></script>
+
+<!-- Try loading from a malware css URI
+<link rel="stylesheet" type="text/css" href="http://malware.com/tests/toolkit/components/url-classifier/tests/mochitest/evil.css"></link>
+
+<!-- Try loading a marked-as-malware css through an @import from a clean URI -->
+<link rel="stylesheet" type="text/css" href="import.css"></link>
+
+<body onload="checkLoads()">
+The following should not be hidden:
+<div id="styleCheck">STYLE TEST</div>
+</body> </html>
new file mode 100644
--- /dev/null
+++ b/toolkit/components/url-classifier/tests/mochitest/evil.css
@@ -0,0 +1,1 @@
+#styleCheck { visibility: hidden; }
\ No newline at end of file
new file mode 100644
--- /dev/null
+++ b/toolkit/components/url-classifier/tests/mochitest/evil.js
@@ -0,0 +1,1 @@
+scriptItem = "loaded malware javascript!";
new file mode 100644
--- /dev/null
+++ b/toolkit/components/url-classifier/tests/mochitest/import.css
@@ -0,0 +1,1 @@
+@import url("http://malware.com/tests/docshell/test/classifierBad.css");
new file mode 100644
--- /dev/null
+++ b/toolkit/components/url-classifier/tests/mochitest/test_classifier.html
@@ -0,0 +1,81 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+  <title>Test the URI Classifier</title>
+  <script type="text/javascript" src="/MochiKit/MochiKit.js"></script>
+  <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<p id="display"></p>
+<div id="content" style="display: none">
+</div>
+<pre id="test">
+
+<script class="testbody" type="text/javascript">
+
+netscape.security.PrivilegeManager.enablePrivilege("UniversalXPConnect");
+var Cc = Components.classes;
+var Ci = Components.interfaces;
+
+// Add some URLs to the malware database.
+var testData = "malware.com/"
+var testUpdate =
+  "n:1000\ni:test-malware-simple\nad:1\n" +
+  "a:524:32:" + testData.length + "\n" +
+  testData;
+
+var dbService = Cc["@mozilla.org/url-classifier/dbservice;1"]
+                .getService(Ci.nsIUrlClassifierDBService);
+
+var numTries = 10;
+
+function doUpdate(update) {
+  var listener = {
+    QueryInterface: function(iid)
+    {
+      if (iid.equals(Ci.nsISupports) ||
+          iid.equals(Ci.nsIUrlClassifierUpdateObserver))
+        return this;
+      throw Cr.NS_ERROR_NO_INTERFACE;
+    },
+
+    updateUrlRequested: function(url) { },
+    streamFinished: function(status) { },
+    updateError: function(errorCode) {
+      ok(false, "Couldn't update classifier.");
+      SimpleTest.finish();
+    },
+    updateSuccess: function(requestedTimeout) {
+      document.getElementById("testFrame").src = "classifierFrame.html";
+    }
+  };
+
+  netscape.security.PrivilegeManager.enablePrivilege("UniversalXPConnect");
+
+  try {
+    dbService.beginUpdate(listener,
+                          "test-malware-simple", "");
+    dbService.beginStream("", "");
+    dbService.updateStream(update);
+    dbService.finishStream();
+    dbService.finishUpdate();
+  } catch(ex) {
+    // The DB service might already be updating.  Try again after a 5 seconds...
+    if (--numTries != 0) {
+      setTimeout(function() { doUpdate(update) }, 5000);
+      return;
+    }
+    throw ex;
+  }
+}
+
+doUpdate(testUpdate);
+
+SimpleTest.waitForExplicitFinish();
+
+</script>
+</pre>
+<iframe id="testFrame" onload=""></iframe>
+</body>
+</html>