2009-09-19 Adam Barth <abarth@webkit.org>
authorabarth@webkit.org <abarth@webkit.org@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Sat, 19 Sep 2009 17:17:20 +0000 (17:17 +0000)
committerabarth@webkit.org <abarth@webkit.org@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Sat, 19 Sep 2009 17:17:20 +0000 (17:17 +0000)
        Reviewed by Oliver Hunt.

        Canvas drawn with data URL image raises SECURITY_ERR when toDataUrl() called.
        https://bugs.webkit.org/show_bug.cgi?id=29305

        Test that drawing a data URL image onto a canvas behaves as expected.
        Note the tricky case involving a data URL SVG image with an embedded
        remote image.

        Also, test that document.domain state doesn't affect canvas taint
        state.

        * http/tests/security/canvas-remote-read-data-url-image-expected.txt: Added.
        * http/tests/security/canvas-remote-read-data-url-image.html: Added.
        * http/tests/security/canvas-remote-read-data-url-svg-image-expected.txt: Added.
        * http/tests/security/canvas-remote-read-data-url-svg-image.html: Added.
        * http/tests/security/canvas-remote-read-remote-image-document-domain-expected.txt: Added.
        * http/tests/security/canvas-remote-read-remote-image-document-domain.html: Added.
2009-09-19  Adam Barth  <abarth@webkit.org>

        Reviewed by Oliver Hunt.

        Canvas drawn with data URL image raises SECURITY_ERR when toDataUrl() called.
        https://bugs.webkit.org/show_bug.cgi?id=29305

        We need to special-case data URLs when tainting a canvas because we
        treat data URLs has having no security origin, unlike other
        browsers.  The reason we do this is to help sites avoid XSS via data
        URLs, but that consideration doesn't apply to canvas taint.

        Also, we were previously incorrectly taking document.domain state
        into account when tainting canvas.

        Tests: http/tests/security/canvas-remote-read-data-url-image.html
               http/tests/security/canvas-remote-read-data-url-svg-image.html
               http/tests/security/canvas-remote-read-remote-image-document-domain.html

        * html/canvas/CanvasRenderingContext2D.cpp:
        (WebCore::CanvasRenderingContext2D::checkOrigin):
        (WebCore::CanvasRenderingContext2D::createPattern):
        * page/SecurityOrigin.cpp:
        (WebCore::SecurityOrigin::taintsCanvas):
        * page/SecurityOrigin.h:

git-svn-id: https://svn.webkit.org/repository/webkit/trunk@48556 268f45cc-cd09-0410-ab3c-d52691b4dbfc

LayoutTests/ChangeLog
LayoutTests/http/tests/security/canvas-remote-read-data-url-image-expected.txt [new file with mode: 0644]
LayoutTests/http/tests/security/canvas-remote-read-data-url-image.html [new file with mode: 0644]
LayoutTests/http/tests/security/canvas-remote-read-data-url-svg-image-expected.txt [new file with mode: 0644]
LayoutTests/http/tests/security/canvas-remote-read-data-url-svg-image.html [new file with mode: 0644]
LayoutTests/http/tests/security/canvas-remote-read-remote-image-document-domain-expected.txt [new file with mode: 0644]
LayoutTests/http/tests/security/canvas-remote-read-remote-image-document-domain.html [new file with mode: 0644]
WebCore/ChangeLog
WebCore/html/canvas/CanvasRenderingContext2D.cpp
WebCore/page/SecurityOrigin.cpp
WebCore/page/SecurityOrigin.h

index 9d60b6036bc12c1d37e69266a84d22a062d9a06a..9354c1b23f497db1c1474c5c02b9bfa903a5c402 100644 (file)
@@ -1,3 +1,24 @@
+2009-09-19  Adam Barth  <abarth@webkit.org>
+
+        Reviewed by Oliver Hunt.
+
+        Canvas drawn with data URL image raises SECURITY_ERR when toDataUrl() called.
+        https://bugs.webkit.org/show_bug.cgi?id=29305
+
+        Test that drawing a data URL image onto a canvas behaves as expected.
+        Note the tricky case involving a data URL SVG image with an embedded
+        remote image.
+        
+        Also, test that document.domain state doesn't affect canvas taint
+        state.
+
+        * http/tests/security/canvas-remote-read-data-url-image-expected.txt: Added.
+        * http/tests/security/canvas-remote-read-data-url-image.html: Added.
+        * http/tests/security/canvas-remote-read-data-url-svg-image-expected.txt: Added.
+        * http/tests/security/canvas-remote-read-data-url-svg-image.html: Added.
+        * http/tests/security/canvas-remote-read-remote-image-document-domain-expected.txt: Added.
+        * http/tests/security/canvas-remote-read-remote-image-document-domain.html: Added.
+
 2009-09-19  Shinichiro Hamaji  <hamaji@chromium.org>
 
         Rubber-stamped by Eric Seidel.
diff --git a/LayoutTests/http/tests/security/canvas-remote-read-data-url-image-expected.txt b/LayoutTests/http/tests/security/canvas-remote-read-data-url-image-expected.txt
new file mode 100644 (file)
index 0000000..29a306c
--- /dev/null
@@ -0,0 +1,5 @@
+PASS: Calling getImageData() from a canvas tainted by a data URL image was allowed.
+PASS: Calling toDataURL() on a canvas tainted by a data URL image was allowed.
+PASS: Calling getImageData() from a canvas tainted by a data URL image pattern was allowed.
+PASS: Calling toDataURL() on a canvas tainted by a data URL image pattern was allowed.
+
diff --git a/LayoutTests/http/tests/security/canvas-remote-read-data-url-image.html b/LayoutTests/http/tests/security/canvas-remote-read-data-url-image.html
new file mode 100644 (file)
index 0000000..a6df93f
--- /dev/null
@@ -0,0 +1,70 @@
+<pre id="console"></pre>
+<script>
+if (window.layoutTestController) {
+    layoutTestController.dumpAsText();
+    layoutTestController.waitUntilDone();
+}
+
+log = function(msg)
+{
+    document.getElementById('console').appendChild(document.createTextNode(msg + "\n"));
+}
+
+testGetImageData = function(context, description)
+{
+    description = "Calling getImageData() from a canvas tainted by a " + description;
+    try {
+        var imageData = context.getImageData(0,0,100,100);
+        log("PASS: " + description + " was allowed.");
+    } catch (e) {
+        log("FAIL: " + description + " was not allowed - Threw error: " + e + ".");
+    }
+}
+
+testToDataURL = function(canvas, description)
+{
+    description = "Calling toDataURL() on a canvas tainted by a " + description;
+    try {
+        var dataURL = canvas.toDataURL();
+        log("PASS: " + description + " was allowed.");
+    } catch (e) {
+        log("FAIL: " + description + " was not allowed - Threw error: " + e + ".");
+    }
+}
+
+test = function(canvas, description)
+{
+    testGetImageData(canvas.getContext("2d"), description);
+    testToDataURL(canvas, description);
+}
+
+var image = new Image();
+image.onload = function() {
+    var canvas = document.createElement("canvas");
+    canvas.width = 100;
+    canvas.height = 100;
+    var context = canvas.getContext("2d");
+
+    // Test reading from a canvas after drawing a data URL image onto it
+    context.drawImage(image, 0, 0, 100, 100);
+
+    test(canvas, "data URL image");
+
+    // Test reading after using a data URL pattern
+    canvas = document.createElement("canvas");
+    canvas.width = 100;
+    canvas.height = 100;
+    var context = canvas.getContext("2d");
+    var remoteImagePattern = context.createPattern(image, "repeat");
+    context.fillStyle = remoteImagePattern;
+    context.fillRect(0, 0, 100, 100);
+
+    test(canvas, "data URL image pattern");
+
+    if (window.layoutTestController)
+        layoutTestController.notifyDone();
+}
+
+// A small red dot.
+image.src = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAoAAAAKCAYAAACNMs+9AAAABGdBTUEAALGPC/xhBQAAAAlwSFlzAAALEwAACxMBAJqcGAAAAAd0SU1FB9YGARc5KB0XV+IAAAAddEVYdENvbW1lbnQAQ3JlYXRlZCB3aXRoIFRoZSBHSU1Q72QlbgAAAF1JREFUGNO9zL0NglAAxPEfdLTs4BZM4DIO4C7OwQg2JoQ9LE1exdlYvBBeZ7jqch9//q1uH4TLzw4d6+ErXMMcXuHWxId3KOETnnXXV6MJpcq2MLaI97CER3N0vr4MkhoXe0rZigAAAABJRU5ErkJggg==";
+</script>
diff --git a/LayoutTests/http/tests/security/canvas-remote-read-data-url-svg-image-expected.txt b/LayoutTests/http/tests/security/canvas-remote-read-data-url-svg-image-expected.txt
new file mode 100644 (file)
index 0000000..45777a9
--- /dev/null
@@ -0,0 +1,4 @@
+This tests that drawing a remote SVG image onto a canvas from a data URL taints the canvas
+
+PASS: getImageData failed. Canvas tainted.
+  
diff --git a/LayoutTests/http/tests/security/canvas-remote-read-data-url-svg-image.html b/LayoutTests/http/tests/security/canvas-remote-read-data-url-svg-image.html
new file mode 100644 (file)
index 0000000..8dc6c10
--- /dev/null
@@ -0,0 +1,40 @@
+<html>
+<head>
+<script>
+if (window.layoutTestController)
+    layoutTestController.dumpAsText();
+
+function log(msg)
+{
+    document.getElementById('console').appendChild(document.createTextNode(msg + "\n"));
+}
+
+function draw()
+{
+    var canvas = document.getElementById("canvas");
+    var ctx = canvas.getContext("2d");
+    ctx.drawImage(document.getElementById("img"), 0, 0);
+
+    try {
+        var data = ctx.getImageData(20, 20, 290, 75);
+        log("FAIL: getImageData succeeded. Canvas not tainted.");
+    } catch (e) {
+        log("PASS: getImageData failed. Canvas tainted.");
+    }
+}
+</script>
+</head>
+<body>
+    <p>This tests that drawing a remote SVG image onto a canvas from a data URL
+    taints the canvas</p>
+    <pre id="console"></pre>
+    <canvas id="canvas" width="330" height="115"></canvas>
+    <img id="img" onload="draw()" src='data:image/svg+xml,
+<svg xmlns="http://www.w3.org/2000/svg"
+     xmlns:xlink="http://www.w3.org/1999/xlink"
+     width="100" height="100">
+    <image xlink:href="http://localhost:8000/security/resources/abe.png"
+           width="100" height="100"/>
+</svg>'>
+</body>
+</html>
diff --git a/LayoutTests/http/tests/security/canvas-remote-read-remote-image-document-domain-expected.txt b/LayoutTests/http/tests/security/canvas-remote-read-remote-image-document-domain-expected.txt
new file mode 100644 (file)
index 0000000..8c8bcdd
--- /dev/null
@@ -0,0 +1,5 @@
+PASS: Calling getImageData() from a canvas tainted by a document.domain image was allowed.
+PASS: Calling toDataURL() on a canvas tainted by a document.domain image was allowed.
+PASS: Calling getImageData() from a canvas tainted by a document.domain image pattern was allowed.
+PASS: Calling toDataURL() on a canvas tainted by a document.domain image pattern was allowed.
+
diff --git a/LayoutTests/http/tests/security/canvas-remote-read-remote-image-document-domain.html b/LayoutTests/http/tests/security/canvas-remote-read-remote-image-document-domain.html
new file mode 100644 (file)
index 0000000..5214863
--- /dev/null
@@ -0,0 +1,71 @@
+<pre id="console"></pre>
+<script>
+if (window.layoutTestController) {
+    layoutTestController.dumpAsText();
+    layoutTestController.waitUntilDone();
+}
+
+// Set the "have set document.domain" flag.
+document.domain = document.domain;
+
+log = function(msg)
+{
+    document.getElementById('console').appendChild(document.createTextNode(msg + "\n"));
+}
+
+testGetImageData = function(context, description)
+{
+    description = "Calling getImageData() from a canvas tainted by a " + description;
+    try {
+        var imageData = context.getImageData(0,0,100,100);
+        log("PASS: " + description + " was allowed.");
+    } catch (e) {
+        log("FAIL: " + description + " was not allowed - Threw error: " + e + ".");
+    }
+}
+
+testToDataURL = function(canvas, description)
+{
+    description = "Calling toDataURL() on a canvas tainted by a " + description;
+    try {
+        var dataURL = canvas.toDataURL();
+        log("PASS: " + description + " was allowed.");
+    } catch (e) {
+        log("FAIL: " + description + " was not allowed - Threw error: " + e + ".");
+    }
+}
+
+test = function(canvas, description)
+{
+    testGetImageData(canvas.getContext("2d"), description);
+    testToDataURL(canvas, description);
+}
+
+var image = new Image();
+image.onload = function() {
+    var canvas = document.createElement("canvas");
+    canvas.width = 100;
+    canvas.height = 100;
+    var context = canvas.getContext("2d");
+
+    // Test reading from a canvas after drawing an image onto it
+    context.drawImage(image, 0, 0, 100, 100);
+
+    test(canvas, "document.domain image");
+
+    // Test reading after using a pattern
+    canvas = document.createElement("canvas");
+    canvas.width = 100;
+    canvas.height = 100;
+    var context = canvas.getContext("2d");
+    var remoteImagePattern = context.createPattern(image, "repeat");
+    context.fillStyle = remoteImagePattern;
+    context.fillRect(0, 0, 100, 100);
+
+    test(canvas, "document.domain image pattern");
+
+    if (window.layoutTestController)
+        layoutTestController.notifyDone();
+}
+image.src = "http://127.0.0.1:8000/security/resources/abe.png";
+</script>
index 9f2c4a20cd78ed979338d2960809acd259455a99..7f2a94db9bbadb3435c3a5461cca46a389de67d4 100644 (file)
@@ -1,3 +1,29 @@
+2009-09-19  Adam Barth  <abarth@webkit.org>
+
+        Reviewed by Oliver Hunt.
+
+        Canvas drawn with data URL image raises SECURITY_ERR when toDataUrl() called.
+        https://bugs.webkit.org/show_bug.cgi?id=29305
+
+        We need to special-case data URLs when tainting a canvas because we
+        treat data URLs has having no security origin, unlike other
+        browsers.  The reason we do this is to help sites avoid XSS via data
+        URLs, but that consideration doesn't apply to canvas taint.
+
+        Also, we were previously incorrectly taking document.domain state
+        into account when tainting canvas.
+
+        Tests: http/tests/security/canvas-remote-read-data-url-image.html
+               http/tests/security/canvas-remote-read-data-url-svg-image.html
+               http/tests/security/canvas-remote-read-remote-image-document-domain.html
+
+        * html/canvas/CanvasRenderingContext2D.cpp:
+        (WebCore::CanvasRenderingContext2D::checkOrigin):
+        (WebCore::CanvasRenderingContext2D::createPattern):
+        * page/SecurityOrigin.cpp:
+        (WebCore::SecurityOrigin::taintsCanvas):
+        * page/SecurityOrigin.h:
+
 2009-09-18  Simon Fraser  <simon.fraser@apple.com>
 
         Fix stylistic issue raised in code review for previous commit.
index 1e3faa37400b4efcb88df05d4b317057cd3f51d2..ed462fc1a64db6a195493180cc97bcaa9179f240 100644 (file)
@@ -935,16 +935,13 @@ static inline FloatRect normalizeRect(const FloatRect& rect)
 
 void CanvasRenderingContext2D::checkOrigin(const KURL& url)
 {
-    RefPtr<SecurityOrigin> origin = SecurityOrigin::create(url);
-    if (!m_canvas->document()->securityOrigin()->canAccess(origin.get()))
+    if (m_canvas->document()->securityOrigin()->taintsCanvas(url))
         m_canvas->setOriginTainted();
 }
 
 void CanvasRenderingContext2D::checkOrigin(const String& url)
 {
-    RefPtr<SecurityOrigin> origin = SecurityOrigin::createFromString(url);
-    if (!m_canvas->document()->securityOrigin()->canAccess(origin.get()))
-        m_canvas->setOriginTainted();
+    checkOrigin(KURL(KURL(), url));
 }
 
 void CanvasRenderingContext2D::drawImage(HTMLImageElement* image, float x, float y)
@@ -1208,8 +1205,7 @@ PassRefPtr<CanvasPattern> CanvasRenderingContext2D::createPattern(HTMLImageEleme
     if (!cachedImage || !image->cachedImage()->image())
         return CanvasPattern::create(Image::nullImage(), repeatX, repeatY, true);
 
-    RefPtr<SecurityOrigin> origin = SecurityOrigin::createFromString(cachedImage->url());
-    bool originClean = m_canvas->document()->securityOrigin()->canAccess(origin.get());
+    bool originClean = !m_canvas->document()->securityOrigin()->taintsCanvas(KURL(KURL(), cachedImage->url()));
     return CanvasPattern::create(cachedImage->image(), repeatX, repeatY, originClean);
 }
 
index 5076adf411e1d84220dfdbc80d6fb103da0a5bc6..b91c1f1976897ca286ce518c08067532bb60f5bb 100644 (file)
@@ -221,6 +221,22 @@ bool SecurityOrigin::canRequest(const KURL& url) const
     return false;
 }
 
+bool SecurityOrigin::taintsCanvas(const KURL& url) const
+{
+    if (canRequest(url))
+        return false;
+
+    // This method exists because we treat data URLs as noAccess, contrary
+    // to the current (9/19/2009) draft of the HTML5 specification.  We still
+    // want to let folks paint data URLs onto untainted canvases, so we special
+    // case data URLs below.  If we change to match HTML5 w.r.t. data URL
+    // security, then we can remove this method in favor of !canRequest.
+    if (url.protocolIs("data"))
+        return false;
+
+    return true;
+}
+
 void SecurityOrigin::grantLoadLocalResources()
 {
     // This method exists only to support backwards compatibility with older
index c8086ac8725d32b810352dfbb9c13c933fa0cc1a..732afa8f78517f90d198483b9bc61ccdbfa6df6a 100644 (file)
@@ -76,6 +76,11 @@ namespace WebCore {
         // XMLHttpRequests.
         bool canRequest(const KURL&) const;
 
+        // Returns true if drawing an image from this URL taints a canvas from
+        // this security origin.  For example, call this function before
+        // drawing an image onto an HTML canvas element with the drawImage API.
+        bool taintsCanvas(const KURL&) const;
+
         // Returns true if this SecurityOrigin can load local resources, such
         // as images, iframes, and style sheets, and can link to local URLs.
         // For example, call this function before creating an iframe to a