Summary
PartitionAlloc is the hardened heap allocator used in Google's Chrome web browser. It is susceptible to a number of attacks. This blog post describes the first attack in a series of posts. I will talk about freelist poisoning and how to make an allocation request return an arbitrary pointer. This can be used with application-logic to develop an arbitrary write primitive.
Introduction
In heap allocators, freelists maintain a group of free memory chunks that are available to be recycled by an allocation request. Freelist poisoning corrupts this list and injects a "fake chunk" pointer. A later allocation will return this fake chunk pointer. So it is possible to make an allocation request return an arbitrary pointer.
I have blogged about freelist poisoning extensively. It is a common attack that many allocators are vulnerable to.
https://blog.infosectcbr.com.au/2020/03/weaknesses-in-linux-kernel-heap.html
https://blog.infosectcbr.com.au/2019/12/freelist-heap-exploitation-on-docker.html
https://blog.infosectcbr.com.au/2019/12/attacks-on-tcmalloc-heap-allocator.html
https://blog.infosectcbr.com.au/2019/11/avr-libc-freelist-poisoning.html
https://blog.infosectcbr.com.au/2019/11/diet-libc-freelist-poisoning.html
https://blog.infosectcbr.com.au/2019/07/linux-heap-tcache-poisoning.html
ParitionAlloc Freelist Poisoning
PartitionAlloc, like many allocators, maintains freelists. It keeps the pointers used in these freelist in the payload area of a free chunk of memory. The main difference between this approach and the typical freelist implementation, is that PartitionAlloc stores the pointer in big endian format on x86 or other little endian architectures, and as a bitwise complement on big endian architectures. Here is the code:
ALWAYS_INLINE PartitionFreelistEntry* partitionFreelistMask(PartitionFreelistEntry* ptr)
{
// We use bswap on little endian as a fast mask for two reasons:
// 1) If an object is freed and its vtable used where the attacker doesn't
// get the chance to run allocations between the free and use, the vtable
// dereference is likely to fault.
// 2) If the attacker has a linear buffer overflow and elects to try and
// corrupt a freelist pointer, partial pointer overwrite attacks are
// thwarted.
// For big endian, similar guarantees are arrived at with a negation.
#if CPU(BIG_ENDIAN)
uintptr_t masked = ~reinterpret_cast<uintptr_t>(ptr);
#else
uintptr_t masked = bswapuintptrt(reinterpret_cast<uintptr_t>(ptr));
#endif
return reinterpret_cast<PartitionFreelistEntry*>(masked);
}
The inline comment describes this "mitigation". It can prevent trivial off-by-1's and the like. However, if an attacker is able to overwrite the entire freelist pointer, then they can simply apply the correct transformation of the pointer.
I have moved ParitionAlloc out of Chrome and made it a standalone library for ease of testing. Here is an example of the freelist poisoning attack using this library.
And when we run that, we are able to gain an arbitrary write to foo and change it to 0x41414141424242. The attack works.
Conclusion
In this blog post, I demonstrated the classic freelist poisoning attack against PartitionAlloc. This allocator has a number of mitigations and hardening strategies. However, attacks still exist. In future blog posts I will talk about other attacks against this allocator.