The Left Branch Less Travelled: A Story of a Mozilla Firefox Use-After-Free Vulnerability

July 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:

Figure 1 - Proof-of-Concept

Running this proof of concept on an affected version of Firefox gives you the following crash and stack trace:

Figure 2 - 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:

 1) A div element is created.
 2) An option element is created.
 3) The option element is appended to the div element. The div is now the parent of the option element.
 4) A 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.
 5) A select element is created.

We go a bit deeper here:

When a select element is created in JavaScript, the function xul.dll!NS_NewHTMLSelectElement receives control. It allocates an object of 0x118 bytes for this select element:

Figure 3 - The xul.dll!NS_NewHTMLSelectElement Function

As you can see, at the end, a jump to mozilla::dom::HTMLSelectElement::HTMLSelectElement function is done.

Figure 4 - The mozilla::dom::HTMLSelectElement::HTMLSelectElement Function

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.

 6) The option element created at Step 2 is moved to the options collection of the select element. Doing this in JavaScript will cause the mozilla::dom::HTMLOptionsCollection::IndexedSetter function to be called (you can see this function is called in the stack trace shown in Figure 2).

Figure 5 - Program logic as seen in IDA

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 [0] 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:

Figure 6 - Reaching the nsINode::ReplaceOrInsertBefore Function

Within the 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.

Figure 7 - Calling the nsContentUtils::MaybeFireNodeRemoved Function

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:

Figure 8 - Triggering the 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:

Figure 9 - Avoiding the UAF via the AddRef Function

The Patch

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:

Figure 10 - Patch details

Conclusion

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.

You can find me on Twitter at @hosselot and follow the team for the latest in exploit techniques and security patches.