+2008-03-06 Oliver Hunt <oliver@apple.com>
+
+ Reviewed by Mitz.
+
+ Test security restrictions for retrieving the content of tainted canvases
+
+ * http/tests/security/canvas-remote-read-remote-image-expected.txt: Added.
+ * http/tests/security/canvas-remote-read-remote-image.html: Added.
+
2008-03-06 Adele Peterson <adele@apple.com>
Reviewed by Darin.
--- /dev/null
+CONSOLE MESSAGE: line 1: Call to getImageData failed due to tainted canvas.
+
+CONSOLE MESSAGE: line 1: Call to getImageData failed due to tainted canvas.
+
+CONSOLE MESSAGE: line 1: Call to getImageData failed due to tainted canvas.
+
+CONSOLE MESSAGE: line 1: Call to getImageData failed due to tainted canvas.
+
+PASS: Reading data from an untainted canvas was allowed
+PASS: Reading data from a canvas tainted by a remote image was not allowed
+PASS: Reading data from a canvas tainted by a tainted canvas was not allowed
+PASS: Reading data from a canvas tainted by a remote image tainted pattern was not allowed
+PASS: Reading data from a canvas tainted by a tainted canvas pattern was not allowed
+
--- /dev/null
+<div id="log"></div>
+<div id="console"></div>
+<script>
+if (window.layoutTestController) {
+ layoutTestController.dumpAsText();
+ layoutTestController.waitUntilDone();
+}
+var image = new Image();
+image.onload = function() {
+ var canvas = document.createElement("canvas");
+ canvas.width = 100;
+ canvas.height = 100;
+ var context = canvas.getContext("2d");
+ if (context.getImageData(0,0,100,100)) {
+ document.getElementById("console").innerHTML += "PASS: Reading data from an untainted canvas was allowed<br/>";
+ } else {
+ document.getElementById("console").innerHTML = "FAIL: Reading data from an untainted canvas was not allowed<br/>";
+ }
+ context.drawImage(image, 0, 0, 100, 100);
+ if (context.getImageData(0,0,100,100) == null) {
+ document.getElementById("console").innerHTML += "PASS: Reading data from a canvas tainted by a remote image was not allowed<br/>";
+ } else {
+ document.getElementById("console").innerHTML += "FAIL: Reading data from a canvas tainted by a remote image was allowed<br/>";
+ }
+ document.getElementById("log").appendChild(canvas);
+ var dirtyCanvas = canvas;
+
+ // Now test reading from a canvas after drawing a tainted canvas onto it
+ canvas = document.createElement("canvas");
+ canvas.width = 100;
+ canvas.height = 100;
+ var context = canvas.getContext("2d");
+ context.drawImage(dirtyCanvas, 0, 0, 100, 100);
+ if (context.getImageData(0,0,100,100) == null) {
+ document.getElementById("console").innerHTML += "PASS: Reading data from a canvas tainted by a tainted canvas was not allowed<br/>";
+ } else {
+ document.getElementById("console").innerHTML += "FAIL: Reading data from a canvas tainted by a tainted canvas was allowed<br/>";
+ }
+
+ document.getElementById("log").appendChild(canvas);
+
+ // Test reading after using a tainted 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);
+ if (context.getImageData(0,0,100,100) == null) {
+ document.getElementById("console").innerHTML += "PASS: Reading data from a canvas tainted by a remote image tainted pattern was not allowed<br/>";
+ } else {
+ document.getElementById("console").innerHTML += "FAIL: Reading data from a canvas tainted by a remote image tainted pattern was allowed<br/>";
+ }
+
+ document.getElementById("log").appendChild(canvas);
+
+ // Test reading after using a tainted pattern
+ canvas = document.createElement("canvas");
+ canvas.width = 100;
+ canvas.height = 100;
+ var context = canvas.getContext("2d");
+ var taintedCanvasPattern = context.createPattern(dirtyCanvas, "repeat");
+ context.fillStyle = taintedCanvasPattern;
+ context.fillRect(0,0,100,100);
+ if (context.getImageData(0,0,100,100) == null) {
+ document.getElementById("console").innerHTML += "PASS: Reading data from a canvas tainted by a tainted canvas pattern was not allowed<br/>";
+ } else {
+ document.getElementById("console").innerHTML += "FAIL: Reading data from a canvas tainted by a tainted canvas pattern was allowed<br/>";
+ }
+
+ document.getElementById("log").appendChild(canvas);
+ if (window.layoutTestController)
+ layoutTestController.notifyDone();
+}
+image.src = "http://localhost:8000/security/resources/abe.png";
+</script>
+2008-03-06 Sam Weinig <sam@webkit.org> with a little help from Oliver Hunt <oliver@apple.com>
+
+ Reviewed by Mitz.
+
+ Implement the HTML5 canvas tainting rules to prevent potential data leakage
+
+ Added originClean to HTMLCanvasElement and CanvasPattern
+ to track whether a canvas (or pattern) is tainted by remote
+ data.
+ Use originClean flag to determine whether getImageData should
+ return, well, image data.
+
+ Test: http/tests/security/canvas-remote-read-remote-image.html
+
+ * html/CanvasPattern.cpp:
+ (WebCore::CanvasPattern::CanvasPattern):
+ * html/CanvasPattern.h:
+ * html/CanvasRenderingContext2D.cpp:
+ (WebCore::CanvasRenderingContext2D::setStrokeStyle):
+ (WebCore::CanvasRenderingContext2D::setFillStyle):
+ (WebCore::CanvasRenderingContext2D::checkOrigin):
+ (WebCore::CanvasRenderingContext2D::drawImage):
+ (WebCore::CanvasRenderingContext2D::drawImageFromRect):
+ (WebCore::CanvasRenderingContext2D::createPattern):
+ (WebCore::CanvasRenderingContext2D::printSecurityExceptionMessage):
+ (WebCore::CanvasRenderingContext2D::getImageData):
+ * html/CanvasRenderingContext2D.h:
+ * html/HTMLCanvasElement.cpp:
+ (WebCore::HTMLCanvasElement::HTMLCanvasElement):
+ * html/HTMLCanvasElement.h:
+ (WebCore::HTMLCanvasElement::setOriginTainted):
+ (WebCore::HTMLCanvasElement::originClean):
+
2008-03-06 Anders Carlsson <andersca@apple.com>
Reviewed by Jon.
#if PLATFORM(CG)
-CanvasPattern::CanvasPattern(CGImageRef image, bool repeatX, bool repeatY)
+CanvasPattern::CanvasPattern(CGImageRef image, bool repeatX, bool repeatY, bool originClean)
: RefCounted<CanvasPattern>(0)
, m_platformImage(image)
, m_cachedImage(0)
, m_repeatX(repeatX)
, m_repeatY(repeatY)
+ , m_originClean(originClean)
{
}
#elif PLATFORM(CAIRO)
-CanvasPattern::CanvasPattern(cairo_surface_t* surface, bool repeatX, bool repeatY)
+CanvasPattern::CanvasPattern(cairo_surface_t* surface, bool repeatX, bool repeatY, bool originClean)
: RefCounted<CanvasPattern>(0)
, m_platformImage(cairo_surface_reference(surface))
, m_cachedImage(0)
, m_repeatX(repeatX)
, m_repeatY(repeatY)
+ , m_originClean(originClean)
{
}
#endif
-CanvasPattern::CanvasPattern(CachedImage* cachedImage, bool repeatX, bool repeatY)
+CanvasPattern::CanvasPattern(CachedImage* cachedImage, bool repeatX, bool repeatY, bool originClean)
: RefCounted<CanvasPattern>(0)
#if PLATFORM(CG) || PLATFORM(CAIRO)
, m_platformImage(0)
, m_cachedImage(cachedImage)
, m_repeatX(repeatX)
, m_repeatY(repeatY)
+ , m_originClean(originClean)
{
if (cachedImage)
cachedImage->ref(this);
static void parseRepetitionType(const String&, bool& repeatX, bool& repeatY, ExceptionCode&);
#if PLATFORM(CG)
- CanvasPattern(CGImageRef, bool repeatX, bool repeatY);
+ CanvasPattern(CGImageRef, bool repeatX, bool repeatY, bool originClean);
#elif PLATFORM(CAIRO)
- CanvasPattern(cairo_surface_t*, bool repeatX, bool repeatY);
+ CanvasPattern(cairo_surface_t*, bool repeatX, bool repeatY, bool originClean);
#endif
- CanvasPattern(CachedImage*, bool repeatX, bool repeatY);
+ CanvasPattern(CachedImage*, bool repeatX, bool repeatY, bool originClean);
~CanvasPattern();
#if PLATFORM(CG)
cairo_pattern_t* createPattern(const cairo_matrix_t&);
#endif
+ bool originClean() const { return m_originClean; }
+
private:
#if PLATFORM(CG)
const RetainPtr<CGImageRef> m_platformImage;
CachedImage* const m_cachedImage;
const bool m_repeatX;
const bool m_repeatY;
+ bool m_originClean;
};
} // namespace WebCore
#include "HTMLNames.h"
#include "ImageBuffer.h"
#include "ImageData.h"
+#include "KURL.h"
#include "NotImplemented.h"
+#include "Page.h"
#include "RenderHTMLCanvas.h"
+#include "SecurityOrigin.h"
#include "Settings.h"
+#include <kjs/interpreter.h>
#include <wtf/MathExtras.h>
#if PLATFORM(QT)
{
if (!style)
return;
+
+ if (m_canvas->originClean()) {
+ if (CanvasPattern* pattern = style->pattern()) {
+ if (!pattern->originClean())
+ m_canvas->setOriginTainted();
+ }
+ }
+
state().m_strokeStyle = style;
GraphicsContext* c = drawingContext();
if (!c)
{
if (!style)
return;
+
+ if (m_canvas->originClean()) {
+ if (CanvasPattern* pattern = style->pattern()) {
+ if (!pattern->originClean())
+ m_canvas->setOriginTainted();
+ }
+ }
+
state().m_fillStyle = style;
GraphicsContext* c = drawingContext();
if (!c)
drawImage(image, FloatRect(0, 0, s.width(), s.height()), FloatRect(x, y, width, height), ec);
}
+void CanvasRenderingContext2D::checkOrigin(const KURL& url)
+{
+ RefPtr<SecurityOrigin> origin = SecurityOrigin::create(url.protocol(), url.host(), url.port(), 0);
+ SecurityOrigin::Reason reason;
+ if (!m_canvas->document()->securityOrigin()->canAccess(origin.get(), reason))
+ m_canvas->setOriginTainted();
+}
+
void CanvasRenderingContext2D::drawImage(HTMLImageElement* image, const FloatRect& srcRect, const FloatRect& dstRect,
ExceptionCode& ec)
{
if (!cachedImage)
return;
+ if (m_canvas->originClean())
+ checkOrigin(KURL(cachedImage->url()));
+
FloatRect sourceRect = c->roundToDevicePixels(srcRect);
FloatRect destRect = c->roundToDevicePixels(dstRect);
willDraw(destRect);
ImageBuffer* buffer = canvas->buffer();
if (!buffer)
return;
-
+
+ if (!canvas->originClean())
+ m_canvas->setOriginTainted();
+
willDraw(destRect);
c->drawImage(buffer, sourceRect, destRect);
}
if (!cachedImage)
return;
+ if (m_canvas->originClean())
+ checkOrigin(KURL(cachedImage->url()));
+
GraphicsContext* c = drawingContext();
if (!c)
return;
CanvasPattern::parseRepetitionType(repetitionType, repeatX, repeatY, ec);
if (ec)
return 0;
- return new CanvasPattern(image ? image->cachedImage() : 0, repeatX, repeatY);
+
+ bool originClean = true;
+ if (CachedImage* cachedImage = image->cachedImage()) {
+ KURL url(cachedImage->url());
+ RefPtr<SecurityOrigin> origin = SecurityOrigin::create(url.protocol(), url.host(), url.port(), 0);
+ SecurityOrigin::Reason reason;
+ originClean = m_canvas->document()->securityOrigin()->canAccess(origin.get(), reason);
+ }
+ return new CanvasPattern(image->cachedImage(), repeatX, repeatY, originClean);
}
PassRefPtr<CanvasPattern> CanvasRenderingContext2D::createPattern(HTMLCanvasElement* canvas,
CGImageRef image = canvas->createPlatformImage();
if (!image)
return 0;
- PassRefPtr<CanvasPattern> pattern = new CanvasPattern(image, repeatX, repeatY);
+ PassRefPtr<CanvasPattern> pattern = new CanvasPattern(image, repeatX, repeatY, canvas->originClean());
CGImageRelease(image);
return pattern;
#elif PLATFORM(CAIRO)
cairo_surface_t* surface = canvas->createPlatformImage();
if (!surface)
return 0;
- PassRefPtr<CanvasPattern> pattern = new CanvasPattern(surface, repeatX, repeatY);
+ PassRefPtr<CanvasPattern> pattern = new CanvasPattern(surface, repeatX, repeatY, canvas->originClean());
cairo_surface_destroy(surface);
return pattern;
#else
return createEmptyImageData(scaledSize);
}
+void CanvasRenderingContext2D::printSecurityExceptionMessage() const
+{
+ static const char* message = "Call to getImageData failed due to tainted canvas.\n";
+
+ Frame* frame = m_canvas->document()->frame();
+ if (!frame)
+ return;
+ if (frame->settings()->privateBrowsingEnabled())
+ return;
+ if (KJS::Interpreter::shouldPrintExceptions())
+ printf("%s", message);
+ if (Page* page = frame->page())
+ page->chrome()->addMessageToConsole(JSMessageSource, ErrorMessageLevel, message, 1, String()); // FIXME: provide a real line number and source URL.
+}
+
PassRefPtr<ImageData> CanvasRenderingContext2D::getImageData(float sx, float sy, float sw, float sh) const
{
+ if (!m_canvas->originClean()) {
+ // FIXME: the WHATWG specification says that this should raise a "security exception", but does not currently
+ // define what one is. For now, we will silently fail with only a log message.
+ printSecurityExceptionMessage();
+ return 0;
+ }
+
FloatRect unscaledRect(sx, sy, sw, sh);
IntRect scaledRect = m_canvas ? m_canvas->convertLogicalToDevice(unscaledRect) : enclosingIntRect(unscaledRect);
if (scaledRect.width() < 1)
class HTMLCanvasElement;
class HTMLImageElement;
class ImageData;
+ class KURL;
typedef int ExceptionCode;
void clearPathForDashboardBackwardCompatibilityMode();
+ void checkOrigin(const KURL&);
+
+ void printSecurityExceptionMessage() const;
+
HTMLCanvasElement* m_canvas;
Vector<State, 1> m_stateStack;
};
HTMLCanvasElement::HTMLCanvasElement(Document* doc)
: HTMLElement(canvasTag, doc)
, m_size(defaultWidth, defaultHeight)
+ , m_originClean(true)
, m_createdImageBuffer(false)
{
}
IntRect convertLogicalToDevice(const FloatRect&) const;
IntSize convertLogicalToDevice(const FloatSize&) const;
IntPoint convertLogicalToDevice(const FloatPoint&) const;
+
+ void setOriginTainted() { m_originClean = false; }
+ bool originClean() const { return m_originClean; }
+
static const float MaxCanvasArea;
+
private:
void createImageBuffer() const;
void reset();
RefPtr<CanvasRenderingContext2D> m_2DContext;
IntSize m_size;
- // FIXME: Web Applications 1.0 describes a security feature where we track
- // if we ever drew any images outside the domain, so we can disable toDataURL.
+ bool m_originClean;
// m_createdImageBuffer means we tried to malloc the buffer. We didn't necessarily get it.
mutable bool m_createdImageBuffer;