Bug 147290 - Crash happens when calling removeEventListener for an SVG element which has an instance inside a <defs> element of shadow tree
Summary: Crash happens when calling removeEventListener for an SVG element which has a...
Status: RESOLVED FIXED
Alias: None
Product: WebKit
Classification: Unclassified
Component: SVG (show other bugs)
Version: 528+ (Nightly build)
Hardware: Unspecified Unspecified
: P2 Normal
Assignee: Said Abou-Hallawa
URL:
Keywords: InRadar
Depends on:
Blocks:
 
Reported: 2015-07-24 21:45 PDT by Said Abou-Hallawa
Modified: 2015-07-28 13:11 PDT (History)
9 users (show)

See Also:


Attachments
test case (will crash) (726 bytes, image/svg+xml)
2015-07-24 21:45 PDT, Said Abou-Hallawa
no flags Details
Patch (4.42 KB, patch)
2015-07-27 12:26 PDT, Said Abou-Hallawa
no flags Details | Formatted Diff | Diff
Patch (4.91 KB, patch)
2015-07-27 14:49 PDT, Said Abou-Hallawa
no flags Details | Formatted Diff | Diff
Patch (4.91 KB, patch)
2015-07-27 15:48 PDT, Said Abou-Hallawa
no flags Details | Formatted Diff | Diff
Patch (5.04 KB, patch)
2015-07-28 10:42 PDT, Said Abou-Hallawa
no flags Details | Formatted Diff | Diff
Patch (5.04 KB, patch)
2015-07-28 11:23 PDT, Said Abou-Hallawa
no flags Details | Formatted Diff | Diff
Patch (5.74 KB, patch)
2015-07-28 13:04 PDT, 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 2015-07-24 21:45:56 PDT
Created attachment 257510 [details]
test case (will crash)

1. Open the attached test case.

Result: WebKit crashes with the following call stack.

0   com.apple.JavaScriptCore      	0x0000000113384a17 WTFCrash + 39
1   com.apple.WebCore             	0x0000000116ab1e7a WebCore::SVGElement::removeEventListener(WTF::AtomicString const&, WebCore::EventListener*, bool) + 554 (SVGElement.cpp:620)
2   com.apple.WebCore             	0x0000000115d5c316 WebCore::jsNodePrototypeFunctionRemoveEventListener(JSC::ExecState*) + 678 (JSNode.cpp:872)
3   ???                           	0x00004539c5601028 0 + 76114426859560
4   com.apple.JavaScriptCore      	0x000000011311304e llint_entry + 25874
5   com.apple.JavaScriptCore      	0x000000011310c8f9 vmEntryToJavaScript + 361
6   com.apple.JavaScriptCore      	0x0000000112f6936c JSC::JITCode::execute(JSC::VM*, JSC::ProtoCallFrame*) + 252
7   com.apple.JavaScriptCore      	0x0000000112f4d7d8 JSC::Interpreter::executeCall(JSC::ExecState*, JSC::JSObject*, JSC::CallType, JSC::CallData const&, JSC::JSValue, JSC::ArgList const&) + 1464
8   com.apple.JavaScriptCore      	0x000000011299c84e JSC::call(JSC::ExecState*, JSC::JSValue, JSC::CallType, JSC::CallData const&, JSC::JSValue, JSC::ArgList const&) + 190
9   com.apple.JavaScriptCore      	0x000000011299c8b3 JSC::call(JSC::ExecState*, JSC::JSValue, JSC::CallType, JSC::CallData const&, JSC::JSValue, JSC::ArgList const&, WTF::NakedPtr<JSC::Exception>&) + 83
Comment 1 Said Abou-Hallawa 2015-07-24 21:46:58 PDT
<rdar://problem/21915355>
Comment 2 Alexey Proskuryakov 2015-07-26 15:23:47 PDT
This stack shows an assertion failure, but sounds like there is also a crash in release mode - is that correct?

The attached test case doesn't crash in shipping Safari/WebKit for me.
Comment 3 Said Abou-Hallawa 2015-07-27 11:14:10 PDT
(In reply to comment #2)
> This stack shows an assertion failure, but sounds like there is also a crash
> in release mode - is that correct?
> 
> The attached test case doesn't crash in shipping Safari/WebKit for me.

Yes the above is an assertion failure. Here is the crashing call stack which I got when opening the attached text case in the latest Safari

Thread 0 Crashed:: Dispatch queue: com.apple.main-thread
0   com.apple.WebCore             	0x00007fff896746f4 WebCore::EventListenerMap::removeFirstEventListenerCreatedFromMarkup(WTF::AtomicString const&) + 20
1   com.apple.WebCore             	0x00007fff89efdca2 WebCore::SVGElement::removeEventListener(WTF::AtomicString const&, WebCore::EventListener*, bool) + 290
2   com.apple.WebCore             	0x00007fff89250578 WebCore::jsNodePrototypeFunctionRemoveEventListener(JSC::ExecState*) + 424
3   ???                           	0x00003a8434e01028 0 + 64339497193512
4   com.apple.JavaScriptCore      	0x00007fff8548a376 llint_entry + 22925
5   com.apple.JavaScriptCore      	0x00007fff854847d9 vmEntryToJavaScript + 326
6   com.apple.JavaScriptCore      	0x00007fff853b3419 JSC::JITCode::execute(JSC::VM*, JSC::ProtoCallFrame*) + 169
7   com.apple.JavaScriptCore      	0x00007fff84f884ad JSC::Interpreter::executeCall(JSC::ExecState*, JSC::JSObject*, JSC::CallType, JSC::CallData const&, JSC::JSValue, JSC::ArgList const&) + 493
8   com.apple.JavaScriptCore      	0x00007fff85125507 JSC::call(JSC::ExecState*, JSC::JSValue, JSC::CallType, JSC::CallData const&, JSC::JSValue, JSC::ArgList const&, WTF::NakedPtr<JSC::Exception>&) + 71
9   com.apple.WebCore             	0x00007fff8924f41a WebCore::JSEventListener::handleEvent(WebCore::ScriptExecutionContext*, WebCore::Event*) + 1002
10  com.apple.WebCore             	0x00007fff896778db WebCore::EventTarget::fireEventListeners(WebCore::Event*, WebCore::EventTargetData*, WTF::Vector<WebCore::RegisteredEventListener, 1ul, WTF::CrashOnOverflow, 16ul>&) + 635
11  com.apple.WebCore             	0x00007fff8915a390 WebCore::EventTarget::fireEventListeners(WebCore::Event*) + 224
12  com.apple.WebCore             	0x00007fff891b0414 WebCore::DOMWindow::dispatchEvent(WTF::PassRefPtr<WebCore::Event>, WTF::PassRefPtr<WebCore::EventTarget>) + 260
13  com.apple.WebCore             	0x00007fff891c251b WebCore::DOMWindow::dispatchLoadEvent() + 347
14  com.apple.WebCore             	0x00007fff89185dd4 WebCore::Document::implicitClose() + 324
15  com.apple.WebCore             	0x00007fff89185853 WebCore::FrameLoader::checkCompleted() + 275
16  com.apple.WebCore             	0x00007fff89184624 WebCore::FrameLoader::finishedParsing() + 100
17  com.apple.WebCore             	0x00007fff891833c1 WebCore::Document::finishedParsing() + 417
18  com.apple.WebCore             	0x00007fff8915b9aa WebCore::DocumentWriter::end() + 58
19  com.apple.WebCore             	0x00007fff8914d55c WebCore::DocumentLoader::finishedLoading(double) + 268
20  com.apple.WebCore             	0x00007fff891e8809 WebCore::CachedResource::checkNotify() + 153
21  com.apple.WebCore             	0x00007fff894abe43 WebCore::CachedRawResource::finishLoading(WebCore::SharedBuffer*) + 227
22  com.apple.WebCore             	0x00007fff891e86a1 WebCore::SubresourceLoader::didFinishLoading(double) + 1153
23  com.apple.WebKit              	0x00007fff8ec57d0d WebKit::WebResourceLoader::didReceiveWebResourceLoaderMessage(IPC::Connection&, IPC::MessageDecoder&) + 561
24  com.apple.WebKit              	0x00007fff8ea9b458 IPC::Connection::dispatchMessage(std::__1::unique_ptr<IPC::MessageDecoder, std::__1::default_delete<IPC::MessageDecoder> >) + 102
25  com.apple.WebKit              	0x00007fff8ea9d984 IPC::Connection::dispatchOneMessage() + 114
26  com.apple.JavaScriptCore      	0x00007fff85570b45 WTF::RunLoop::performWork() + 437
27  com.apple.JavaScriptCore      	0x00007fff85571222 WTF::RunLoop::performWork(void*) + 34
28  com.apple.CoreFoundation      	0x00007fff8adf8ac1 __CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE0_PERFORM_FUNCTION__ + 17
29  com.apple.CoreFoundation      	0x00007fff8adeb0bc __CFRunLoopDoSources0 + 556
30  com.apple.CoreFoundation      	0x00007fff8adea5df __CFRunLoopRun + 927
31  com.apple.CoreFoundation      	0x00007fff8ade9fd8 CFRunLoopRunSpecific + 296
32  com.apple.HIToolbox           	0x00007fff8b6a86a1 RunCurrentEventLoopInMode + 235
33  com.apple.HIToolbox           	0x00007fff8b6a8437 ReceiveNextEventCommon + 432
34  com.apple.HIToolbox           	0x00007fff8b6a8277 _BlockUntilNextEventMatchingListInModeWithFilter + 71
35  com.apple.AppKit              	0x00007fff8d6e106f _DPSNextEvent + 1076
36  com.apple.AppKit              	0x00007fff8daae597 -[NSApplication _nextEventMatchingEventMask:untilDate:inMode:dequeue:] + 423
37  com.apple.AppKit              	0x00007fff8d6d6efa -[NSApplication run] + 682
38  com.apple.AppKit              	0x00007fff8d6590b2 NSApplicationMain + 1176
39  libxpc.dylib                  	0x00007fff91d08fa0 _xpc_objc_main + 793
40  libxpc.dylib                  	0x00007fff91d0a6ef xpc_main + 494
41  com.apple.WebKit.WebContent   	0x0000000105b79b4a 0x105b79000 + 2890
42  libdyld.dylib                 	0x00007fff950075ad start + 1
Comment 4 Said Abou-Hallawa 2015-07-27 12:07:02 PDT
This crash happens under these conditions:

1. An element 'parent' is defined to be referenced later 
2. A <defs> element 'parent-defs' is included inside 'parent'
3. An element 'child' is defined inside 'parent-defs'
4. A visible <use> element 'use-parent' references 'parent'
5. A addEventListener is made for 'child' before the onload fires
6. A removeEventListener is made for 'child' after the onload fires

Here is the explanation of why the crash happens in this case:

1. Because addEventListener() was called before onload fires, the event is only added to the element itself. No instances are created before the onload event.
2. SVGUseElement::updateShadowTree() is called as part of resolving the style of 'use-parent'. It calls cloneTarget() and then calls transferEventListenersToShadowTree().
3. 'use-parent' references 'parent' so the whole 'parent' tree is cloned as a shadow tree for 'use-parent'
4. 'parent-defs' is a child of 'parent' so it is cloned and its subtree which includes the cloned 'child'
5. The cloned 'child' is added to the instances()  of 'child' and its correspondingElement is set to 'child'
6. After finishing the cloning, removeDisallowedElementsFromSubtree() is called which removes the cloned 'parent-defs' from the shadow subtree. The cloned 'parent-defs' includes the clone of 'child' which is now inaccessible from 'use-parent'.
7. When transferEventListenersToShadowTree() is called we fail to transfer the EventListener from the 'child' to the cloned 'child' since its parent cloned 'parent-defs' was removed from the shadow tree of the 'parent' element. Yet the 'child' instances() list still contains the cloned 'child'
8. When SVGElement::removeEventListener() is called for 'child' 'element', it loops through its instances() list and calls Node::removeEventListener() for each instance. We fail to removeEventListener() for the cloned 'child' since addEventListener() was never called for it. So we end up crashing when calling EventListenerMap::removeFirstEventListenerCreatedFromMarkup() since instance->eventTargetData() is null. The only way to get it valid is to call addEventListener() which did not happen.
Comment 5 Said Abou-Hallawa 2015-07-27 12:26:02 PDT
Created attachment 257579 [details]
Patch
Comment 6 Simon Fraser (smfr) 2015-07-27 14:12:06 PDT
Comment on attachment 257579 [details]
Patch

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

> Source/WebCore/ChangeLog:10
> +        are disallowed and removed. Make sure, when disallowing  an element in the

Double space between "disallowing" and "an"
Comment 7 Said Abou-Hallawa 2015-07-27 14:49:51 PDT
Created attachment 257595 [details]
Patch
Comment 8 WebKit Commit Bot 2015-07-27 15:41:35 PDT
Comment on attachment 257595 [details]
Patch

Rejecting attachment 257595 [details] from commit-queue.

Failed to run "['/Volumes/Data/EWS/WebKit/Tools/Scripts/webkit-patch', '--status-host=webkit-queues.appspot.com', '--bot-id=webkit-cq-01', 'validate-changelog', '--check-oops', '--non-interactive', 257595, '--port=mac']" exit_code: 1 cwd: /Volumes/Data/EWS/WebKit

ChangeLog entry in LayoutTests/ChangeLog contains OOPS!.

Full output: http://webkit-queues.appspot.com/results/5146182305185792
Comment 9 Said Abou-Hallawa 2015-07-27 15:48:42 PDT
Created attachment 257606 [details]
Patch
Comment 10 WebKit Commit Bot 2015-07-27 16:40:21 PDT
Comment on attachment 257606 [details]
Patch

Clearing flags on attachment: 257606

Committed r187463: <http://trac.webkit.org/changeset/187463>
Comment 11 WebKit Commit Bot 2015-07-27 16:40:26 PDT
All reviewed patches have been landed.  Closing bug.
Comment 12 Jon Lee 2015-07-27 16:53:41 PDT
rdar://problem/21915355
Comment 13 Alexey Proskuryakov 2015-07-28 09:23:35 PDT
This caused memory corruption on SVG tests, detected by GuardMalloc and ASan.
Comment 14 Alexey Proskuryakov 2015-07-28 09:31:21 PDT
Rolled out manually in r187486.
Comment 15 Daniel Bates 2015-07-28 09:33:11 PDT
Re-opening bug per comment #14.
Comment 16 Said Abou-Hallawa 2015-07-28 10:42:43 PDT
Created attachment 257657 [details]
Patch
Comment 17 Said Abou-Hallawa 2015-07-28 10:55:09 PDT
(In reply to comment #13)
> This caused memory corruption on SVG tests, detected by GuardMalloc and ASan.

The memory corruption happened because pf calling descendantsOfType<SVGElement>(*element) after calling element->parentNode()->removeChild(element). removeChild() causes the element data to be garbage-collected so when firstChild() is called, a crash happens.

The fix is to call setCorrespondingElement() for the SVGElement descendants of element before removing the element from its parent children list. Also there is no need to call descendant.setCorrespondingElement(nullptr) if descendant.correspondingElement() is null. setCorrespondingElement() calls ensureSVGRareData() which allocates SVGElementRareData if it is null. All these descendants will be garbage-collected later anyway.
Comment 18 Said Abou-Hallawa 2015-07-28 11:23:44 PDT
Created attachment 257664 [details]
Patch
Comment 19 Said Abou-Hallawa 2015-07-28 11:30:16 PDT
This guardMalloc memory corruption actually emphasizes the original crash more and explains it better. We were not only trying to removeEventListener from an element which addEventListener was never called for it but we were referencing an abandoned child element. And the memory of this element is going to be claimed later by the garbage collector. So the type of crash depends on when the JavaScript call is made to WebCore to removeEventListener(). It can be referencing a null pointer or it can be referencing a freed memory.
Comment 20 Daniel Bates 2015-07-28 11:32:49 PDT
Comment on attachment 257664 [details]
Patch

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

> Source/WebCore/svg/SVGUseElement.cpp:336
> +        for (auto& descendant : descendantsOfType<SVGElement>(*element)) {
> +            if (descendant.correspondingElement())
> +                descendant.setCorrespondingElement(nullptr);
> +        }

We should consider nullifying the corresponding element in general when we remove an SVG child (see ContainerNode::removeChild()). Notice that we currently invalidate the instance associated with the remove node in SVGElement::childrenChanged().
Comment 21 Darin Adler 2015-07-28 11:53:01 PDT
(In reply to comment #17)
> Also
> there is no need to call descendant.setCorrespondingElement(nullptr) if
> descendant.correspondingElement() is null. setCorrespondingElement() calls
> ensureSVGRareData() which allocates SVGElementRareData if it is null.

I think this should be fixed by enhancing the setCorrespondingElement function to handle setting to null when it’s already null without allocating rare data, instead of working around it here.
Comment 22 Said Abou-Hallawa 2015-07-28 12:26:17 PDT
(In reply to comment #20)
> Comment on attachment 257664 [details]
> Patch
> 
> View in context:
> https://bugs.webkit.org/attachment.cgi?id=257664&action=review
> 
> > Source/WebCore/svg/SVGUseElement.cpp:336
> > +        for (auto& descendant : descendantsOfType<SVGElement>(*element)) {
> > +            if (descendant.correspondingElement())
> > +                descendant.setCorrespondingElement(nullptr);
> > +        }
> 
> We should consider nullifying the corresponding element in general when we
> remove an SVG child (see ContainerNode::removeChild()). Notice that we
> currently invalidate the instance associated with the remove node in
> SVGElement::childrenChanged().

I agree we should have a general solution which works for all the cases when the child is removed. However removeChild() is very generic and although SVGElement::childrenChanged() is called from the ContainerNode::removeChild(), knowing whether removing the child from the parent children tree will cause this child to be freed or not seems to need more investigation. I will log a separate bug to track this issue down.
Comment 23 Said Abou-Hallawa 2015-07-28 12:26:54 PDT
(In reply to comment #21)
> (In reply to comment #17)
> > Also
> > there is no need to call descendant.setCorrespondingElement(nullptr) if
> > descendant.correspondingElement() is null. setCorrespondingElement() calls
> > ensureSVGRareData() which allocates SVGElementRareData if it is null.
> 
> I think this should be fixed by enhancing the setCorrespondingElement
> function to handle setting to null when it’s already null without allocating
> rare data, instead of working around it here.

Good suggestion I am going to do it and update the patch.
Comment 24 Said Abou-Hallawa 2015-07-28 13:04:46 PDT
Created attachment 257672 [details]
Patch
Comment 25 Said Abou-Hallawa 2015-07-28 13:11:26 PDT
Comment on attachment 257672 [details]
Patch

Clearing flags on attachment: 257672

Committed r187504: <http://trac.webkit.org/changeset/187504>
Comment 26 Said Abou-Hallawa 2015-07-28 13:11:33 PDT
All reviewed patches have been landed.  Closing bug.