Bug 693940: Restrict SVG-as-an-image to being able to load (local) URIs that have either the URI_INHERITS_SECURITY_CONTEXT or URI_LOADABLE_BY_SUBSUMERS flags. r=bz
authorDaniel Holbert <dholbert@cs.stanford.edu>
Mon, 07 Nov 2011 13:45:42 -0800
changeset 80205 2da841d9692593db16236bba80ab0c6f53c49843
parent 80204 e804728b128b5ddca05ce09bfee4ae7b49be466d
child 80206 11ed523f1ea2df581b9f8913478d480721c658b9
push id323
push userrcampbell@mozilla.com
push dateTue, 15 Nov 2011 21:58:36 +0000
treeherderfx-team@3ea216303184 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersbz
bugs693940
milestone11.0a1
Bug 693940: Restrict SVG-as-an-image to being able to load (local) URIs that have either the URI_INHERITS_SECURITY_CONTEXT or URI_LOADABLE_BY_SUBSUMERS flags. r=bz
content/base/src/nsDataDocumentContentPolicy.cpp
layout/reftests/svg/as-image/img-blobBuilder-1.html
layout/reftests/svg/as-image/img-blobBuilder-2.html
layout/reftests/svg/as-image/reftest.list
--- a/content/base/src/nsDataDocumentContentPolicy.cpp
+++ b/content/base/src/nsDataDocumentContentPolicy.cpp
@@ -46,16 +46,27 @@
 #include "nsScriptSecurityManager.h"
 #include "nsIDocument.h"
 #include "nsINode.h"
 #include "nsIDOMWindow.h"
 #include "nsIDOMDocument.h"
 
 NS_IMPL_ISUPPORTS1(nsDataDocumentContentPolicy, nsIContentPolicy)
 
+// Helper method for ShouldLoad()
+// Checks a URI for the given flags.  Returns true if the URI has the flags,
+// and false if not (or if we weren't able to tell).
+static bool
+HasFlags(nsIURI* aURI, PRUint32 aURIFlags)
+{
+  bool hasFlags;
+  nsresult rv = NS_URIChainHasFlags(aURI, aURIFlags, &hasFlags);
+  return NS_SUCCEEDED(rv) && hasFlags;
+}
+
 NS_IMETHODIMP
 nsDataDocumentContentPolicy::ShouldLoad(PRUint32 aContentType,
                                         nsIURI *aContentLocation,
                                         nsIURI *aRequestingLocation,
                                         nsISupports *aRequestingContext,
                                         const nsACString &aMimeGuess,
                                         nsISupports *aExtra,
                                         PRInt16 *aDecision)
@@ -82,43 +93,48 @@ nsDataDocumentContentPolicy::ShouldLoad(
 
   // Nothing else is OK to load for data documents
   if (doc->IsLoadedAsData()) {
     *aDecision = nsIContentPolicy::REJECT_TYPE;
     return NS_OK;
   }
 
   if (doc->IsBeingUsedAsImage()) {
-    // Allow local resources for SVG-as-an-image documents, but disallow
-    // everything else, to prevent data leakage
-    bool hasFlags;
-    nsresult rv = NS_URIChainHasFlags(aContentLocation,
-                                      nsIProtocolHandler::URI_IS_LOCAL_RESOURCE,
-                                      &hasFlags);
-    if (NS_FAILED(rv) || !hasFlags) {
-      // resource is not local (or we couldn't tell) - reject!
+    // We only allow SVG images to load content from URIs that are local and
+    // also satisfy one of the following conditions:
+    //  - URI inherits security context, e.g. data URIs
+    //   OR
+    //  - URI loadable by subsumers, e.g. moz-filedata URIs
+    // Any URI that doesn't meet these requirements will be rejected below.
+    if (!HasFlags(aContentLocation,
+                  nsIProtocolHandler::URI_IS_LOCAL_RESOURCE) ||
+        (!HasFlags(aContentLocation,
+                   nsIProtocolHandler::URI_INHERITS_SECURITY_CONTEXT) &&
+         !HasFlags(aContentLocation,
+                   nsIProtocolHandler::URI_LOADABLE_BY_SUBSUMERS))) {
       *aDecision = nsIContentPolicy::REJECT_TYPE;
 
-      // report error, if we can.
+      // Report error, if we can.
       if (node) {
         nsIPrincipal* requestingPrincipal = node->NodePrincipal();
         nsRefPtr<nsIURI> principalURI;
-        rv = requestingPrincipal->GetURI(getter_AddRefs(principalURI));
+        nsresult rv =
+          requestingPrincipal->GetURI(getter_AddRefs(principalURI));
         if (NS_SUCCEEDED(rv) && principalURI) {
           nsScriptSecurityManager::ReportError(
             nsnull, NS_LITERAL_STRING("CheckSameOriginError"), principalURI,
             aContentLocation);
         }
       }
     } else if (aContentType == nsIContentPolicy::TYPE_IMAGE &&
                doc->GetDocumentURI()) {
       // Check for (& disallow) recursive image-loads
       bool isRecursiveLoad;
-      rv = aContentLocation->EqualsExceptRef(doc->GetDocumentURI(),
-                                             &isRecursiveLoad);
+      nsresult rv = aContentLocation->EqualsExceptRef(doc->GetDocumentURI(),
+                                                      &isRecursiveLoad);
       if (NS_FAILED(rv) || isRecursiveLoad) {
         NS_WARNING("Refusing to recursively load image");
         *aDecision = nsIContentPolicy::REJECT_TYPE;
       }
     }
     return NS_OK;
   }
 
new file mode 100644
--- /dev/null
+++ b/layout/reftests/svg/as-image/img-blobBuilder-1.html
@@ -0,0 +1,37 @@
+<!DOCTYPE html>
+<!-- This test checks to be sure we can render SVG-as-an-image
+     from a MozBlobBuilder-generated 'moz-filedata' URI. -->
+<html class="reftest-wait">
+<head>
+  <script>
+    function go() {
+      // Generate a moz-filedata URL encoding of an SVG document
+      var filedataURL = generateMozFiledataURL();
+
+      // Tell our img element to render the URL
+      var img = document.getElementsByTagName("img")[0]
+      img.src = filedataURL;
+
+      // Once our img loads, take reftest snapshot.
+      img.addEventListener("load", function() {
+        document.documentElement.removeAttribute("class");
+      });
+    }
+
+    // Helper function -- returns a moz-filedata URL representing a
+    // 100x100 fully-lime SVG document.
+    function generateMozFiledataURL() {
+      var blobBuilder = new self.MozBlobBuilder;
+      var svg =
+        '<svg xmlns="http://www.w3.org/2000/svg" width="100" height="100">' +
+          '<rect height="100%" width="100%" fill="lime"/>' +
+        '</svg>';
+      blobBuilder.append(svg);
+      return self.URL.createObjectURL(blobBuilder.getBlob("image/svg+xml"));
+    }
+  </script>
+</head>
+<body onload="go()">
+  <img src="">
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/svg/as-image/img-blobBuilder-2.html
@@ -0,0 +1,46 @@
+<!DOCTYPE html>
+<!-- This test checks to be sure we allow MozBlobBuilder-generated
+     'moz-filedata' URIs *inside of* SVG-as-an-image. -->
+<html class="reftest-wait">
+<head>
+  <script>
+    function go() {
+      // Generate a moz-filedata URL encoding of an SVG document
+      var filedataURL = generateMozFiledataURL();
+
+      // Now generate a data URI, containing our moz-filedata URI
+      var outerSVG =
+        '<svg xmlns="http://www.w3.org/2000/svg" ' +
+             'xmlns:xlink="http://www.w3.org/1999/xlink" ' +
+             'width="100" height="100">' +
+          '<image height="100" width="100" ' +
+             'xlink:href="' + filedataURL + '"/>' +
+        '</svg>';
+
+      // Tell our img element to render the URL
+      var img = document.getElementsByTagName("img")[0]
+      img.src = "data:image/svg+xml," + outerSVG;
+
+      // Once our img loads, take reftest snapshot.
+      img.addEventListener("load", function() {
+        document.documentElement.removeAttribute("class");
+      });
+    }
+
+    // Helper function -- returns a moz-filedata URL representing a
+    // 100x100 fully-lime SVG document.
+    function generateMozFiledataURL() {
+      var blobBuilder = new self.MozBlobBuilder;
+      var svg =
+        '<svg xmlns="http://www.w3.org/2000/svg" width="100" height="100">' +
+          '<rect height="100%" width="100%" fill="lime"/>' +
+        '</svg>';
+      blobBuilder.append(svg);
+      return self.URL.createObjectURL(blobBuilder.getBlob("image/svg+xml"));
+    }
+  </script>
+</head>
+<body onload="go()">
+  <img src="">
+</body>
+</html>
--- a/layout/reftests/svg/as-image/reftest.list
+++ b/layout/reftests/svg/as-image/reftest.list
@@ -50,16 +50,18 @@ fails == canvas-drawImage-slice-1b.html 
 == img-simple-6.html  lime100x100-ref.html
 == img-simple-7.html  img-simple-7-ref.html
 
 # Test with mix of <html:img> and <svg:image> referring to the same images,
 # with a variety of preserveAspectRatio values in play.
 random == img-and-image-1.html img-and-image-1-ref.svg # bug 645267
 
 # More complex <img> tests
+== img-blobBuilder-1.html lime100x100-ref.html
+== img-blobBuilder-2.html lime100x100-ref.html
 == img-content-outside-viewBox-1.html img-content-outside-viewBox-1-ref.html
 == img-dyn-1.html img-dyn-1-ref.html
 == img-foreignObject-1.html lime100x100-ref.html
 
 # The following tests check that content embedded via <iframe> and <embed>
 # doesn't load (or execute scripts) in SVG-as-an-image.
 # The "!=" lines are to test that the SVG content, when viewed directly (not as
 # an image), does actually render its external content (making it look
@@ -115,21 +117,21 @@ random == img-and-image-1.html img-and-i
 
 # test that scripting feature is not supported in images or referenced documents
 == svg-image-script-1.svg lime100x100.svg
 == svg-image-script-2.svg lime100x100.svg
 
 # tests for external resources vs. data URIs in SVG as an image
 == svg-image-datauri-1.html            lime100x100.svg
 HTTP == svg-image-datauri-1.html       lime100x100.svg
-fails-if(Android) == svg-image-external-1.html           lime100x100.svg
+== svg-image-external-1.html           blue100x100.svg
 HTTP == svg-image-external-1.html      blue100x100.svg
 == svg-stylesheet-datauri-1.html       lime100x100.svg
 HTTP == svg-stylesheet-datauri-1.html  lime100x100.svg
-random == svg-stylesheet-external-1.html      lime100x100.svg # see bug 629885 comment 9
+== svg-stylesheet-external-1.html      blue100x100.svg
 HTTP == svg-stylesheet-external-1.html blue100x100.svg
 
 # test that :visited status is ignored in image documents
 # We load the images directly first, to be sure history is populated.
 # It's also good to verify that the helper images don't match lime100x100.svg
 # in non-image contexts, but for that to work, we have to reliably count on
 # :visited styles loading (asynchronously), so we test that in
 # layout/style/test/test_visited_reftests.html instead of the reftest harness.