Last year, The UK’s National Cyber Security Centre (NCSC) reported a bug existed in the V8 compiler and Google patched it silently. The bug ID is 1003286 and you can find the details here. According to their report, it is NULL pointer dereference DoS bug which is not exploitable and it can be triggered via WASM code. During the deep analysis, we found that there is another way to trigger the bug and it is exploitable by leveraging V8 JIT compiler process.
In this blog, we describe the details to exploit this vulnerability and demonstrate the remote code execution.
For the code optimization, V8 JIT compiler uses the Node graphs and generates optimized native code by reducing them through several phase of optimization pipeline. This Node graphs are also used in WASM compiler to compile the WASM code to native code.
The Nodes are linked each other in the graph using “Use” structure as following.
By using this structure, Nodes can specify their input nodes and user nodes for traversing the graph to reduce. The Use structure contains bit-field that is used to hold multiple pieces of information.
The InputIndex field indicates the index of the input Node of this Use and this is used to locate the corresponding input Node of the user Node. However this field only provides 17 bits of space to store the index and there is no code to check this limitation. Therefore constructing a graph with a large number of input Nodes to a single Node can cause integer overflow. For example, 0x20002 indicates 0x2 as the input index.
This InputIndex field is used in Use::input_ptr and Use::from functions used to locate the user Node or corresponding input Node.
This can cause type confusion between Node, Use and its sub-fields.
NCSC constructed proof-of-concept (PoC) using WebAssembly and led to NULL pointer dereference. The WASM code optimization process is much simpler than JIT compiler and thus controlling the process is more difficult than using the JIT compiler, so they cannot avoid NULL pointer dereferencing.
During the analysis, we found the JIT code that can make a Node having a lot of input Nodes.
Using this function, we constructed the PoC different from NCSC’s triggering bug successfully and got more ability to control execution flow.
For successful exploitation, it is needed to generate vulnerable code leveraging the original bug and achieve remote code execution from it. As described above, the typical type of JIT bugs are invalid map check, bound check and typing bugs and it is easy to exploit them. So we decided to generate these types of vulnerable code and studied the method to eliminate MapCheck or BoundCheck Node in the graph by replacing the Opcode of the Node.
Node, Use and Operator are the structures we can cause type confusion and they have the following structures.
Above mentioned Use::input_ptr and Use::from functions are called from ReplaceWithValue function which is used for replacing a value Node with other node.
Here, the type of “old_to” is not Node by the type confusion and it can be the field of Use e.g. Use *next, *prev or bit_field.
If use->prev is NULL, old_to->first_use is replaced with use->next. From the fact that “old_to” can be Use, old_to->first_use can be the Operator of CheckMap Node. If old_to->first_use is replaced with use->next, the CheckMapNode->op->opcode becomes use->next->bit_field. In this case, the opcode of the CheckMap Node can be replaced with other opcode and this results in CheckMap Node elimination. This is the main scenario of first step of exploitation.
Based on this model, we created the code as following.
The Node graph before replacing JSStrictEqual Node created by Turbolizer satisfies our modeling.
The array MapCheck Node is successfully changed with other opcode (EffectPhi), but several problems occur due to the changing including crashes in the next phases of optimization. To overcome these problems, we trimmed the graph that causes the crash by triggering GraphTrimmingPhase of compilation pipeline and generated valid optimized native code not including array map checking that can be used for out-of-bound access.
Now, we can cause type confusion between PACKED_DOUBLE_ELEMENTS and DICTIONARY_ELEMENTS arrays and obtain arbitrary read/write and addrof primitives used for achieve remote code execution.
You can confirm it works by lauching the full exploit on Chrome version 77.
By the efforts of many security researchers interested in V8, a lot of vulnerabilities were discovered and V8 has become more strengthened. There are few vulnerabilities in V8 that can be exploited easily, in other words, critical vulnerabilities. This says deep dive analysis and more efforts are essentially needed to exploit the vulnerabilities in V8.
We expect our blog will help you.
Thanks for reading this. Here is the demo video.