High firefox Logic Error 🔧 Commit mapped

Overview

High
Severity
—
CVSS
No
Exploited ITW
Fixed
Fix Status
Impacthigh
DescriptionUse-after-free in the Internationalization component
ComponentDOM
Bug ClassLogic Error
Tracker2073685
Fix commit671c6f648c03 (firefox) +80/-106
CISA KEVNot listed
CreditedMozilla
Disclosed2026-10-27

Changed Functions

FunctionChangeNotes
ProcessTypedArraysFixed
dom/compression/BaseAlgorithms.cpp
modified

Files Changed

  • dom/compression/BaseAlgorithms.cpp
  • dom/compression/BaseAlgorithms.h
  • dom/encoding/TextDecoderStream.cpp
  • dom/encoding/TextEncoderStream.cpp
  • dom/streams/TransformerCallbackHelpers.cpp
  • dom/streams/TransformerCallbackHelpers.h
  • testing/web-platform/tests/encoding/streams/crashtests/from-cross-realm.html
diff --git a/dom/compression/BaseAlgorithms.cpp b/dom/compression/BaseAlgorithms.cpp
index b680d57bbdc..d5fe06684d6 100644
--- a/dom/compression/BaseAlgorithms.cpp
+++ b/dom/compression/BaseAlgorithms.cpp
@@ -17,36 +17,29 @@ namespace mozilla::dom::compression {
 // runs the compress and enqueue a chunk algorithm with this and chunk.
 MOZ_CAN_RUN_SCRIPT
 void CompressionStreamAlgorithms::TransformCallbackImpl(
-    JS::Handle<JS::Value> aChunk, TransformStreamDefaultController& aController,
-    ErrorResult& aRv) {
-  AutoJSAPI jsapi;
-  if (!jsapi.Init(aController.GetParentObject())) {
-    aRv.ThrowUnknownError("Internal error");
-    return;
-  }
-  JSContext* cx = jsapi.cx();
-
+    JSContext* aCx, JS::Handle<JS::Value> aChunk,
+    TransformStreamDefaultController& aController, ErrorResult& aRv) {
   // https://compression.spec.whatwg.org/#compress-and-enqueue-a-chunk
 
   // Step 1: If chunk is not a BufferSource type, then throw a TypeError.
-  RootedUnion<OwningBufferSource> bufferSource(cx);
-  if (!bufferSource.Init(cx, aChunk)) {
+  RootedUnion<OwningBufferSource> bufferSource(aCx);
+  if (!bufferSource.Init(aCx, aChunk)) {
     aRv.MightThrowJSException();
-    aRv.StealExceptionFromJSContext(cx);
+    aRv.StealExceptionFromJSContext(aCx);
     return;
   }
 
   // Steps 2 - 4:
-  JS::RootedVector<JSObject*> array(cx);
+  JS::RootedVector<JSObject*> array(aCx);
   ProcessTypedArraysFixed(bufferSource, [&](const Span<uint8_t>& aData) {
-    Compress(cx, aData, &array, Flush::No, aRv);
+    Compress(aCx, aData, &array, Flush::No, aRv);
   });
   if (aRv.Failed()) {
     return;
   }
 
   // Step 5: For each Uint8Array array, enqueue array in cs's transform.
-  Enqueue(cx, array, aController, aRv);
+  Enqueue(aCx, array, aController, aRv);
 }
 
 // Step 4 of
@@ -54,24 +47,18 @@ void CompressionStreamAlgorithms::TransformCallbackImpl(
 // Let flushAlgorithm be an algorithm which takes no argument and runs the
 // compress flush and enqueue algorithm with this.
 MOZ_CAN_RUN_SCRIPT void CompressionStreamAlgorithms::FlushCallbackImpl(
-    TransformStreamDefaultController& aController, ErrorResult& aRv) {
-  AutoJSAPI jsapi;
-  if (!jsapi.Init(aController.GetParentObject())) {
-    aRv.ThrowUnknownError("Internal error");
-    return;
-  }
-  JSContext* cx = jsapi.cx();
-
+    JSContext* aCx, TransformStreamDefaultController& aController,
+    ErrorResult& aRv) {
   // https://compression.spec.whatwg.org/#compress-flush-and-enqueue
   // Step 1-3:
-  JS::RootedVector<JSObject*> arrays(cx);
-  Compress(cx, Span<const uint8_t>(), &arrays, Flush::Yes, aRv);
+  JS::RootedVector<JSObject*> arrays(aCx);
+  Compress(aCx, Span<const uint8_t>(), &arrays, Flush::Yes, aRv);
   if (aRv.Failed()) {
     return;
   }
 
   // Step 4: For each Uint8Array array, enqueue array in cs's transform.
-  Enqueue(cx, arrays, aController, aRv);
+  Enqueue(aCx, arrays, aController, aRv);
 }
 
 MOZ_CAN_RUN_SCRIPT void CompressionStreamAlgorithms::Enqueue(
@@ -93,22 +80,15 @@ MOZ_CAN_RUN_SCRIPT void CompressionStreamAlgorithms::Enqueue(
 // runs the compress and enqueue a chunk algorithm with this and chunk.
 MOZ_CAN_RUN_SCRIPT
 void DecompressionStreamAlgorithms::TransformCallbackImpl(
-    JS::Handle<JS::Value> aChunk, TransformStreamDefaultController& aController,
-    ErrorResult& aRv) {
-  AutoJSAPI jsapi;
-  if (!jsapi.Init(aController.GetParentObject())) {
-    aRv.ThrowUnknownError("Internal error");
-    return;
-  }
-  JSContext* cx = jsapi.cx();
-
+    JSContext* aCx, JS::Handle<JS::Value> aChunk,
+    TransformStreamDefaultController& aController, ErrorResult& aRv) {
   // https://compression.spec.whatwg.org/#decompress-and-enqueue-a-chunk
 
   // Step 1: If chunk is not a BufferSource type, then throw a TypeError.
-  RootedUnion<OwningBufferSource> bufferSource(cx);
-  if (!bufferSource.Init(cx, aChunk)) {
+  RootedUnion<OwningBufferSource> bufferSource(aCx);
+  if (!bufferSource.Init(aCx, aChunk)) {
     aRv.MightThrowJSException();
-    aRv.StealExceptionFromJSContext(cx);
+    aRv.StealExceptionFromJSContext(aCx);
     return;
   }
 
@@ -117,17 +97,17 @@ void DecompressionStreamAlgorithms::TransformCallbackImpl(
   // TypeError.
   // Step 4: Let arrays be the result of splitting buffer into one or more
   // non-empty pieces and converting them into Uint8Arrays.
-  JS::RootedVector<JSObject*> array(cx);
+  JS::RootedVector<JSObject*> array(aCx);
   bool fullyConsumed = false;
   ProcessTypedArraysFixed(bufferSource, [&](const Span<uint8_t>& aData) {
-    fullyConsumed = Decompress(cx, aData, &array, Flush::No, aRv);
+    fullyConsumed = Decompress(aCx, aData, &array, Flush::No, aRv);
   });
   if (aRv.Failed()) {
     return;
   }
 
   // Step 5: For each Uint8Array array, enqueue array in ds's transform.
-  Enqueue(cx, array, aController, aRv);
+  Enqueue(aCx, array, aController, aRv);
   if (aRv.Failed()) {
     return;
   }
@@ -144,25 +124,19 @@ void DecompressionStreamAlgorithms::TransformCallbackImpl(
 // Let flushAlgorithm be an algorithm which takes no argument and runs the
 // compress flush and enqueue algorithm with this.
 MOZ_CAN_RUN_SCRIPT void DecompressionStreamAlgorithms::FlushCallbackImpl(
-    TransformStreamDefaultController& aController, ErrorResult& aRv) {
-  AutoJSAPI jsapi;
-  if (!jsapi.Init(aController.GetParentObject())) {
-    aRv.ThrowUnknownError("Internal error");
-    return;
-  }
-  JSContext* cx = jsapi.cx();
-
-  JS::RootedVector<JSObject*> array(cx);
+    JSContext* aCx, TransformStreamDefaultController& aController,
+    ErrorResult& aRv) {
+  JS::RootedVector<JSObject*> array(aCx);
 
   // https://compression.spec.whatwg.org/#decompress-flush-and-enqueue
   // Step 1 and 2.1:
-  (void)Decompress(cx, Span<const uint8_t>(), &array, Flush::Yes, aRv);
+  (void)Decompress(aCx, Span<const uint8_t>(), &array, Flush::Yes, aRv);
   if (aRv.Failed()) {
     return;
   }
 
   // Step 2.2: For each Uint8Array array, enqueue array in ds's transform.
-  Enqueue(cx, array, aController, aRv);
+  Enqueue(aCx, array, aController, aRv);
   if (aRv.Failed()) {
     return;
   }
diff --git a/dom/compression/BaseAlgorithms.h b/dom/compression/BaseAlgorithms.h
index b99c2fefe1f..1deb2b1190d 100644
--- a/dom/compression/BaseAlgorithms.h
+++ b/dom/compression/BaseAlgorithms.h
@@ -22,7 +22,7 @@ class CompressionStreamAlgorithms : public TransformerAlgorithmsWrapper {
   // Let transformAlgorithm be an algorithm which takes a chunk argument and
   // runs the compress and enqueue a chunk algorithm with this and chunk.
   MOZ_CAN_RUN_SCRIPT
-  void TransformCallbackImpl(JS::Handle<JS::Value> aChunk,
+  void TransformCallbackImpl(JSContext* aCx, JS::Handle<JS::Value> aChunk,
                              TransformStreamDefaultController& aController,
                              ErrorResult& aRv) override;
 
@@ -31,7 +31,8 @@ class CompressionStreamAlgorithms : public TransformerAlgorithmsWrapper {
   // Let flushAlgorithm be an algorithm which takes no argument and runs the
   // compress flush and enqueue algorithm with this.
   MOZ_CAN_RUN_SCRIPT void FlushCallbackImpl(
-      TransformStreamDefaultController& aController, ErrorResult& aRv) override;
+      JSContext* aCx, TransformStreamDefaultController& aController,
+      ErrorResult& aRv) override;
 
  protected:
   static const uint16_t kBufferSize = 16384;
@@ -60,7 +61,7 @@ class DecompressionStreamAlgorithms : public TransformerAlgorithmsWrapper {
   // Let transformAlgorithm be an algorithm which takes a chunk argument and
   // runs the compress and enqueue a chunk algorithm with this and chunk.
   MOZ_CAN_RUN_SCRIPT
-  void TransformCallbackImpl(JS::Handle<JS::Value> aChunk,
+  void TransformCallbackImpl(JSContext* aCx, JS::Handle<JS::Value> aChunk,
                              TransformStreamDefaultController& aController,
                              ErrorResult& aRv) override;
 
@@ -69,7 +70,8 @@ class DecompressionStreamAlgorithms : public TransformerAlgorithmsWrapper {
   // Let flushAlgorithm be an algorithm which takes no argument and runs the
   // compress flush and enqueue algorithm with this.
   MOZ_CAN_RUN_SCRIPT void FlushCallbackImpl(
-      TransformStreamDefaultController& aController, ErrorResult& aRv) override;
+      JSContext* aCx, TransformStreamDefaultController& aController,
+      ErrorResult& aRv) override;
 
  protected:
   static const uint16_t kBufferSize = 16384;
diff --git a/dom/encoding/TextDecoderStream.cpp b/dom/encoding/TextDecoderStream.cpp
index 607dd101f3a..f12c5305fdb 100644
--- a/dom/encoding/TextDecoderStream.cpp
+++ b/dom/encoding/TextDecoderStream.cpp
@@ -93,7 +93,7 @@ class TextDecoderStreamAlgorithms : public TransformerAlgorithmsWrapper {
 
   // https://encoding.spec.whatwg.org/#dom-textdecoderstream
   MOZ_CAN_RUN_SCRIPT void TransformCallbackImpl(
-      JS::Handle<JS::Value> aChunk,
+      JSContext* aCx, JS::Handle<JS::Value> aChunk,
       TransformStreamDefaultController& aController,
       ErrorResult& aRv) override {
     // Step 7. Let transformAlgorithm be an algorithm which takes a chunk
@@ -102,43 +102,29 @@ class TextDecoderStreamAlgorithms : public TransformerAlgorithmsWrapper {
 
     // https://encoding.spec.whatwg.org/#decode-and-enqueue-a-chunk
 
-    AutoJSAPI jsapi;
-    if (!jsapi.Init(aController.GetParentObject())) {
-      aRv.ThrowUnknownError("Internal error");
-      return;
-    }
-    JSContext* cx = jsapi.cx();
-
     // Step 1. Let bufferSource be the result of converting chunk to an
     // [AllowShared] BufferSource.
-    RootedUnion<OwningBufferSource> bufferSource(cx);
-    if (!bufferSource.Init(cx, aChunk)) {
+    RootedUnion<OwningBufferSource> bufferSource(aCx);
+    if (!bufferSource.Init(aCx, aChunk)) {
       aRv.MightThrowJSException();
-      aRv.StealExceptionFromJSContext(cx);
+      aRv.StealExceptionFromJSContext(aCx);
       return;
     }
 
-    DecodeBufferSourceAndEnqueue(cx, &bufferSource, false, aController, aRv);
+    DecodeBufferSourceAndEnqueue(aCx, &bufferSource, false, aController, aRv);
   }
 
   // https://encoding.spec.whatwg.org/#dom-textdecoderstream
   MOZ_CAN_RUN_SCRIPT void FlushCallbackImpl(
-      TransformStreamDefaultController& aController,
+      JSContext* aCx, TransformStreamDefaultController& aController,
       ErrorResult& aRv) override {
     // Step 8. Let flushAlgorithm be an algorithm which takes no arguments and
     // runs the flush and enqueue algorithm with this.
 
-    AutoJSAPI jsapi;
-    if (!jsapi.Init(aController.GetParentObject())) {
-      aRv.ThrowUnknownError("Internal error");
-      return;
-    }
-    JSContext* cx = jsapi.cx();
-
     // https://encoding.spec.whatwg.org/#flush-and-enqueue
     // (The flush and enqueue algorithm is basically a subset of decode and
     // enqueue one, so let's reuse it)
-    DecodeBufferSourceAndEnqueue(cx, nullptr, true, aController, aRv);
+    DecodeBufferSourceAndEnqueue(aCx, nullptr, true, aController, aRv);
   }
 
  private:
diff --git a/dom/encoding/TextEncoderStream.cpp b/dom/encoding/TextEncoderStream.cpp
index d275802c264..9296c11dbba 100644
--- a/dom/encoding/TextEncoderStream.cpp
+++ b/dom/encoding/TextEncoderStream.cpp
@@ -139,51 +139,37 @@ class TextEncoderStreamAlgorithms : public TransformerAlgorithmsWrapper {
 
   // https://encoding.spec.whatwg.org/#dom-textencoderstream
   MOZ_CAN_RUN_SCRIPT void TransformCallbackImpl(
-      JS::Handle<JS::Value> aChunk,
+      JSContext* aCx, JS::Handle<JS::Value> aChunk,
       TransformStreamDefaultController& aController,
       ErrorResult& aRv) override {
     // Step 2. Let transformAlgorithm be an algorithm which takes a chunk
     // argument and runs the encode and enqueue a chunk algorithm with this and
     // chunk.
 
-    AutoJSAPI jsapi;
-    if (!jsapi.Init(aController.GetParentObject())) {
-      aRv.ThrowUnknownError("Internal error");
-      return;
-    }
-    JSContext* cx = jsapi.cx();
-
     // https://encoding.spec.whatwg.org/#encode-and-enqueue-a-chunk
 
     // Step 1. Let input be the result of converting chunk to a DOMString.
     // Step 2. Convert input to an I/O queue of code units.
     nsString str;
-    if (!ConvertJSValueToString(cx, aChunk, eStringify, eStringify, str)) {
+    if (!ConvertJSValueToString(aCx, aChunk, eStringify, eStringify, str)) {
       aRv.MightThrowJSException();
-      aRv.StealExceptionFromJSContext(cx);
+      aRv.StealExceptionFromJSContext(aCx);
       return;
     }
 
-    EncodeAndEnqueue(cx, str, aController, false, aRv);
+    EncodeAndEnqueue(aCx, str, aController, false, aRv);
   }
 
   // https://encoding.spec.whatwg.org/#dom-textencoderstream
   MOZ_CAN_RUN_SCRIPT void FlushCallbackImpl(
-      TransformStreamDefaultController& aController,
+      JSContext* aCx, TransformStreamDefaultController& aController,
       ErrorResult& aRv) override {
     // Step 3. Let flushAlgorithm be an algorithm which runs the encode and
     // flush algorithm with this.
 
-    AutoJSAPI jsapi;
-    if (!jsapi.Init(aController.GetParentObject())) {
-      aRv.ThrowUnknownError("Internal error");
-      return;
-    }
-    JSContext* cx = jsapi.cx();
-
     // The spec manually manages pending high surrogate here, but let's call the
     // encoder as it's managed there.
-    EncodeAndEnqueue(cx, u""_ns, aController, true, aRv);
+    EncodeAndEnqueue(aCx, u""_ns, aController, true, aRv);
   }
 
  private:
diff --git a/dom/streams/TransformerCallbackHelpers.cpp b/dom/streams/TransformerCallbackHelpers.cpp
index 18780d28772..379a546cb8b 100644
--- a/dom/streams/TransformerCallbackHelpers.cpp
+++ b/dom/streams/TransformerCallbackHelpers.cpp
@@ -5,7 +5,9 @@
 #include "TransformerCallbackHelpers.h"
 
 #include "StreamUtils.h"
+#include "jsapi.h"
 #include "mozilla/dom/Promise.h"
+#include "mozilla/dom/ScriptSettings.h"
 #include "mozilla/dom/TransformStreamDefaultController.h"
 
 using namespace mozilla::dom;
@@ -84,26 +86,42 @@ already_AddRefed<Promise> TransformerAlgorithms::FlushCallback(
 }
 
 already_AddRefed<Promise> TransformerAlgorithmsWrapper::TransformCallback(
-    JSContext*, JS::Handle<JS::Value> aChunk,
+    JSContext* aCx, JS::Handle<JS::Value> aChunk,
     TransformStreamDefaultController& aController, ErrorResult& aRv) {
+  // We are in the writer stream's realm, but we want to call the transform
+  // algorithm in the transform stream's realm to make sure the generated object
+  // are from the right realm.
   nsCOMPtr<nsIGlobalObject> global = aController.GetParentObject();
+  JSAutoRealm realm(aCx, global->GetGlobalJSObject());
+
+  JS::Rooted<JS::Value> chunk(aCx, aChunk);
+  if (!JS_WrapValue(aCx, &chunk)) {
+    aRv.MightThrowJSException();
+    aRv.StealExceptionFromJSContext(aCx);
+    return nullptr;
+  }
+
   return PromisifyAlgorithm(
       global,
-      [this, &aChunk, &aController](ErrorResult& aRv)
+      [this, aCx, &chunk, &aController](ErrorResult& aRv)
           MOZ_CAN_RUN_SCRIPT_FOR_DEFINITION {
-            return TransformCallbackImpl(aChunk, aController, aRv);
+            return TransformCallbackImpl(aCx, chunk, aController, aRv);
           },
       aRv);
 }
 
 already_AddRefed<Promise> TransformerAlgorithmsWrapper::FlushCallback(
-    JSContext*, TransformStreamDefaultController& aController,
+    JSContext* aCx, TransformStreamDefaultController& aController,
     ErrorResult& aRv) {
+  // Same potential realm difference, see also TransformCallback above
   nsCOMPtr<nsIGlobalObject> global = aController.GetParentObject();
+  JSAutoRealm realm(aCx, global->GetGlobalJSObject());
+
   return PromisifyAlgorithm(
       global,
-      [this, &aController](ErrorResult& aRv) MOZ_CAN_RUN_SCRIPT_FOR_DEFINITION {
-        return FlushCallbackImpl(aController, aRv);
-      },
+      [this, aCx, &aController](ErrorResult& aRv)
+          MOZ_CAN_RUN_SCRIPT_FOR_DEFINITION {
+            return FlushCallbackImpl(aCx, aController, aRv);
+          },
       aRv);
 }
diff --git a/dom/streams/TransformerCallbackHelpers.h b/dom/streams/TransformerCallbackHelpers.h
index 4f8173d2e27..6ebf3831052 100644
--- a/dom/streams/TransformerCallbackHelpers.h
+++ b/dom/streams/TransformerCallbackHelpers.h
@@ -86,11 +86,12 @@ class TransformerAlgorithmsWrapper : public TransformerAlgorithmsBase {
       ErrorResult& aRv) final;
 
   MOZ_CAN_RUN_SCRIPT virtual void TransformCallbackImpl(
-      JS::Handle<JS::Value> aChunk,
+      JSContext* aCx, JS::Handle<JS::Value> aChunk,
... (file diff truncated)
Loading diff…

Regression Test / PoC

shipped with the fix
diff --git a/testing/web-platform/tests/encoding/streams/crashtests/from-cross-realm.html b/testing/web-platform/tests/encoding/streams/crashtests/from-cross-realm.html
new file mode 100644
index 00000000000..0fcae0e2552
--- /dev/null
+++ b/testing/web-platform/tests/encoding/streams/crashtests/from-cross-realm.html
@@ -0,0 +1,7 @@
+<!doctype html>
+<script>
+const encoder = new TextEncoderStream();
+encoder.readable.getReader().read();
+const w = window.open();
+new w.WritableStreamDefaultWriter(encoder.writable).write({});
+</script>
Loading diff…