Analysis of vulnerability CVE-2024-7965

On August 21, the Chrome browser received an update that fixed 37 security-related bugs. The attention of researchers around the world was drawn to the vulnerability CVE-2024-7965, described as an incorrect implementation in V8. In practice, this means the possibility of RCE in the browser renderer, which opens up space for further exploitation. Researchers' interest increased even more when on August 26, Google reported the use of CVE-2024-7965 "in the wild".

We analyzed this vulnerability, so you don't have to.


Analysis of vulnerability CVE-2024-7965 in software

Initial Patch Analysis

Unlike our previous research, where it was necessary to compare executable files, now there is no need to do anything like that: the entire V8 source code is public. However, some analysis still needs to be done to find the necessary commit. After some time of searching, we see the following:


Diagram showing the process of exploiting the CVE-2024-7965 vulnerability

Here we immediately notice an important detail: the patch is made to the V8 TurboFan component — the JS code optimizing compiler. TurboFan works on the principle of sea of nodes: first it builds a graph, performs optimizations on it, and then selects instructions for a specific architecture and generates machine code.

The function ZeroExtendsWord32ToWord64 has been fixed, which allows the compiler to check whether the value coming through different paths (the so-called phi nodes chain, phi nodes) always has the upper 32 bits equal to 0. If the compiler cannot prove that the upper 32 bits of the number are 0, it adds an additional check - compares the number with the maximum value of unsigned int (0xffffffff).

The potentially vulnerable function works as follows: it recursively traverses the graph of phi nodes in depth, marking the passed nodes as kUpperBitsGuaranteedZero. The marking of nodes occurs through writing to a custom vector phi_states_. If among the descendants there is at least one node whose upper bits are not guaranteed to be zero, then a recursive return occurs with the marking of all nodes on the path to this one as kNoGuarantee. If the node has already been marked, the compiler does not pass it.


Graph showing the spread of the CVE-2024-7965 vulnerability across software versions

Incorrect graph

If we look at the patch, we will notice that when entering a node for the first time, it resets all previously saved values for previous nodes. This leads us to think that the graph traversal makes incorrect markings. But how does this happen? After all, if we try to traverse a regular graph, we will notice that everything works correctly:

In the x86-64 processor architecture, the compiler considers that a positive constant has the upper 32 bits equal to 0, and a negative constant does not. When traversing such a graph, phi_states_ will be in a completely correct state.

In the pictures with graph traversal, the vertices that are defined as having the upper 32 bits equal to zero are marked in green, and those whose upper 32 bits are guaranteed not to be zero are marked in red. As we remember, if at least one descendant of a vertex does not have the upper 32 bits equal to zero, then the vertex itself is marked in red.

However, if the graph is cyclic, you can get an incorrect state of phi_states_: for example, if the traversal goes through the cycle earlier than through other neighboring nodes, phi_states_ will store an incorrect state.

It is possible to obtain a graph from phi-nodes, in which a certain node is marked in phi_states_ as green, however, the upper 32 bits of its descendant may not be zero. Thus, there is a path where the compiler "believes" our value, and we deceive it. This is exactly what the patch fixes. With each repeated call to the ZeroExtendsWord32ToWord64 function, it resets the state of phi_states_.

What critical can happen in such a situation? If we look at how the array indexing operation is compiled, we will notice that in 64-bit architectures, the compiler first tries to ensure that the index we are accessing has the upper 32 bits equal to zero, then puts it in a 64-bit register and performs the operation on memory. If the compiler "believes" that the upper 32 bits are zero, it removes the check that the number is less than 0xffffffff. Accordingly, we get a situation where if the upper bits in the 64-bit register are not zero at the time of indexing, an out-of-bounds access will occur.

Unfortunately, on the x86-64 architecture, it was not possible to obtain a memory corruption PoC due to the fact that when trying to cast a 64-bit number to a 32-bit one, the compilation is done in such a way as to guarantee zeroing of the upper 32 bits, so we cannot get such a register in which undefined values will be located. The comment at the beginning of the function describes this quite well:


Graph showing the number of attacks related to the CVE-2024-7965 vulnerability

However, in the ARM64 architecture, a similar function leaves undefined values in the upper bits, which allows us to get them not equal to zero at the time of array indexing:


Diagram illustrating the process of patching the CVE-2024-7965 vulnerability

Just as a serious chess player, without calculating the entire variation, can understand that this is the right move, at this stage we were sure that this was the correct way to exploit and proceeded to develop the PoC.

Exploitation, Impact, and Conclusion

So, our plan for exploiting the vulnerability:

  1. We obtain the necessary cyclic graph using loops and conditions.

  2. We initiate an incorrect state in phi_states_. Our PoC uses BigInt for this.

  3. We add a vertex TruncateInt64ToWord32 to the graph using a combination of Math.min with the operator >>> 0.

  4. We initiate the subsequent call in such a way that the index is the number where we placed the upper bits.

These steps allow us to achieve memory corruption and catch a segmentation fault in V8.

What does exploiting this vulnerability give? Since it allows attacking only devices with ARM64 processor architecture, it mostly affects Android smartphones and Apple laptops released after November 2020. If hackers have an exploit that allows escaping the browser sandbox, they can gain full control over the browser application: read passwords, steal user sessions.

At the same time, the separate exploitation of the considered vulnerability is also dangerous: if there is XSS on any of the subdomains of the site, you can get passwords and cookies from the main domain and all subdomains. It is not possible to obtain confidential data from other sites in this case, as the Site Isolation technology, which separates renderer processes, protects against this. However, even in this form, the vulnerability is critical, and if the version of Chrome on your devices is still less than 128.0.6613.84, you need to update your browser as soon as possible.

Fun fact: since when accessing an array by index we control the upper 32 bits, it is likely that there will be a situation where no additional vulnerability is needed to bypass the V8 sandbox and it will be possible to immediately proceed to exploiting the browser's sandbox itself.

Link to PoC.

Appendix. Graph obtained during exploitation


Screenshot of event logs related to the CVE-2024-7965 vulnerability

Author of the article:

Yuri Pazdnikov, Junior Vulnerability Research Specialist

Comments