Title: [182750] trunk
Revision
182750
Author
s...@apple.com
Date
2015-04-13 12:53:14 -0700 (Mon, 13 Apr 2015)

Log Message

Canvas drawImage() has a security hole when the image isn't yet fully loaded.
https://bugs.webkit.org/show_bug.cgi?id=58681.

Reviewed by Darin Adler.

Source/WebCore:

There is a race condition which may happen if an image from a different
origin is drawn on a canvas before it finishes loading. The check to taint
the canvas comes before drawing it. This check returns false if the image
is not completely loaded because we check the URL of the resource response.
If after this check and before the drawing, the image finishes loading, the
canvas will not be tainted but the image will be drawn.
        
The fix is to move the check to taint the canvas after drawing the image.
The only problem with this solution is basically the opposite of this bug:
we will become stricter than before with images which are from a different
origin and before they finish loading. The image has not finished loading,
so we do not draw it. Before we check for tainting, the image finishes
loading. So we decide to taint the canvas even the image is not drawn.
        
But this should not be a security issue anymore. I personally do not know
if it is even a correctness issue or not.

Test: http/tests/canvas/canvas-tainted-after-draw-image.html

* html/canvas/CanvasRenderingContext2D.cpp:
(WebCore::CanvasRenderingContext2D::drawImage):

LayoutTests:

This test confirms when we load an image from a different origin and try
drawing it on a canvas, the canvas is tainted if the image is completely
loaded and drawn. Otherwise the image is not drawn.

* http/tests/canvas/canvas-tainted-after-draw-image-expected.txt: Added.
* http/tests/canvas/canvas-tainted-after-draw-image.html: Added.
* http/tests/canvas/resources: Added.
* http/tests/canvas/resources/100x100-lime-rect.svg: Added.

Modified Paths

Added Paths

Diff

Modified: trunk/LayoutTests/ChangeLog (182749 => 182750)


--- trunk/LayoutTests/ChangeLog	2015-04-13 19:52:11 UTC (rev 182749)
+++ trunk/LayoutTests/ChangeLog	2015-04-13 19:53:14 UTC (rev 182750)
@@ -1,3 +1,19 @@
+2015-04-13  Said Abou-Hallawa  <sabouhall...@apple.com>
+
+        Canvas drawImage() has a security hole when the image isn't yet fully loaded.
+        https://bugs.webkit.org/show_bug.cgi?id=58681.
+
+        Reviewed by Darin Adler.
+
+        This test confirms when we load an image from a different origin and try
+        drawing it on a canvas, the canvas is tainted if the image is completely
+        loaded and drawn. Otherwise the image is not drawn.
+
+        * http/tests/canvas/canvas-tainted-after-draw-image-expected.txt: Added.
+        * http/tests/canvas/canvas-tainted-after-draw-image.html: Added.
+        * http/tests/canvas/resources: Added.
+        * http/tests/canvas/resources/100x100-lime-rect.svg: Added.
+
 2015-04-13  Beth Dakin  <bda...@apple.com>
 
         Add force property to MouseEvents

Added: trunk/LayoutTests/http/tests/canvas/canvas-tainted-after-draw-image-expected.txt (0 => 182750)


--- trunk/LayoutTests/http/tests/canvas/canvas-tainted-after-draw-image-expected.txt	                        (rev 0)
+++ trunk/LayoutTests/http/tests/canvas/canvas-tainted-after-draw-image-expected.txt	2015-04-13 19:53:14 UTC (rev 182750)
@@ -0,0 +1,11 @@
+CONSOLE MESSAGE: line 60: Unable to get image data from canvas because the canvas has been tainted by cross-origin data.
+Tainting works correctly.
+Tainting works correctly.
+Tainting works correctly.
+Tainting works correctly.
+Tainting works correctly.
+Tainting works correctly.
+PASS successfullyParsed is true
+
+TEST COMPLETE
+      

Added: trunk/LayoutTests/http/tests/canvas/canvas-tainted-after-draw-image.html (0 => 182750)


--- trunk/LayoutTests/http/tests/canvas/canvas-tainted-after-draw-image.html	                        (rev 0)
+++ trunk/LayoutTests/http/tests/canvas/canvas-tainted-after-draw-image.html	2015-04-13 19:53:14 UTC (rev 182750)
@@ -0,0 +1,90 @@
+<!DOCTYPE html>
+<html>
+<head>
+  <script src=""
+</head>
+<body>
+  <canvas id="sourceCanvas" width="100" height="100"></canvas>
+  <canvas id="dataURLSynchronousCanvas" width="100" height="100"></canvas>
+  <canvas id="dataURLAsynchronousCanvas" width="100" height="100"></canvas>
+  <canvas id="sameDomainSynchronousCanvas" width="100" height="100"></canvas>
+  <canvas id="sameDomainAsynchronousCanvas" width="100" height="100"></canvas>
+  <canvas id="crossDomainSynchronousCanvas" width="100" height="100"></canvas>
+  <canvas id="crossDomainAsynchronousCanvas" width="100" height="100"></canvas>
+  <script>
+    function drawCanvasBackground(id, color) {
+      var canvas = document.getElementById(id);
+      var context = canvas.getContext("2d");
+      context.fillStyle = color;
+      context.fillRect(0, 0, 100, 100);
+      return context;
+    }
+    
+    function incrementLoadedImagesCount() {
+      if (typeof incrementLoadedImagesCount.counter == 'undefined')
+        incrementLoadedImagesCount.counter = 0;
+        
+      if (++incrementLoadedImagesCount.counter == 6) {
+        if (window.testRunner)
+          testRunner.notifyDone();
+      }
+    }
+
+    function drawAndGetImageDataSynchronous(id, color, imageSrc, shouldTaint) {
+      var context = drawCanvasBackground(id, color);
+      var image =  new Image();
+      image.src = ""
+      context.drawImage(image, 0, 0);
+
+      try {
+        var pixels = new Uint32Array(context.getImageData(0, 0, 1, 1).data.buffer);
+        if (pixels[0] == 0xFF0000FF)
+          debug(shouldTaint ? "Tainting works correctly." : "Tainting works incorrectly.");
+        else
+          debug(shouldTaint ? "Tainting works incorrectly." : "Tainting works correctly.");
+      }
+      catch (error) {
+        debug(shouldTaint ? "Tainting works correctly." : "Tainting works incorrectly.");
+      }
+      
+      incrementLoadedImagesCount();
+    }
+    
+    function drawAndGetImageDataAsynchronous(canvasId, imageSrc, shouldTaint) {
+      var context = drawCanvasBackground(canvasId, '#f00');
+      var image =  new Image();
+
+      image._onload_ = function() {
+        context.drawImage(image, 0, 0);
+        try {
+          var pixels = new Uint32Array(context.getImageData(0, 0, 1, 1).data.buffer);
+          debug(shouldTaint ? "Tainting works incorrectly." : "Tainting works correctly.");
+        }
+        catch (error) {
+          debug(shouldTaint ? "Tainting works correctly." : "Tainting works incorrectly.");
+        }
+
+        incrementLoadedImagesCount();
+      }
+      image.src = ""
+    }
+
+    if (window.testRunner) {
+      testRunner.dumpAsText();
+      testRunner.waitUntilDone();
+    }
+
+    drawCanvasBackground("sourceCanvas", '#0f0');
+        
+    drawAndGetImageDataSynchronous("dataURLSynchronousCanvas", sourceCanvas.toDataURL(), false);
+    drawAndGetImageDataAsynchronous("dataURLAsynchronousCanvas", sourceCanvas.toDataURL(), false);
+    
+    drawAndGetImageDataSynchronous("sameDomainSynchronousCanvas", "http://127.0.0.1:8000/canvas/resources/100x100-lime-rect.svg", false);
+    drawAndGetImageDataAsynchronous("sameDomainAsynchronousCanvas", "http://127.0.0.1:8000/canvas/resources/100x100-lime-rect.svg", false);
+    
+    drawAndGetImageDataSynchronous("crossDomainSynchronousCanvas", "http://localhost:8000/canvas/resources/100x100-lime-rect.svg", true);
+    drawAndGetImageDataAsynchronous("crossDomainAsynchronousCanvas", "http://localhost:8000/canvas/resources/100x100-lime-rect.svg", true);
+  </script>
+  <script src=""
+</body>
+</html>

Added: trunk/LayoutTests/http/tests/canvas/resources/100x100-lime-rect.svg (0 => 182750)


--- trunk/LayoutTests/http/tests/canvas/resources/100x100-lime-rect.svg	                        (rev 0)
+++ trunk/LayoutTests/http/tests/canvas/resources/100x100-lime-rect.svg	2015-04-13 19:53:14 UTC (rev 182750)
@@ -0,0 +1,3 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="100" height="100">
+    <rect x="0" y="0" width="100px" height="100px" fill="lime"/>
+</svg>
\ No newline at end of file

Modified: trunk/Source/WebCore/ChangeLog (182749 => 182750)


--- trunk/Source/WebCore/ChangeLog	2015-04-13 19:52:11 UTC (rev 182749)
+++ trunk/Source/WebCore/ChangeLog	2015-04-13 19:53:14 UTC (rev 182750)
@@ -1,3 +1,32 @@
+2015-04-13  Said Abou-Hallawa  <sabouhall...@apple.com>
+
+        Canvas drawImage() has a security hole when the image isn't yet fully loaded.
+        https://bugs.webkit.org/show_bug.cgi?id=58681.
+
+        Reviewed by Darin Adler.
+
+        There is a race condition which may happen if an image from a different
+        origin is drawn on a canvas before it finishes loading. The check to taint
+        the canvas comes before drawing it. This check returns false if the image
+        is not completely loaded because we check the URL of the resource response.
+        If after this check and before the drawing, the image finishes loading, the
+        canvas will not be tainted but the image will be drawn.
+        
+        The fix is to move the check to taint the canvas after drawing the image.
+        The only problem with this solution is basically the opposite of this bug:
+        we will become stricter than before with images which are from a different
+        origin and before they finish loading. The image has not finished loading,
+        so we do not draw it. Before we check for tainting, the image finishes
+        loading. So we decide to taint the canvas even the image is not drawn.
+        
+        But this should not be a security issue anymore. I personally do not know
+        if it is even a correctness issue or not.
+
+        Test: http/tests/canvas/canvas-tainted-after-draw-image.html
+
+        * html/canvas/CanvasRenderingContext2D.cpp:
+        (WebCore::CanvasRenderingContext2D::drawImage):
+
 2015-04-13  Beth Dakin  <bda...@apple.com>
 
         Add force property to MouseEvents

Modified: trunk/Source/WebCore/html/canvas/CanvasRenderingContext2D.cpp (182749 => 182750)


--- trunk/Source/WebCore/html/canvas/CanvasRenderingContext2D.cpp	2015-04-13 19:52:11 UTC (rev 182749)
+++ trunk/Source/WebCore/html/canvas/CanvasRenderingContext2D.cpp	2015-04-13 19:53:14 UTC (rev 182750)
@@ -1377,8 +1377,6 @@
     if (!cachedImage)
         return;
 
-    checkOrigin(image);
-
     if (rectContainsCanvas(normalizedDstRect)) {
         c->drawImage(cachedImage->imageForRenderer(image->renderer()), ColorSpaceDeviceRGB, normalizedDstRect, normalizedSrcRect, ImagePaintingOptions(op, blendMode));
         didDrawEntireCanvas();
@@ -1393,6 +1391,8 @@
         c->drawImage(cachedImage->imageForRenderer(image->renderer()), ColorSpaceDeviceRGB, normalizedDstRect, normalizedSrcRect, ImagePaintingOptions(op, blendMode));
         didDraw(normalizedDstRect);
     }
+
+    checkOrigin(image);
 }
 
 void CanvasRenderingContext2D::drawImage(HTMLCanvasElement* sourceCanvas, float x, float y, ExceptionCode& ec)
_______________________________________________
webkit-changes mailing list
webkit-changes@lists.webkit.org
https://lists.webkit.org/mailman/listinfo/webkit-changes

Reply via email to