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
+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.
--- /dev/null
+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.
+
--- /dev/null
+<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>
--- /dev/null
+This tests that drawing a remote SVG image onto a canvas from a data URL taints the canvas
+
+PASS: getImageData failed. Canvas tainted.
+
--- /dev/null
+<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>
--- /dev/null
+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.
+
--- /dev/null
+<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>
+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.
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)
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);
}
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
// 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