Cross Cache for Lazy People -- The Padding Spray Method - Thu, Mar 14, 2024
TLDR
There is a proper way to cross cache (documented in wonderful detail by ruia-ruia) which involves strategically allocating and freeing objects, but some pwners (like me) are lazy. This post is about the “spray and pray” method of cross cache used by some pwners that still magically works, even if you don’t want to do math :D
A Brief Overview of Kernel Slab Caches
Originally all kernel objects were allocated in normal kmalloc caches, but with this kernel patch, objects allocated with GFP_KERNEL_ACCOUNT would go into a kmalloc-cg cache instead of a normal one.
This complicates exploitation for kernel pwners, as if the target victim object is allocated with GFP_KERNEL, it would end up in a non-cg kmalloc cache, and technically only objects that are allocated in the same cache can be used to reclaim the freed space (assuming that we have a UAF situation). This would heavily restrict the structs that we can use in kernel exploitation, as many nice things like struct msg_msg or struct pipe_buffer will end up in kmalloc-cg, but vulnerable objects tend to be in a normal kmalloc cache for some reason :(
As such, some stonk pwner invented the cross cache attack so that we would be able to continue using our beloved structs to reclaim and forge parts of victim objects to our liking :D
The objective of the cross cache attack is to make the kernel reallocate the slab with our victim object from its original cache to another cache where our target object will be allocated.
In order for the kernel to allocate a new slab to a particular cache, the following conditions must be fulfilled:
- CPU cache freelist is empty
- Page freelist is empty
- CPU partial list is empty
- Node freelist is empty
In short, if we spray enough of our target object, we will be able to exhaust all the freelists, and force the kernel to allocate a new slab to the target object’s cache.
Now, how do we allocate the slab with our victim object to our desired target object’s cache? We would first have to make the kernel free that particular slab before it can be reallocated. To free a slab, the following conditions must be fulfilled (refer to ruia-ruia’s writeup for a nice diagram):
- The CPU partial list is full – this will trigger a call to unfreeze_partials()
- The slab has absolutely zero allocated objects on it – this is SUPER IMPORTANT, and in my experience this is the usual reason for a cross cache to fail
Only if the slab is completely freed will a call be made to free_slab(), which will then give us the ability to allocate the freed slab to the cache that we want.
The Lazy Way To Cross Cache
There is a proper targeted way to cross cache as described in ruia-ruia’s writeup, but there is also a very lazy method. Let’s assume that our victim object is allocated in kmalloc-256, and we want to cross cache to an object such as msg_msg which is allocated in kmalloc-cg-256.
The gist of the method is as such:
- Spray a large amount of an object allocated in the same cache as the victim object as the “padding spray”
- Allocate the victim object
- Free the victim object via the vulnerability
- Reclaim the victim object by spraying more of the object allocated in the same cache
- Free the padding spray and the second spray
- Cross cache by spraying the target object
In this case, let’s use timerfd_ctx as our object allocated in the same cache as the victim object, and msg_msg as our cross cache target object. This means that we are trying to reallocate a slab originally allocated to the kmalloc-256 slab cache to the kmalloc-cg-256 slab cache.
Step 1: Spray object allocated in the same cache as the victim object – The “Padding Spray”
This spray will be done even before the victim object has been allocated. By spraying lots and lots of the same cache object, we are trying to exhaust the freelists for that particular cache (kmalloc-256), and trying to get the kernel to allocate a new slab to kmalloc-256. This will give us a clean slate to work with as when the new slab is allocated and partially filled with our padding spray, the only objects on the slab would either be the sprayed timerfd_ctx objects or objects that are yet to be allocated.
Note that the objects allocated may not be continguous in memory nor in the order that they were allocated – this is due to CONFIG_SLAB_FREELIST_RANDOM.
Step 2: Allocate the victim object
The victim object is now allocated, and the slab now only consists of the timerfd_ctx objects, the victim object, and free objects.
Step 3: Free the victim object
We now free the victim object via the vulnerability, giving us a UAF. The slab now only consists of timerfd_ctx objects and free objects.
Step 4: Reclaim the victim object
We will then spray even more of the object allocated in the same cache as the victim object to reclaim the freed space from the victim object. The entire slab is now filled with timerfd_ctx objects.
Step 5: Free the padding and second spray
We will now free every single object on the slab by freeing the initial padding spray, as well as the second spray used to reclaim the victim object. The entire slab is now completely free.
Step 6: Cross cache with target object
Finally, we can spray a massive amount of our target object (msg_msg in this case) allocated in our target cache (kmalloc-cg-256). This will use up all the freelists in our target cache, forcing the kernel to allocate a new slab. However, remember that the slab with our victim object has no allocated objects on it. That slab will hence be reallocated from its original kmalloc-256 cache to the target kmalloc-cg-256 cache, resulting in a successful cross cache attack.
This method is a lot less targeted than the proper method to cross cache, but if you mess around with the number of objects in the padding spray, second spray and target object spray, there will be a sweet spot where this will work.
Hope this was helpful, and thanks for reading! :D