128 - A Windows UAF, Branch Prediction Bugs and an io_uring Exploit
We have previously discussed this vulnerability, which provides a primitive to free adjacent memory.
This post covers more on the discovery of the issue and the exploitation techniques.
Heap Spray Primitive - Fairly common technique of using
setxattr to to spray the head along with something to block the copy from finishing. The idea being that you can get
setxattr to allocate a buffer with size of your choosing, then copies your data into it. Writes it to the extended attribute, and frees the buffer. So its useful just for spaying the heap with specific data when you can block the copy so it doesn’t get free’d too quickly. Blocking the copy was by having the memory be a mmap’d file backed by FUSE. So a userland application could block the return.
Memory Leak Primitive - This one was cool, and kinda turned the
setxattr primitive around. Rather than using
setxattr to spray the heap with data to fill a buffer that had been free’d instead she free’d the
setxattr buffer, so it would get reused by some other object. Unblocked that datacopy, so when the buffer is copied into the extended attribute it will contain the new objects data. Then could be read just using
The overall strategy here was to leak a few important structures: an
io_tctx_node for a
task_struct pointer (common target for Linux privelege escalation),
seq_operations an operations structure so it contains function pointers, useful to break kASLR. Then targeted an
sk_filter and its
prog field with an eBPF program attached. Crafting a malicious eBPF program that would overwrite the effective uid/gid of the previously leaked
I’ve of course glossed over some details of the exploit, its a great post to check out though. And at the surface this looks like a fairly education bug if someone wants to play around with some kernel exploitation.
A UAF in the Common Logging File System (CLFS). Some background is needed on how this custom filesystem works to provide context for the bug. All Base log files contain 3 different metadata blocks that are parsed when opening log files; Control records, which contain layout information and extended area info. Base records, which have symbol tables and other information like security context info. Finally, there’s truncate records, which contain info regarding sector changes for truncation ops. Each of these blocks also has a “shadow block” associated with it, where the metadata is essentially duplicated and backed up for restoration upon an event such as a torn write.
It’s also worth noting that it’s possible for blocks to be “extended” depending on how the control record is setup with it’s various sector offsets. The
eExtendState field is kept track of when operating on a log file.
Under normal circumstances, a stable logfile when being read from the disk will always have an extension state of
0. However, an attacker has full control over the metadata blocks, and can force them to be parsed as extended by manipulating the control record. Additionally, an attacker also has control over the index of the block to be extended or flushed via the extend context. By setting up a specially crafted file and getting
ExtendMetadataBlockDescriptor() called in the open path, it’s possible to reach a branch that will flush and free a targeted block’s
pbImage. Just after that free, the record will then be updated and refreshed with the contents from the that block’s mirroring shadow block. The problem is, the shadow block’s
pbImage points to the same memory that was just free’d, re-introducing a dangling reference.
This opens up the possibility to double free, which can be used to poison the pool allocator into granting a targeted use-after-free. In this case, they use the Windows Notification Facility (WNF) and the double free to overlap some state data objects, which they then use to get an out-of-bounds read/write to smash adjacent state data objects. This can be leveraged to change the size of an adjacent state data object in order to have it overlap with a token when free’d, which can be used to corrupt a process token to privilege escalate.
Yet another branch predictor bug was discovered by grsecurity when testing a performance optimization for Reuse Attack Protector (RAP) return hash sequences. RAP sequences have an unconditional jump, followed by a
movabs instruction and some
int3 instructions for debugging purposes. In a normal application, these instructions should never be executed and the unconditional indirect jump should be taken. Unfortunately for Zen 1 and Zen 2 AMD CPUs, there’s a window where these instructions could be executed speculatively due to straight-line speculation (SLS). This attack was detailed on ARM some years ago, though unfortunately there doesn’t seem to be many details on what the root cause is.
In the original ARM attack, SLS only occurred with indirect unconditional control flow changes. Unlike ARM though, in vulnerable AMD x86 CPUs, SLS can occur on both indirect and direct control flow changes. This is because on x86, the branch predictor has to predict both the condition and the target. The target is a weakpoint, as the predictor has to rely on the Branch Target Buffer (BTB) to start fetching instructions. If the BTB is flushed and no entry is present, it defaults to predicting the branch is not taken, when then allows for speculative execution of the following instructions.
This speculation, depending on the gadgets available, can leave behind secrets in the cache. To demonstrate a real world application of this bug, the authors deployed a custom eBPF filter which could setup a gadget that would do a cache load, which could be probed for the access times to leak bits of a kernel pointer and defeat kASLR.
An attack which extends upon branch target injection, which is an attack where you train the branch predictor on an indirect branch to speculatively execute a branch erroneously and side-channel the cache to leak data. One of the various mitigations introduced by Intel and ARM were Enhanced Indirect Branch Restricted Speculation (eIBRS) and CSV2 respectively. There’s two key terms that are important to keep in mind, the Branch Target Buffer (BTB) and the Branch History Buffer (BHB). What this mitigation did was it would tag BTB entries, and that tag is calculated by taking the branch source address and combining it with the history of past branches (stored in the BHB).
Due to performance considerations, the BHB is not isolated across privilege boundaries, because this would have a significant impact on things like syscalls. This is a weakness that allows an attacker to get a degree of control over the tags that are calculated for BTB entries. This bypasses the “authentication” on the BTB entries that’s provided by eIBRS/CSV2.