Isolated Heap & Friends - Object Allocation Hardening in Web Browsers
In a recent Microsoft Patch Tuesday, Internet Explorer recently introduced a new heap protection aimed at making the exploitation of use-after-free vulnerabilities more difficult. This blog post details the protection, how it works, and how it compares to similar protections present in Mozilla Firefox and Google Chrome.
Many people noted the huge amount of fixes for Internet Explorer vulnerabilities (59 in total) in the recent Microsoft Patch Tuesday release. Even more interesting is the addition of a new exploitation mitigation feature in IE, the Isolated Heap for DOM objects. This feature aims to harden the browser against the exploitation of use-after-free (UAF) vulnerabilities, as well as some other memory corruption bug classes.
Chrome and Firefox deploy similar mitigations which we will discuss and later in this blog post. First, let’s have a look at the generic pattern of exploiting use-after-free vulnerabilities and the new Isolated Heap protection in Internet Explorer.
Exploiting use-after-free vulnerabilities
Due to the way heaps typically work, recently freed chunks of memory are preferred for fulfilling requests for similarly sized allocations, in order to prevent fragmentation of the heap and make optimal use of CPU cache behaviour. An attacker trying to exploit a use-after-free vulnerability reliably will likely try to allocate controlled data in place of the recently freed object. Typically, this controlled data is either a arbitrarily controllable type (such as a string or ArrayBuffer), or an object of a different type to the one that has been freed (replacing the object with one of the same type has limited worth from an exploitation point of view).
The Isolated Heap in Internet Explorer
Most of the DOM objects and supporting objects are now allocated on a separate heap. This will prevent an attacker from easily allocating arbitrary data in the space of a freed DOM object. The separate heap is allocated during the initialisation phase of mshtml.dll:
; START OF FUNCTION CHUNK FOR __DllMainStartup@12
loc_63C94EA9: ; dwMaximumSize push 0 push 0 ; dwInitialSize push 0 ; flOptions call ds:__imp__HeapCreate@12 ; HeapCreate(x,x,x) mov _g_hIsolatedHeap, eax
The handle to the newly created heap is stored in a global variable. A call is made to the HeapSetInformation_LowFragmentation_Downlevel function, which will forcefully enable the Low Fragmentation Heap for this newly created heap. By following the references to the global variable, it is straight-forward to track down the allocation functions for the Isolated Heap:
The “Clear” variant will call HeapAlloc with the HEAP_ZERO_MEMORY flag set, which will zero all allocated memory before returning the newly allocated buffer. This is presumably done in order to prevent the exploitation of uninitialised memory vulnerabilities. Interestingly this is not done for all objects, only the following object types are being allocated by the non-zeroing variant:
The list of objects allocated by the zeroing variant is much larger and too long to list here, as it includes all HTML and SVG DOM Elements and supporting Element such as CTreeNode, CMarkup, CAttribute and others.
The most obvious shortcoming of this implementation is that the protection is only restricted to a subset of objects in the browser. However, it is an improvement as the chosen subset includes many of the objects which are complex, and prone to UAF conditions as a result.
Using the standard heap implementation (as opposed to a separate heap implementation) to try to prevent the exploitation of use-after-free vulnerabilities in a browser has also a few problems. Compared to the protections in other browsers, it should be fairly easy for an attacker to allocate objects of a different type over a freed object, especially when heap chunks are coalesced.
When the zeroing variant of the allocation function is used, the protection against uninitialised memory vulnerabilities appears to be effective for the subset of protected objects. However, a number of other objects exist that are unprotected or allocated without first zeroing the memory, and these objects may still be partially uninitialised.
Without a doubt the sudden introduction of this new mitigation technique on a Patch Tuesday will impact on actively exploited in-the-wild vulnerabilities, and at the very least will provide some headaches for attackers.
Presentation Arena and Frame Poisoning in Mozilla Firefox
Ever since the fork from Netscape, Firefox has used arena allocations for certain object types to recycle common object sizes and improve allocation efficiency. While this was initially done for performance reasons, it has some security benefits as well. In 2009, Mozilla added a protection called Frame Poisoning for some frame object allocations which were being heavily used during the page layout phase. Every object that is being freed will be replaced with a chosen pattern, making it easier to spot existing use-after-free vulnerabilities through use of the poisoned value (see here for an example).
The current implementation can be found in the nsPresArena, and supports three types of allocations:
- By object ID
- By frame ID
- By size
A separate free list is maintained for every object, frame ID and size allocation. During the lifetime of a presentation shell, a certain object or frame type will always be guaranteed to be allocated in a space that will only ever be filled with an object of the same type, or a poison value. This will prevent an attack from filling a freed object’s memory with arbitrary values.
While this protection is effective for frame object allocation, many other object types in Firefox are not protected in a similar way (e.g. DOM objects). Which still allows attacker to exploit use-after-free vulnerabilities for object types which are not protected.
PartitionAlloc in Google Chrome
Chrome uses Blink (a forked version of WebKit) as its rendering engine. One of the security features added to Blink since the fork from WebKit is a separate heap allocator called PartitionAlloc. PartitionAlloc separates allocations on its heap into partitions, depending on the type of objects that will be allocated within that partition. At present, there are four partitions:
- ObjectModelPartition – This stores all objects which are subclasses of the Node class. This roughly equates to almost all objects from the DOM tree.
- RenderingPartion – This stores all objects within the render tree. To understand how these objects relate to the DOM tree, this is a good talk by Eric Seidel from Google on the subject.
- “General Partition” – This stores all allocations served by WTF::fastMalloc (when the use of the system malloc is not specified).
Within each partition, allocations are further separated into buckets based on the size of the allocation. Separating allocations by size hopes to limit an attacker’s options for different objects that can be reliably allocated in place of the freed object.
For example, let’s say that an attacker has triggered the free of a DOM object that occupied an 176-byte chunk of memory on the heap. As discussed earlier in this post, prior to PartitionAlloc an attacker would have a number of options for allocations in place of the freed object. However, with PartitionAlloc, an attacker would be limited to allocating an object from the same partition (the ObjectModelPartition in this case) and of roughly the same size (176 bytes).
The worst case for this limitation is that a number of objects exist which fill these criteria (this can happen when the memory footprint of the freed object is the same size as a generic base class, such as HTMLElement), however there is still a smaller subset of candidate objects available to an attacker. In addition, replacing the freed object with a different object will either result in replacing the vtable pointer of the freed object with a similar one, or with a freelist pointer (depending on whether the replacement object is in use or free). In either case, the most flexible option (from the point of view of an attacker) would be replacing the memory with objects where the data is controlled completely (such as a Uint8Array). As these objects are allocated outside of the current PartitionRoot, this is not possible.
The best case scenario is that there is only one object which fulfills these criteria, and as a result it is only possible to replace the freed object with another object of the same type. This makes exploitation significantly more challenging, but not impossible (the newly allocated object may have a different state, or reference memory left over from the previous object which may now also be free).