The Left Branch Less Travelled: A Story of a Mozilla Firefox Use-After-Free VulnerabilityJuly 01, 2019 | Hossein Lotfi
In December 2018, Mozilla released Firefox version 64 via mfsa2018-29, which was originally discovered and reported by Nils. This release fixed several security issues, among them CVE-2018-18492, which is a use-after-free (UAF) vulnerability related to the
select element. We’ve discussed UAF bugs before, and we’ve seen vendors implement sweeping protections [PDF] in an attempt to eliminate them. Even today, it’s not unusual to discover UAF-related vulnerabilities in web browsers, so understanding them is essential in finding and fixing these bugs. In this blog, I provide you with more details regarding this particular UAF vulnerability and the patch released to address it.
Triggering the vulnerability
The following proof of concept can be used to trigger this issue:
Running this proof of concept on an affected version of Firefox gives you the following crash and stack trace:
As you can see, a read access violation is produced when dereferencing a memory address filled with 0xe5e5e5e5. This is a value used by jemalloc to poison freed memory. By “poisoning”, we mean filling freed memory with a recognizable pattern for diagnostic purposes. Preferably, the fill pattern does not correspond to accessible addresses, so that any attempt to dereference values loaded from the filled memory – for example, in the event of a use-after-free – will result in an immediate and distinctive crash.
Root cause analysis
The PoC consists of 6 lines. Let’s break it down line by line:
div element is created.
option element is created.
option element is appended to the
div element. The
div is now the parent of the
DOMNodeRemoved event listener is added to the
div element. This means that if the
option node is removed, the function we put here will be called.
select element is created.
We go a bit deeper here:
xul.dll!NS_NewHTMLSelectElement receives control. It allocates an object of 0x118 bytes for this select element:
As you can see, at the end, a jump to
mozilla::dom::HTMLSelectElement::HTMLSelectElement function is done.
Within this function, various fields of the newly allocated object are initialized. Note that another object of 0x38 bytes is also allocated, and it is initialized as an
HTMLOptionsCollection object. As a result, every
select element will have an options collection by default. Let`s move to last line.
option element created at Step 2 is moved to the options collection of the
mozilla::dom::HTMLOptionsCollection::IndexedSetter function to be called (you can see this function is called in the stack trace shown in Figure 2).
Here some checks are done by the browser. For example, if the option index is larger than the current length of the options collection, the options collection is enlarged via a call to the
mozilla::dom::HTMLSelectElement::SetLength function. In our PoC, it is zero due to  in line 6 (see Figure 1). Then a check is done at the blue block in Figure 5. If the index to be set is not equal to the options count of the options collection, the right branch is taken. In our PoC, the desired index value is 0, and the options count is also zero, so the left branch is taken. Thus, execution reaches the
nsINode::ReplaceOrInsertBefore function, as you can see below in the red block:
nsINode::ReplaceOrInsertBefore function, a call to the
nsContentUtils::MaybeFireNodeRemoved function is done to notify parent about child removal if it is listening for such an event.
As we set a
DOMNodeRemoved event listener on the
div element at line 4 of the PoC (see Figure 1), the function we put there is fired. In this function, first the
sel variable is set to 0. This removes the last reference to the
select element. Next, the function creates a huge array buffer. This produces memory pressure, causing the garbage collector to kick in. At this point the
select element object is freed as there are no longer any references to it. This freed memory will be poisoned by 0xe5e5e5e5. Finally, the function calls
alert to flush pending asynchronous tasks. Upon return from the
nsContentUtils::MaybeFireNodeRemoved function, the freed
select object is used to read a pointer that triggers a read access violation:
One interesting note here is that if right branch was taken, the exact same function (
nsINode::ReplaceOrInsertBefore) will be called, but just before this call, the
AddRef function will be used to increase the reference count of the
select object. Consequently, no use-after-free will occur:
Mozilla patched this vulnerability via changeset d4f3e119ae841008c1be59e72ee0a058e3803cf3. The main change is that the weak reference to the
select element within the options collection is replaced by a strong reference:
Despite being a well-known issue, UAF bugs continue to be a problem for multiple browsers. Just a couple of months ago, active attacks targeting Google Chrome used a UAF vulnerability. UAFs exist beyond the browser, too. The Linux kernel released a patch to address a denial-of-service condition that was caused by a UAF. Understanding how UAFs occur is a key to detecting them. Similar to buffer overflows, we’re unlikely to ever see the end of UAFs in software. However, proper coding and secure development practices can help eliminate, or at least lessen, the impact from UAFs in the future.