Bug 180301 - A canvas should not be tainted if it draws a data URL SVGImage with a <foreignObject>
Summary: A canvas should not be tainted if it draws a data URL SVGImage with a <foreig...
Status: RESOLVED FIXED
Alias: None
Product: WebKit
Classification: Unclassified
Component: SVG (show other bugs)
Version: WebKit Nightly Build
Hardware: Unspecified Unspecified
: P2 Normal
Assignee: Said Abou-Hallawa
URL:
Keywords: InRadar
Depends on:
Blocks:
 
Reported: 2017-12-01 17:52 PST by Said Abou-Hallawa
Modified: 2018-01-08 16:35 PST (History)
14 users (show)

See Also:


Attachments
Patch (4.71 KB, patch)
2017-12-01 17:54 PST, Said Abou-Hallawa
no flags Details | Formatted Diff | Diff
Archive of layout-test-results from ews102 for mac-elcapitan (2.46 MB, application/zip)
2017-12-01 18:44 PST, EWS Watchlist
no flags Details
Patch (4.72 KB, patch)
2017-12-01 18:46 PST, Said Abou-Hallawa
no flags Details | Formatted Diff | Diff
Patch (11.66 KB, patch)
2017-12-04 12:38 PST, Said Abou-Hallawa
no flags Details | Formatted Diff | Diff
Patch (11.91 KB, patch)
2017-12-04 12:47 PST, Said Abou-Hallawa
no flags Details | Formatted Diff | Diff
Patch (11.54 KB, patch)
2018-01-08 15:58 PST, Said Abou-Hallawa
no flags Details | Formatted Diff | Diff

Note You need to log in before you can comment on or make changes to this bug.
Description Said Abou-Hallawa 2017-12-01 17:52:51 PST
After <http://trac.webkit.org/changeset/195614>, the canvas will tainted if it draws any SVGImage with a <foreignObject>. The reason for this is we do not want to SVGImages to leak cross-origin data (e.g., visited links, spellcheck).

But if the sourceURL of the SVGImage is a data URL, then there should not be a cross-origin data leak. So we should not taint the canvas in this case. We need to relax the tainting check for data URL SVGImage.
Comment 1 Said Abou-Hallawa 2017-12-01 17:54:42 PST
Created attachment 328199 [details]
Patch
Comment 2 Said Abou-Hallawa 2017-12-01 17:55:25 PST
<rdar://problem/35319543>
Comment 3 EWS Watchlist 2017-12-01 18:44:08 PST
Comment on attachment 328199 [details]
Patch

Attachment 328199 [details] did not pass mac-ews (mac):
Output: http://webkit-queues.webkit.org/results/5461862

New failing tests:
svg/as-image/svg-canvas-data-url-svg-with-foreign-object-not-tainted.html
Comment 4 EWS Watchlist 2017-12-01 18:44:10 PST
Created attachment 328204 [details]
Archive of layout-test-results from ews102 for mac-elcapitan

The attached test failures were seen while running run-webkit-tests on the mac-ews.
Bot: ews102  Port: mac-elcapitan  Platform: Mac OS X 10.11.6
Comment 5 Said Abou-Hallawa 2017-12-01 18:46:02 PST
Created attachment 328205 [details]
Patch
Comment 6 Darin Adler 2017-12-02 10:00:41 PST
Comment on attachment 328205 [details]
Patch

View in context: https://bugs.webkit.org/attachment.cgi?id=328205&action=review

> Source/WebCore/html/canvas/CanvasRenderingContext.cpp:85
> +    const URL url = image->sourceURL();
> +    if (url.protocolIsData())
> +        return false;

It seems strange to modify this function. Why wouldn't we want hasSingleSecurityOrigin to return true for an SVG image from a data URL? Are there other clients of that function that need to get false for that case?

The const here is peculiar and not needed, nor is a local variable needed. If this check is correct, then I would write:

    if (image->sourceURL().protocolIsData())
        return false;
Comment 7 Dean Jackson 2017-12-03 18:30:57 PST
What if the foreign object references cross origin content?
Comment 8 Said Abou-Hallawa 2017-12-04 12:38:15 PST
Created attachment 328375 [details]
Patch
Comment 9 Said Abou-Hallawa 2017-12-04 12:47:20 PST
Created attachment 328378 [details]
Patch
Comment 10 Said Abou-Hallawa 2017-12-04 13:19:47 PST
Comment on attachment 328205 [details]
Patch

View in context: https://bugs.webkit.org/attachment.cgi?id=328205&action=review

>> Source/WebCore/html/canvas/CanvasRenderingContext.cpp:85
>> +        return false;
> 
> It seems strange to modify this function. Why wouldn't we want hasSingleSecurityOrigin to return true for an SVG image from a data URL? Are there other clients of that function that need to get false for that case?
> 
> The const here is peculiar and not needed, nor is a local variable needed. If this check is correct, then I would write:
> 
>     if (image->sourceURL().protocolIsData())
>         return false;

I think we can't modify SVGImage::hasSingleSecurityOrigin() to check for the data url source because SVGFEImageElement::hasSingleSecurityOrigin() calls Image::hasSingleSecurityOrigin().

Suppose we have the following HTML page:

    var svg = new Image();
    svg.src = "url-to-an-svg-with-an-feimage-which-has-forigen-object.svg";
    svg.onload = function() {
        ctx.drawImage(svg, 0, 0);
    }

And we have the following url-to-an-svg-with-an-feimage-which-has-forigen-object.svg:

<svg>
    <filter id="image">
        <feImage xlink:href="data:image/svg+xml;..."/>
    </filter>
    <rect width="100" height="100" filter="url(#image)"/>
</svg>

And suppose we changed SVGImage::hasSingleSecurityOrigin() to look like this:

bool SVGImage::hasSingleSecurityOrigin()
{
+    if (sourceURL().protocolIsData())
+        return true;
...
}

Then this svg will not taint the canvas regardless of it is content. In this case, of the following call stack will be executed:

CanvasRenderingContext::wouldTaintOrigin() calls SVGImage::hasSingleSecurityOrigin() for the SVGImage "url-to-an-svg-with-an-feimage-which-has-forigen-object.svg".
    SVGImage::hasSingleSecurityOrigin() calls SVGFEImageElement::hasSingleSecurityOrigin() for the child <feimage>.
        SVGFEImageElement::hasSingleSecurityOrigin() calls SVGImage::hasSingleSecurityOrigin() for its data url image.
            SVGImage::hasSingleSecurityOrigin() will return true because if (sourceURL().protocolIsData()) will be true.

And this wrong. But you are right about other places that need to do the same check. I searched the source code and I found out that taintsOrigin() in ImageBitmap.cpp needs a similar check and I fixed it the same way.
Comment 11 Said Abou-Hallawa 2017-12-04 13:35:55 PST
(In reply to Dean Jackson from comment #7)
> What if the foreign object references cross origin content?

I think that's fine. The data uri image are embedded in the page. The tags inside the SVG <foreignObject> should be treated the same way all other tags in the HTML page are treated.

For example the text inside the <a> tag and the text which is drawn on the canvas should be treated the same way.

<a href="http://www.example.com/">link</a>
<canvas></canvas>
<script>
    var data = "data:image/svg+xml;charset=utf-8,"
        + '<svg xmlns="http://www.w3.org/2000/svg">'
        + '    <foreignObject width="100%" height="100%">'
        + '        <xhtml:a href="http://www.example.com/">link</xhtml:a>'
        + '    </foreignObject>'
        + '</svg>';

    var svg = new Image();
    svg.onload = function() {
        var canvas = document.querySelector('canvas');
        var ctx = canvas.getContext('2d');
            ctx.drawImage(svg, 0, 0);
    }
    svg.src = data;
</script>
Comment 12 Dean Jackson 2018-01-08 15:07:08 PST
(In reply to Said Abou-Hallawa from comment #10)
> Comment on attachment 328205 [details]
> 
> I think we can't modify SVGImage::hasSingleSecurityOrigin() to check for the
> data url source because SVGFEImageElement::hasSingleSecurityOrigin() calls
> Image::hasSingleSecurityOrigin().
> 
> Suppose we have the following HTML page:
> 
>     var svg = new Image();
>     svg.src = "url-to-an-svg-with-an-feimage-which-has-forigen-object.svg";
>     svg.onload = function() {
>         ctx.drawImage(svg, 0, 0);
>     }
> 
> And we have the following
> url-to-an-svg-with-an-feimage-which-has-forigen-object.svg:
> 
> <svg>
>     <filter id="image">
>         <feImage xlink:href="data:image/svg+xml;..."/>
>     </filter>
>     <rect width="100" height="100" filter="url(#image)"/>
> </svg>
> 
> And suppose we changed SVGImage::hasSingleSecurityOrigin() to look like this:
> 
> bool SVGImage::hasSingleSecurityOrigin()
> {
> +    if (sourceURL().protocolIsData())
> +        return true;
> ...
> }
> 
> Then this svg will not taint the canvas regardless of it is content. In this
> case, of the following call stack will be executed:
> 
> CanvasRenderingContext::wouldTaintOrigin() calls
> SVGImage::hasSingleSecurityOrigin() for the SVGImage
> "url-to-an-svg-with-an-feimage-which-has-forigen-object.svg".
>     SVGImage::hasSingleSecurityOrigin() calls
> SVGFEImageElement::hasSingleSecurityOrigin() for the child <feimage>.
>         SVGFEImageElement::hasSingleSecurityOrigin() calls
> SVGImage::hasSingleSecurityOrigin() for its data url image.
>             SVGImage::hasSingleSecurityOrigin() will return true because if
> (sourceURL().protocolIsData()) will be true.
> 
> And this wrong. 

Sorry for being dumb, but what is wrong here? The image in the FEImage is an SVGImage, and it does have a single security origin right? (since it is a data URL) So the uppermost SVG, used to draw into the context, has a single security origin and should not taint the canvas?
Comment 13 Said Abou-Hallawa 2018-01-08 15:41:50 PST
(In reply to Dean Jackson from comment #12)
> (In reply to Said Abou-Hallawa from comment #10)
> > Comment on attachment 328205 [details]
> > 
> > I think we can't modify SVGImage::hasSingleSecurityOrigin() to check for the
> > data url source because SVGFEImageElement::hasSingleSecurityOrigin() calls
> > Image::hasSingleSecurityOrigin().
> > 
> > Suppose we have the following HTML page:
> > 
> >     var svg = new Image();
> >     svg.src = "url-to-an-svg-with-an-feimage-which-has-forigen-object.svg";
> >     svg.onload = function() {
> >         ctx.drawImage(svg, 0, 0);
> >     }
> > 
> > And we have the following
> > url-to-an-svg-with-an-feimage-which-has-forigen-object.svg:
> > 
> > <svg>
> >     <filter id="image">
> >         <feImage xlink:href="data:image/svg+xml;..."/>
> >     </filter>
> >     <rect width="100" height="100" filter="url(#image)"/>
> > </svg>
> > 
> > And suppose we changed SVGImage::hasSingleSecurityOrigin() to look like this:
> > 
> > bool SVGImage::hasSingleSecurityOrigin()
> > {
> > +    if (sourceURL().protocolIsData())
> > +        return true;
> > ...
> > }
> > 
> > Then this svg will not taint the canvas regardless of it is content. In this
> > case, of the following call stack will be executed:
> > 
> > CanvasRenderingContext::wouldTaintOrigin() calls
> > SVGImage::hasSingleSecurityOrigin() for the SVGImage
> > "url-to-an-svg-with-an-feimage-which-has-forigen-object.svg".
> >     SVGImage::hasSingleSecurityOrigin() calls
> > SVGFEImageElement::hasSingleSecurityOrigin() for the child <feimage>.
> >         SVGFEImageElement::hasSingleSecurityOrigin() calls
> > SVGImage::hasSingleSecurityOrigin() for its data url image.
> >             SVGImage::hasSingleSecurityOrigin() will return true because if
> > (sourceURL().protocolIsData()) will be true.
> > 
> > And this wrong. 
> 
> Sorry for being dumb, but what is wrong here? The image in the FEImage is an
> SVGImage, and it does have a single security origin right? (since it is a
> data URL) So the uppermost SVG, used to draw into the context, has a single
> security origin and should not taint the canvas?

You are not "dumb" Dean :). It took me awhile to remember why I wrote this comment and I realized this area is very confusing.

The html and the svg do not have the same security origin "if the svg includes foreign objects." See the for-loop in SVGImage::hasSingleSecurityOrigin() and <http://trac.webkit.org/changeset/195614>. See also the layout test LayoutTests/svg/as-image/svg-canvas-svg-with-feimage-with-link-tainted.html which looks very similar to what I described above. In fact this test case will fail if I implement Darin's suggestion.

I think the fix is to check whether the uppermost SVG is a data uri or not. If it is then, we should not taint the canvas. Putting the check of protocolIsData() at the beginning of SVGImage::hasSingleSecurityOrigin() will bypass the check of the <foreignObject> even if the uppermost SVG is not a data uri.
Comment 14 Said Abou-Hallawa 2018-01-08 15:58:48 PST
Created attachment 330750 [details]
Patch
Comment 15 Said Abou-Hallawa 2018-01-08 16:00:04 PST
(In reply to Said Abou-Hallawa from comment #14)
> Created attachment 330750 [details]
> Patch

I re-uploaded the same patch to make sure it can still be applied to the trunk.
Comment 16 WebKit Commit Bot 2018-01-08 16:35:39 PST
Comment on attachment 330750 [details]
Patch

Clearing flags on attachment: 330750

Committed r226599: <https://trac.webkit.org/changeset/226599>
Comment 17 WebKit Commit Bot 2018-01-08 16:35:41 PST
All reviewed patches have been landed.  Closing bug.