tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

commit 97ba1a3a46a1f3e1839162df339e21c951c96ad1
parent 4f57f90c265de2774990c22c21e7277024c8176a
Author: Rob Wu <rob@robwu.nl>
Date:   Tue,  7 Oct 2025 16:31:07 +0000

Bug 1991950 - Hold onto buffer while IO is pending r=gstoll

Differential Revision: https://phabricator.services.mozilla.com/D267719

Diffstat:
Mtoolkit/modules/subprocess/subprocess_shared_win.js | 1+
Mtoolkit/modules/subprocess/subprocess_win.worker.js | 38+++++++++++++++++++++++++++++++++++---
2 files changed, 36 insertions(+), 3 deletions(-)

diff --git a/toolkit/modules/subprocess/subprocess_shared_win.js b/toolkit/modules/subprocess/subprocess_shared_win.js @@ -88,6 +88,7 @@ Object.assign(win32, { ERROR_BROKEN_PIPE: 109, ERROR_INSUFFICIENT_BUFFER: 122, ERROR_ABANDONED_WAIT_0: 735, + ERROR_IO_INCOMPLETE: 996, ERROR_IO_PENDING: 997, FILE_ATTRIBUTE_NORMAL: 0x00000080, diff --git a/toolkit/modules/subprocess/subprocess_win.worker.js b/toolkit/modules/subprocess/subprocess_win.worker.js @@ -105,11 +105,20 @@ class Pipe extends BasePipe { debug(`Failed to associate IOCP: ${ctypes.winLastError}`); } + // this.buffer is set to an ArrayBuffer, which should not be reused nor + // released until the IO methods (ReadFile or WriteFile) have acknowledged + // completion, or reported an error other than ERROR_IO_PENDING. this.buffer = null; + // Whether this.buffer is part of a pending IO operation. + this.bufferIsPendingIO = false; + // When close(force = true) is called while IO is pending, we notify + // read()/write() callers of completion but internally we await a IOCP + // message for this pipe before closing the pipe for real. + this.awaitingBufferClose = false; } hasPendingIO() { - return !!this.pending.length; + return !!this.pending.length || this.bufferIsPendingIO; } maybeClose() {} @@ -138,6 +147,12 @@ class Pipe extends BasePipe { } this.pending.length = 0; + if ((this.bufferIsPendingIO &&= this.#checkIfBufferIsStillPendingIO())) { + // We cannot release the pipe (specifically this.buffer) until the + // pending ReadFile/WriteFile operation on the buffer completed. + this.awaitingBufferClose = true; + return this.closedPromise; + } this.buffer = null; if (!this.closed) { @@ -161,6 +176,18 @@ class Pipe extends BasePipe { onError() { this.close(true); } + + #checkIfBufferIsStillPendingIO() { + let numberOfBytesTransferred = win32.DWORD(); + let ok = libc.GetOverlappedResult( + this.handle, + this.overlapped.address(), + numberOfBytesTransferred.address(), + false + ); + // Ok or error other than ERROR_IO_INCOMPLETE means that I/O completed. + return !ok && ctypes.winLastError === win32.ERROR_IO_INCOMPLETE; + } } class InputPipe extends Pipe { @@ -189,7 +216,7 @@ class InputPipe extends Pipe { false ); - if (!ok) { + if (!ok && ctypes.winLastError !== win32.ERROR_IO_INCOMPLETE) { this.onError(); } } @@ -226,6 +253,7 @@ class InputPipe extends Pipe { */ readBuffer(count) { this.buffer = new ArrayBuffer(count); + this.bufferIsPendingIO = true; let ok = libc.ReadFile( this.handle, @@ -239,6 +267,7 @@ class InputPipe extends Pipe { !ok && (!this.process.handle || ctypes.winLastError !== win32.ERROR_IO_PENDING) ) { + this.bufferIsPendingIO = ctypes.winLastError === win32.ERROR_IO_PENDING; this.onError(); } else { io.updatePollEvents(); @@ -326,6 +355,7 @@ class OutputPipe extends Pipe { */ writeBuffer(buffer) { this.buffer = buffer; + this.bufferIsPendingIO = true; let ok = libc.WriteFile( this.handle, @@ -336,6 +366,7 @@ class OutputPipe extends Pipe { ); if (!ok && ctypes.winLastError !== win32.ERROR_IO_PENDING) { + this.bufferIsPendingIO = false; this.onError(); } else { io.updatePollEvents(); @@ -849,7 +880,8 @@ io = { debug(`IOCP notification for unknown pipe: ${pipeId}`); continue; } - if (deqWinErr === win32.ERROR_BROKEN_PIPE) { + pipe.bufferIsPendingIO = false; + if (deqWinErr === win32.ERROR_BROKEN_PIPE || pipe.awaitingBufferClose) { pipe.onError(); continue; }