Medium firefox UAF 🔧 Commit mapped

Overview

Medium
Severity
CVSS
No
Exploited ITW
Fixed
Fix Status
Impactmoderate
DescriptionStarting in Firefox 143, the use of the native messaging API by web extensions on Windows could lead to crashes caused by use-after-free memory corruption.
ComponentToolkit
Bug ClassUAF
Tracker1991950
Fix commit97ba1a3a46a1 (firefox) +36/-3
CISA KEVNot listed
CreditedFilip Štamcar
Disclosed2025-10-14

Changed Functions

FunctionChangeNotes
hasPendingIO
toolkit/modules/subprocess/subprocess_win.worker.js
modified
maybeClose
toolkit/modules/subprocess/subprocess_win.worker.js
modified
if
toolkit/modules/subprocess/subprocess_win.worker.js
modified
onError
toolkit/modules/subprocess/subprocess_win.worker.js
modified
InputPipe
toolkit/modules/subprocess/subprocess_win.worker.js
modified
readBuffer
toolkit/modules/subprocess/subprocess_win.worker.js
modified
writeBuffer
toolkit/modules/subprocess/subprocess_win.worker.js
modified

Files Changed

  • toolkit/modules/subprocess/subprocess_shared_win.js
  • toolkit/modules/subprocess/subprocess_win.worker.js
diff --git a/toolkit/modules/subprocess/subprocess_shared_win.js b/toolkit/modules/subprocess/subprocess_shared_win.js
index 8fc98ac97c5..fcdc7277aea 100644
--- 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
index c7672828d1d..6186e70b588 100644
--- 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;
         }
Loading diff…