Bug 159586

Summary: Infinite Canvas context save() causes WebKit to crash
Product: WebKit Reporter: Said Abou-Hallawa <sabouhallawa>
Component: CanvasAssignee: Said Abou-Hallawa <sabouhallawa>
Status: RESOLVED FIXED    
Severity: Normal CC: achristensen, cdumez, commit-queue, darin, dbates, dino, esprehn+autocc, gyuyoung.kim, mkwst, simon.fraser
Priority: P2 Keywords: InRadar
Version: WebKit Nightly Build   
Hardware: Unspecified   
OS: Unspecified   
Attachments:
Description Flags
canvas-context-infinite-save
none
Patch
none
Patch
none
Patch
none
Patch
none
Patch
none
Patch
none
Patch none

Said Abou-Hallawa
Reported 2016-07-08 16:34:48 PDT
Created attachment 283216 [details] canvas-context-infinite-save If a developer adds a call to CanvasRenderingContext2D.save() in an animation without adding the corresponding CanvasRenderingContext2D.restore(), Webkit might end up crashing. Neither the code nor the specs species any limit on how many context state can be saved.
Attachments
canvas-context-infinite-save (196 bytes, text/html)
2016-07-08 16:34 PDT, Said Abou-Hallawa
no flags
Patch (4.88 KB, patch)
2016-07-08 18:20 PDT, Said Abou-Hallawa
no flags
Patch (5.45 KB, patch)
2016-07-08 21:35 PDT, Said Abou-Hallawa
no flags
Patch (6.73 KB, patch)
2016-07-25 16:55 PDT, Said Abou-Hallawa
no flags
Patch (6.76 KB, patch)
2016-07-25 17:43 PDT, Said Abou-Hallawa
no flags
Patch (6.82 KB, patch)
2016-07-25 18:16 PDT, Said Abou-Hallawa
no flags
Patch (6.85 KB, patch)
2016-07-26 09:15 PDT, Said Abou-Hallawa
no flags
Patch (6.85 KB, patch)
2016-07-26 11:29 PDT, Said Abou-Hallawa
no flags
Said Abou-Hallawa
Comment 1 2016-07-08 18:20:55 PDT
Said Abou-Hallawa
Comment 2 2016-07-08 19:28:31 PDT
Chris Dumez
Comment 3 2016-07-08 19:45:16 PDT
Comment on attachment 283233 [details] Patch View in context: https://bugs.webkit.org/attachment.cgi?id=283233&action=review > Source/WebCore/ChangeLog:6 > + https://bugs.webkit.org/show_bug.cgi?id=159586 Duplicate line? > Source/WebCore/html/canvas/CanvasRenderingContext2D.h:388 > + static const unsigned m_maxSaveCount = 1024; We don't use m_prefix for statics AFAIK. > LayoutTests/fast/canvas/canvas-context-save-limit.html:9 > + const RED = '#ff0000'; Missing a description(); > LayoutTests/fast/canvas/canvas-context-save-limit.html:14 > + var c=document.getElementById("canvas"); missing spaces around the = > LayoutTests/fast/canvas/canvas-context-save-limit.html:15 > + var ctx=c.getContext("2d"); missing spaces around the =
Said Abou-Hallawa
Comment 4 2016-07-08 21:35:34 PDT
Alex Christensen
Comment 5 2016-07-09 15:38:21 PDT
Here's the stack trace when it crashes: Crashed Thread: 0 Dispatch queue: com.apple.main-thread Exception Type: EXC_BREAKPOINT (SIGTRAP) Exception Codes: 0x0000000000000002, 0x0000000000000000 Exception Note: EXC_CORPSE_NOTIFY ... Thread 0 Crashed:: Dispatch queue: com.apple.main-thread 0 com.apple.WebCore 0x00007fffbe1378b2 WTF::Vector<WebCore::CanvasRenderingContext2D::State, 1ul, WTF::CrashOnOverflow, 16ul>::reserveCapacity(unsigned long) + 258 1 com.apple.WebCore 0x00007fffbe137919 void WTF::Vector<WebCore::CanvasRenderingContext2D::State, 1ul, WTF::CrashOnOverflow, 16ul>::appendSlowCase<WebCore::CanvasRenderingContext2D::State const&>(WebCore::CanvasRenderingContext2D::State const&&&) + 89 2 com.apple.WebCore 0x00007fffbe03d528 WebCore::CanvasRenderingContext2D::realizeSavesLoop() + 72 3 com.apple.WebCore 0x00007fffbe1311a4 WebCore::CanvasRenderingContext2D::setFillColor(WTF::String const&, WTF::Optional<float>) + 132 4 com.apple.WebCore 0x00007fffbe5f0012 WebCore::JSCanvasRenderingContext2D::setFillStyle(JSC::ExecState&, JSC::JSValue) + 82 5 com.apple.WebCore 0x00007fffbe5e57f6 WebCore::setJSCanvasRenderingContext2DFillStyle(JSC::ExecState*, long long, long long) + 150 6 com.apple.JavaScriptCore 0x00007fffb9d2c2cf JSC::callCustomSetter(JSC::ExecState*, JSC::JSValue, bool, JSC::JSObject*, JSC::JSValue, JSC::JSValue) + 31
Alex Christensen
Comment 6 2016-07-09 15:39:39 PDT
Comment on attachment 283244 [details] Patch View in context: https://bugs.webkit.org/attachment.cgi?id=283244&action=review > Source/WebCore/html/canvas/CanvasRenderingContext2D.h:388 > + static const unsigned MaxSaveCount = 1024; I can safely go much higher than this. Where did 1024 come from?
Said Abou-Hallawa
Comment 7 2016-07-11 11:27:41 PDT
Comment on attachment 283244 [details] Patch View in context: https://bugs.webkit.org/attachment.cgi?id=283244&action=review >> Source/WebCore/html/canvas/CanvasRenderingContext2D.h:388 >> + static const unsigned MaxSaveCount = 1024; > > I can safely go much higher than this. Where did 1024 come from? Yes it can go up to std::numeric_limits<unsigned>::max() / sizeof(State). But actually it is hard to track even tens of stacked states. And I think the only possible case for reaching this limit (1024) is a bug in the developer javascript code. I think also the specs does not mention it because it is system and implementation details. In our case we put the limit as std::numeric_limits<unsigned>::max() / sizeof(State) but the problem we crash when we reach this limit. Increasing this limit will not allow more flexibility for the developer to stack more states, but rather it will waste more memory for a buggy code.
Simon Fraser (smfr)
Comment 8 2016-07-21 11:59:04 PDT
Comment on attachment 283244 [details] Patch View in context: https://bugs.webkit.org/attachment.cgi?id=283244&action=review > Source/WebCore/ChangeLog:4 > + https://bugs.webkit.org/show_bug.cgi?id=159586 Is there a corresponding radar? >>> Source/WebCore/html/canvas/CanvasRenderingContext2D.h:388 >>> + static const unsigned MaxSaveCount = 1024; >> >> I can safely go much higher than this. Where did 1024 come from? > > Yes it can go up to std::numeric_limits<unsigned>::max() / sizeof(State). But actually it is hard to track even tens of stacked states. And I think the only possible case for reaching this limit (1024) is a bug in the developer javascript code. I think also the specs does not mention it because it is system and implementation details. In our case we put the limit as std::numeric_limits<unsigned>::max() / sizeof(State) but the problem we crash when we reach this limit. Increasing this limit will not allow more flexibility for the developer to stack more states, but rather it will waste more memory for a buggy code. What do other browsers do?
Daniel Bates
Comment 9 2016-07-21 12:20:03 PDT
Firefox for Mac version 47.0.1 crashes when I visit attachment 283216 [details].
Chris Dumez
Comment 10 2016-07-21 12:23:57 PDT
(In reply to comment #9) > Firefox for Mac version 47.0.1 crashes when I visit attachment 283216 [details] > [details]. Ditto for Chrome 52 on Mac.
Darin Adler
Comment 11 2016-07-21 19:37:04 PDT
Seems like a reasonable change to me. Not sure why 1024 specifically is best. I wonder if maybe it would be even better to do something like throw an exception when save is called instead of silently not saving.
Said Abou-Hallawa
Comment 12 2016-07-25 16:19:00 PDT
Said Abou-Hallawa
Comment 13 2016-07-25 16:55:51 PDT
Simon Fraser (smfr)
Comment 14 2016-07-25 17:10:49 PDT
Comment on attachment 284548 [details] Patch View in context: https://bugs.webkit.org/attachment.cgi?id=284548&action=review > Source/WebCore/html/canvas/CanvasRenderingContext2D.cpp:360 > + static NeverDestroyed<String> consoleMessage(ASCIILiteral("The number of unbalanced CanvasRenderingContext2D::save() has exceeded the limit.")); This should refer not to the C++ CanvasRenderingContext2D::save(), but to something the JS author is familiar with. It should also say what the limit is.
Said Abou-Hallawa
Comment 15 2016-07-25 17:19:53 PDT
(In reply to comment #11) > Seems like a reasonable change to me. Not sure why 1024 specifically is > best. I wonder if maybe it would be even better to do something like throw > an exception when save is called instead of silently not saving. The current limit is just an arbitrary number as well. It is limit of the Vector<State>. It is equal to std::numeric_limits<unsigned>::max() / sizeof(State). If the size of State increases or decreases, the limit will change as well. I view MaxSaveCount as the size of the stack for drawing a canvas which can be something like that: function draw1(ctx) { ctx.save(); draw2(ctx); ctx.restore(); } function draw2(ctx) { ctx.save(); draw3(ctx); ctx.restore(); } And so on for MaxSaveCount calls. How deep a calls tack can JSC handle? I do not know but I can't imagine a drawing scenario which can contain more than 10 unbalanced save() calls. To be in the safe side, I changed MaxSaveCount to be 1024 * 16. I added a console message to be printed when the limit is reached. The purpose of this fix/message is to notify the developer that his JS code has bug. Instead of just letting WebKit crashes because a bug in the developer's code, we print a message to show the problem.
Said Abou-Hallawa
Comment 16 2016-07-25 17:40:39 PDT
Comment on attachment 284548 [details] Patch View in context: https://bugs.webkit.org/attachment.cgi?id=284548&action=review >> Source/WebCore/html/canvas/CanvasRenderingContext2D.cpp:360 >> + static NeverDestroyed<String> consoleMessage(ASCIILiteral("The number of unbalanced CanvasRenderingContext2D::save() has exceeded the limit.")); > > This should refer not to the C++ CanvasRenderingContext2D::save(), but to something the JS author is familiar with. It should also say what the limit is. The message is changed to be "CanvasRenderingContext2D.save() has been called without a matching restore() too many times. Ignoring save()." > LayoutTests/fast/canvas/canvas-context-save-limit-expected.txt:2 > +CONSOLE MESSAGE: line 29: The number of unbalanced CanvasRenderingContext2D::save() has exceeded the limit. > +CONSOLE MESSAGE: line 29: The number of unbalanced CanvasRenderingContext2D::save() has exceeded the limit. This message is printed by CanvasRenderingContext2D::realizeSaves(). It is printed twice because of the statement "ctx.fillStyle = RED" in the test file. CanvasRenderingContext2D::setFillColor() calls realizeSaves() and then it calls setFillStyle() which calls realizeSaves() also.
Said Abou-Hallawa
Comment 17 2016-07-25 17:43:41 PDT
Said Abou-Hallawa
Comment 18 2016-07-25 18:16:21 PDT
WebKit Commit Bot
Comment 19 2016-07-26 09:08:45 PDT
Comment on attachment 284557 [details] Patch Rejecting attachment 284557 [details] from commit-queue. Failed to run "['/Volumes/Data/EWS/WebKit/Tools/Scripts/webkit-patch', '--status-host=webkit-queues.webkit.org', '--bot-id=webkit-cq-03', 'validate-changelog', '--check-oops', '--non-interactive', 284557, '--port=mac']" exit_code: 1 cwd: /Volumes/Data/EWS/WebKit ChangeLog entry in LayoutTests/ChangeLog contains OOPS!. Full output: http://webkit-queues.webkit.org/results/1756859
Said Abou-Hallawa
Comment 20 2016-07-26 09:15:34 PDT
Said Abou-Hallawa
Comment 21 2016-07-26 11:29:39 PDT
WebKit Commit Bot
Comment 22 2016-07-26 11:59:44 PDT
Comment on attachment 284607 [details] Patch Clearing flags on attachment: 284607 Committed r203729: <http://trac.webkit.org/changeset/203729>
WebKit Commit Bot
Comment 23 2016-07-26 11:59:53 PDT
All reviewed patches have been landed. Closing bug.
Note You need to log in before you can comment on or make changes to this bug.