High chrome UAF 🔧 Commit mapped

Overview

High
Severity
—
CVSS
No
Exploited ITW
Fixed
Fix Status
ImpactUse after free in WebRTC
DescriptionUse after free in WebRTC
ComponentWebRTC
Bug ClassUAF
Tracker568945639
Fix commit6c38d4ec50ea (chromium/src) +208/-0
CISA KEVNot listed
CreditedC0rk1
Disclosed2026-10-20

Files Changed

  • third_party/blink/renderer/modules/peerconnection/rtc_encoded_audio_underlying_source_test.cc
  • third_party/blink/renderer/modules/peerconnection/rtc_encoded_audio_sender_source_optimizer.cc
  • third_party/blink/renderer/modules/peerconnection/rtc_encoded_audio_sender_sink_optimizer.cc
  • third_party/blink/renderer/modules/peerconnection/rtc_encoded_audio_underlying_sink_test.cc
  • third_party/blink/renderer/modules/peerconnection/rtc_encoded_video_sender_source_optimizer.cc
  • third_party/blink/renderer/modules/peerconnection/rtc_encoded_video_receiver_source_optimizer.cc
  • third_party/blink/renderer/modules/peerconnection/rtc_encoded_video_underlying_sink_test.cc
  • third_party/blink/renderer/modules/peerconnection/rtc_encoded_video_sender_sink_optimizer.cc
  • third_party/blink/renderer/modules/peerconnection/rtc_encoded_video_underlying_source_test.cc
  • third_party/blink/renderer/modules/peerconnection/rtc_encoded_video_receiver_sink_optimizer.cc
  • third_party/blink/renderer/modules/peerconnection/rtc_encoded_audio_receiver_source_optimizer.cc
  • third_party/blink/renderer/modules/peerconnection/rtc_encoded_audio_receiver_sink_optimizer.cc
From 6c38d4ec50ea5a153e2e616a39c06d6a4777b7c8 Mon Sep 17 00:00:00 2001
From: Guido Urdaneta <guidou@chromium.org>
Date: Fri, 16 Oct 2026 00:23:42 -0700
Subject: [PATCH] Do not optimize RTC encoded stream transfer if context is destroyed

When a ReadableStream created by RTCRtpReceiver::createEncodedStreams()
or RTCRtpSender::createEncodedStreams() is transferred and deserialized
in a destination ExecutionContext that has already been destroyed,
PerformInProcessOptimization() previously constructed a new
RTCEncoded{Audio,Video}UnderlyingSource on the destroyed context and
bound it to the active WebRTC encoded stream transformer. Because
NotifyContextDestroyed() had already fired on the destination context,
ContextDestroyed() would never be invoked to unregister the transformer
callback.

Mirroring https://crrev.com/c/8463165 (crbug.com/565742180), guard
RtcEncoded{Audio,Video}{Receiver,Sender}SourceOptimizer::
PerformInProcessOptimization() against a null or destroyed
ExecutionContext, invoking the disconnect callback before returning
nullptr so the encoded stream transformer callback is unregistered.
Also add a defensive guard to
RtcEncoded{Audio,Video}{Receiver,Sender}SinkOptimizer::
PerformInProcessOptimization().

Fixed: 568945639
Change-Id: I618e945c4c4a06c866be936b1a489bd5c109d5c5
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/8577741
Reviewed-by: Henrik Boström <hbos@chromium.org>
Commit-Queue: Guido Urdaneta <guidou@chromium.org>
Cr-Commit-Position: refs/heads/main@{#1720474}
---
diff --git a/third_party/blink/renderer/modules/peerconnection/rtc_encoded_audio_receiver_sink_optimizer.cc b/third_party/blink/renderer/modules/peerconnection/rtc_encoded_audio_receiver_sink_optimizer.cc
index 781ec82..1735c3d9 100644
--- a/third_party/blink/renderer/modules/peerconnection/rtc_encoded_audio_receiver_sink_optimizer.cc
+++ b/third_party/blink/renderer/modules/peerconnection/rtc_encoded_audio_receiver_sink_optimizer.cc
@@ -7,6 +7,7 @@
 #include <utility>
 
 #include "base/memory/scoped_refptr.h"
+#include "third_party/blink/renderer/core/execution_context/execution_context.h"
 #include "third_party/blink/renderer/platform/heap/garbage_collected.h"
 #include "third_party/blink/renderer/platform/peerconnection/rtc_encoded_audio_stream_transformer.h"
 
@@ -21,6 +22,13 @@
 UnderlyingSinkBase*
 RtcEncodedAudioReceiverSinkOptimizer::PerformInProcessOptimization(
     ScriptState* script_state) {
+  ExecutionContext* context = ExecutionContext::From(script_state);
+  if (!context || context->IsContextDestroyed()) {
+    // Do not allocate a sink on a destroyed context or bind a persistent
+    // reference to it on the receiver.
+    return nullptr;
+  }
+
   auto* new_sink = MakeGarbageCollected<RTCEncodedAudioUnderlyingSink>(
       script_state, std::move(transformer_),
       /*detach_frame_data_on_write=*/false);
diff --git a/third_party/blink/renderer/modules/peerconnection/rtc_encoded_audio_receiver_source_optimizer.cc b/third_party/blink/renderer/modules/peerconnection/rtc_encoded_audio_receiver_source_optimizer.cc
index 3724ea58..6825a11 100644
--- a/third_party/blink/renderer/modules/peerconnection/rtc_encoded_audio_receiver_source_optimizer.cc
+++ b/third_party/blink/renderer/modules/peerconnection/rtc_encoded_audio_receiver_source_optimizer.cc
@@ -25,6 +25,13 @@
 RtcEncodedAudioReceiverSourceOptimizer::PerformInProcessOptimization(
     ScriptState* script_state) {
   ExecutionContext* context = ExecutionContext::From(script_state);
+  if (!context || context->IsContextDestroyed()) {
+    // A source created on a destroyed context would never get
+    // ContextDestroyed(), so the disconnect callback would never run and the
+    // transformer would keep pushing frames to a dead heap. Disconnect now.
+    std::move(disconnect_callback_).Run();
+    return nullptr;
+  }
 
   scoped_refptr<base::SingleThreadTaskRunner> current_runner =
       context->GetTaskRunner(TaskType::kInternalMediaRealTime);
diff --git a/third_party/blink/renderer/modules/peerconnection/rtc_encoded_audio_sender_sink_optimizer.cc b/third_party/blink/renderer/modules/peerconnection/rtc_encoded_audio_sender_sink_optimizer.cc
index 55c682c9..72371a2 100644
--- a/third_party/blink/renderer/modules/peerconnection/rtc_encoded_audio_sender_sink_optimizer.cc
+++ b/third_party/blink/renderer/modules/peerconnection/rtc_encoded_audio_sender_sink_optimizer.cc
@@ -7,6 +7,7 @@
 #include <utility>
 
 #include "base/memory/scoped_refptr.h"
+#include "third_party/blink/renderer/core/execution_context/execution_context.h"
 #include "third_party/blink/renderer/platform/heap/cross_thread_persistent.h"
 #include "third_party/blink/renderer/platform/heap/garbage_collected.h"
 #include "third_party/blink/renderer/platform/peerconnection/rtc_encoded_audio_stream_transformer.h"
@@ -22,6 +23,13 @@
 UnderlyingSinkBase*
 RtcEncodedAudioSenderSinkOptimizer::PerformInProcessOptimization(
     ScriptState* script_state) {
+  ExecutionContext* context = ExecutionContext::From(script_state);
+  if (!context || context->IsContextDestroyed()) {
+    // Do not allocate a sink on a destroyed context or bind a persistent
+    // reference to it on the sender.
+    return nullptr;
+  }
+
   auto* new_sink = MakeGarbageCollected<RTCEncodedAudioUnderlyingSink>(
       script_state, std::move(transformer_),
       /*detach_frame_data_on_write=*/false);
diff --git a/third_party/blink/renderer/modules/peerconnection/rtc_encoded_audio_sender_source_optimizer.cc b/third_party/blink/renderer/modules/peerconnection/rtc_encoded_audio_sender_source_optimizer.cc
index 02634f4..409c8f18 100644
--- a/third_party/blink/renderer/modules/peerconnection/rtc_encoded_audio_sender_source_optimizer.cc
+++ b/third_party/blink/renderer/modules/peerconnection/rtc_encoded_audio_sender_source_optimizer.cc
@@ -26,6 +26,13 @@
 RtcEncodedAudioSenderSourceOptimizer::PerformInProcessOptimization(
     ScriptState* script_state) {
   ExecutionContext* context = ExecutionContext::From(script_state);
+  if (!context || context->IsContextDestroyed()) {
+    // A source created on a destroyed context would never get
+    // ContextDestroyed(), so the disconnect callback would never run and the
+    // transformer would keep pushing frames to a dead heap. Disconnect now.
+    std::move(disconnect_callback_).Run();
+    return nullptr;
+  }
 
   scoped_refptr<base::SingleThreadTaskRunner> current_runner =
       context->GetTaskRunner(TaskType::kInternalMediaRealTime);
diff --git a/third_party/blink/renderer/modules/peerconnection/rtc_encoded_video_receiver_sink_optimizer.cc b/third_party/blink/renderer/modules/peerconnection/rtc_encoded_video_receiver_sink_optimizer.cc
index ffdf91b..f97e09b0 100644
--- a/third_party/blink/renderer/modules/peerconnection/rtc_encoded_video_receiver_sink_optimizer.cc
+++ b/third_party/blink/renderer/modules/peerconnection/rtc_encoded_video_receiver_sink_optimizer.cc
@@ -7,6 +7,7 @@
 #include <utility>
 
 #include "base/memory/scoped_refptr.h"
+#include "third_party/blink/renderer/core/execution_context/execution_context.h"
 #include "third_party/blink/renderer/platform/heap/cross_thread_persistent.h"
 #include "third_party/blink/renderer/platform/heap/garbage_collected.h"
 #include "third_party/blink/renderer/platform/peerconnection/rtc_encoded_video_stream_transformer.h"
@@ -22,6 +23,13 @@
 UnderlyingSinkBase*
 RtcEncodedVideoReceiverSinkOptimizer::PerformInProcessOptimization(
     ScriptState* script_state) {
+  ExecutionContext* context = ExecutionContext::From(script_state);
+  if (!context || context->IsContextDestroyed()) {
+    // Do not allocate a sink on a destroyed context or bind a persistent
+    // reference to it on the receiver.
+    return nullptr;
+  }
+
   auto* new_sink = MakeGarbageCollected<RTCEncodedVideoUnderlyingSink>(
       script_state, std::move(transformer_),
       /*detach_frame_data_on_write=*/false);
diff --git a/third_party/blink/renderer/modules/peerconnection/rtc_encoded_video_receiver_source_optimizer.cc b/third_party/blink/renderer/modules/peerconnection/rtc_encoded_video_receiver_source_optimizer.cc
index c455a28..845e51c6 100644
--- a/third_party/blink/renderer/modules/peerconnection/rtc_encoded_video_receiver_source_optimizer.cc
+++ b/third_party/blink/renderer/modules/peerconnection/rtc_encoded_video_receiver_source_optimizer.cc
@@ -26,6 +26,13 @@
 RtcEncodedVideoReceiverSourceOptimizer::PerformInProcessOptimization(
     ScriptState* script_state) {
   ExecutionContext* context = ExecutionContext::From(script_state);
+  if (!context || context->IsContextDestroyed()) {
+    // A source created on a destroyed context would never get
+    // ContextDestroyed(), so the disconnect callback would never run and the
+    // transformer would keep pushing frames to a dead heap. Disconnect now.
+    std::move(disconnect_callback_).Run();
+    return nullptr;
+  }
 
   scoped_refptr<base::SingleThreadTaskRunner> current_runner =
       context->GetTaskRunner(TaskType::kInternalMediaRealTime);
diff --git a/third_party/blink/renderer/modules/peerconnection/rtc_encoded_video_sender_sink_optimizer.cc b/third_party/blink/renderer/modules/peerconnection/rtc_encoded_video_sender_sink_optimizer.cc
index 29f598f7..ac5cc7e1 100644
--- a/third_party/blink/renderer/modules/peerconnection/rtc_encoded_video_sender_sink_optimizer.cc
+++ b/third_party/blink/renderer/modules/peerconnection/rtc_encoded_video_sender_sink_optimizer.cc
@@ -7,6 +7,7 @@
 #include <utility>
 
 #include "base/memory/scoped_refptr.h"
+#include "third_party/blink/renderer/core/execution_context/execution_context.h"
 #include "third_party/blink/renderer/platform/heap/cross_thread_persistent.h"
 #include "third_party/blink/renderer/platform/heap/garbage_collected.h"
 #include "third_party/blink/renderer/platform/peerconnection/rtc_encoded_video_stream_transformer.h"
@@ -22,6 +23,13 @@
 UnderlyingSinkBase*
 RtcEncodedVideoSenderSinkOptimizer::PerformInProcessOptimization(
     ScriptState* script_state) {
+  ExecutionContext* context = ExecutionContext::From(script_state);
+  if (!context || context->IsContextDestroyed()) {
+    // Do not allocate a sink on a destroyed context or bind a persistent
+    // reference to it on the sender.
+    return nullptr;
+  }
+
   auto* new_sink = MakeGarbageCollected<RTCEncodedVideoUnderlyingSink>(
       script_state, std::move(transformer_),
       /*detach_frame_data_on_write=*/false);
diff --git a/third_party/blink/renderer/modules/peerconnection/rtc_encoded_video_sender_source_optimizer.cc b/third_party/blink/renderer/modules/peerconnection/rtc_encoded_video_sender_source_optimizer.cc
index 77f021af..29961fe5 100644
--- a/third_party/blink/renderer/modules/peerconnection/rtc_encoded_video_sender_source_optimizer.cc
+++ b/third_party/blink/renderer/modules/peerconnection/rtc_encoded_video_sender_source_optimizer.cc
@@ -26,6 +26,13 @@
 RtcEncodedVideoSenderSourceOptimizer::PerformInProcessOptimization(
     ScriptState* script_state) {
   ExecutionContext* context = ExecutionContext::From(script_state);
+  if (!context || context->IsContextDestroyed()) {
+    // A source created on a destroyed context would never get
+    // ContextDestroyed(), so the disconnect callback would never run and the
+    // transformer would keep pushing frames to a dead heap. Disconnect now.
+    std::move(disconnect_callback_).Run();
+    return nullptr;
+  }
 
   scoped_refptr<base::SingleThreadTaskRunner> current_runner =
       context->GetTaskRunner(TaskType::kInternalMediaRealTime);
diff --git a/third_party/blink/renderer/modules/peerconnection/rtc_encoded_audio_underlying_sink_test.cc b/third_party/blink/renderer/modules/peerconnection/rtc_encoded_audio_underlying_sink_test.cc
index 3b5e3be..126fdab2 100644
--- a/third_party/blink/renderer/modules/peerconnection/rtc_encoded_audio_underlying_sink_test.cc
+++ b/third_party/blink/renderer/modules/peerconnection/rtc_encoded_audio_underlying_sink_test.cc
@@ -13,6 +13,7 @@
 #include "base/memory/ptr_util.h"
 #include "base/memory/scoped_refptr.h"
 #include "base/task/single_thread_task_runner.h"
+#include "base/test/mock_callback.h"
 #include "testing/gmock/include/gmock/gmock.h"
 #include "testing/gtest/include/gtest/gtest.h"
 #include "third_party/blink/public/platform/scheduler/test/renderer_scheduler_test_support.h"
@@ -22,16 +23,20 @@
 #include "third_party/blink/renderer/bindings/core/v8/v8_dom_exception.h"
 #include "third_party/blink/renderer/bindings/modules/v8/v8_rtc_encoded_audio_frame.h"
 #include "third_party/blink/renderer/core/dom/dom_exception.h"
+#include "third_party/blink/renderer/core/execution_context/execution_context.h"
 #include "third_party/blink/renderer/core/streams/writable_stream.h"
 #include "third_party/blink/renderer/core/streams/writable_stream_default_writer.h"
 #include "third_party/blink/renderer/modules/peerconnection/rtc_encoded_audio_frame.h"
 #include "third_party/blink/renderer/modules/peerconnection/rtc_encoded_audio_frame_delegate.h"
+#include "third_party/blink/renderer/modules/peerconnection/rtc_encoded_audio_receiver_sink_optimizer.h"
+#include "third_party/blink/renderer/modules/peerconnection/rtc_encoded_audio_sender_sink_optimizer.h"
 #include "third_party/blink/renderer/platform/bindings/exception_code.h"
 #include "third_party/blink/renderer/platform/bindings/exception_state.h"
 #include "third_party/blink/renderer/platform/heap/garbage_collected.h"
 #include "third_party/blink/renderer/platform/peerconnection/rtc_encoded_audio_stream_transformer.h"
 #include "third_party/blink/renderer/platform/testing/task_environment.h"
 #include "third_party/blink/renderer/platform/testing/testing_platform_support.h"
+#include "third_party/blink/renderer/platform/wtf/cross_thread_functional.h"
 #include "third_party/blink/renderer/platform/wtf/functional.h"
 #include "third_party/blink/renderer/platform/wtf/vector.h"
 #include "third_party/webrtc/api/frame_transformer_interface.h"
@@ -191,4 +196,34 @@
   EXPECT_FALSE(dummy_exception_state.HadException());
 }
 
+TEST_F(RTCEncodedAudioUnderlyingSinkTest,
+       ReceiverOptimizerAfterContextDestroyedReturnsNull) {
+  V8TestingScope v8_scope;
+  base::MockOnceCallback<void(RTCEncodedAudioUnderlyingSink*)>
+      set_underlying_sink;
+  RtcEncodedAudioReceiverSinkOptimizer optimizer(
+      CrossThreadBindOnce(set_underlying_sink.Get()), transformer_.GetBroker());
+
+  v8_scope.GetExecutionContext()->NotifyContextDestroyed();
+
+  EXPECT_CALL(set_underlying_sink, Run(_)).Times(0);
+  EXPECT_EQ(optimizer.PerformInProcessOptimization(v8_scope.GetScriptState()),
+            nullptr);
+}
+
+TEST_F(RTCEncodedAudioUnderlyingSinkTest,
+       SenderOptimizerAfterContextDestroyedReturnsNull) {
+  V8TestingScope v8_scope;
+  base::MockOnceCallback<void(RTCEncodedAudioUnderlyingSink*)>
+      set_underlying_sink;
+  RtcEncodedAudioSenderSinkOptimizer optimizer(
+      CrossThreadBindOnce(set_underlying_sink.Get()), transformer_.GetBroker());
+
+  v8_scope.GetExecutionContext()->NotifyContextDestroyed();
+
+  EXPECT_CALL(set_underlying_sink, Run(_)).Times(0);
+  EXPECT_EQ(optimizer.PerformInProcessOptimization(v8_scope.GetScriptState()),
+            nullptr);
+}
+
 }  // namespace blink
diff --git a/third_party/blink/renderer/modules/peerconnection/rtc_encoded_audio_underlying_source_test.cc b/third_party/blink/renderer/modules/peerconnection/rtc_encoded_audio_underlying_source_test.cc
index 8594917..14e3cc4 100644
--- a/third_party/blink/renderer/modules/peerconnection/rtc_encoded_audio_underlying_source_test.cc
+++ b/third_party/blink/renderer/modules/peerconnection/rtc_encoded_audio_underlying_source_test.cc
@@ -15,9 +15,12 @@
 #include "third_party/blink/renderer/bindings/core/v8/v8_binding_for_testing.h"
 #include "third_party/blink/renderer/bindings/core/v8/v8_readable_stream_read_result.h"
 #include "third_party/blink/renderer/bindings/modules/v8/v8_rtc_encoded_audio_frame.h"
+#include "third_party/blink/renderer/core/execution_context/execution_context.h"
 #include "third_party/blink/renderer/core/streams/readable_stream.h"
 #include "third_party/blink/renderer/core/streams/readable_stream_default_controller_with_script_scope.h"
 #include "third_party/blink/renderer/modules/peerconnection/rtc_encoded_audio_frame.h"
+#include "third_party/blink/renderer/modules/peerconnection/rtc_encoded_audio_receiver_source_optimizer.h"
+#include "third_party/blink/renderer/modules/peerconnection/rtc_encoded_audio_sender_source_optimizer.h"
 #include "third_party/blink/renderer/platform/bindings/exception_state.h"
 #include "third_party/blink/renderer/platform/heap/garbage_collected.h"
 #include "third_party/blink/renderer/platform/heap/persistent.h"
@@ -194,4 +197,40 @@
   task_environment_.RunUntilIdle();
 }
 
+TEST_F(RTCEncodedAudioUnderlyingSourceTest,
+       ReceiverOptimizerAfterContextDestroyedDisconnectsAndReturnsNull) {
+  V8TestingScope v8_scope;
+  base::MockRepeatingCallback<void(RTCEncodedAudioUnderlyingSource*,
+                                   scoped_refptr<base::SingleThreadTaskRunner>)>
+      set_underlying_source;
+  RtcEncodedAudioReceiverSourceOptimizer optimizer(
+      CrossThreadBindRepeating(set_underlying_source.Get()),
+      CrossThreadBindOnce(disconnect_callback_.Get()));
+
+  v8_scope.GetExecutionContext()->NotifyContextDestroyed();
+
+  EXPECT_CALL(set_underlying_source, Run(testing::_, testing::_)).Times(0);
+  EXPECT_CALL(disconnect_callback_, Run()).Times(1);
+  EXPECT_EQ(optimizer.PerformInProcessOptimization(v8_scope.GetScriptState()),
+            nullptr);
+}
+
+TEST_F(RTCEncodedAudioUnderlyingSourceTest,
+       SenderOptimizerAfterContextDestroyedDisconnectsAndReturnsNull) {
+  V8TestingScope v8_scope;
+  base::MockRepeatingCallback<void(RTCEncodedAudioUnderlyingSource*,
+                                   scoped_refptr<base::SingleThreadTaskRunner>)>
+      set_underlying_source;
+  RtcEncodedAudioSenderSourceOptimizer optimizer(
+      CrossThreadBindRepeating(set_underlying_source.Get()),
+      CrossThreadBindOnce(disconnect_callback_.Get()));
+
+  v8_scope.GetExecutionContext()->NotifyContextDestroyed();
+
+  EXPECT_CALL(set_underlying_source, Run(testing::_, testing::_)).Times(0);
+  EXPECT_CALL(disconnect_callback_, Run()).Times(1);
+  EXPECT_EQ(optimizer.PerformInProcessOptimization(v8_scope.GetScriptState()),
+            nullptr);
+}
+
 }  // namespace blink
diff --git a/third_party/blink/renderer/modules/peerconnection/rtc_encoded_video_underlying_sink_test.cc b/third_party/blink/renderer/modules/peerconnection/rtc_encoded_video_underlying_sink_test.cc
index 834c4ad..e370d769 100644
--- a/third_party/blink/renderer/modules/peerconnection/rtc_encoded_video_underlying_sink_test.cc
+++ b/third_party/blink/renderer/modules/peerconnection/rtc_encoded_video_underlying_sink_test.cc
@@ -10,6 +10,7 @@
 
 #include "base/memory/scoped_refptr.h"
 #include "base/task/single_thread_task_runner.h"
+#include "base/test/mock_callback.h"
 #include "testing/gmock/include/gmock/gmock.h"
 #include "testing/gtest/include/gtest/gtest.h"
 #include "third_party/blink/public/platform/scheduler/test/renderer_scheduler_test_support.h"
@@ -19,16 +20,20 @@
 #include "third_party/blink/renderer/bindings/core/v8/v8_dom_exception.h"
 #include "third_party/blink/renderer/bindings/modules/v8/v8_rtc_encoded_video_frame.h"
 #include "third_party/blink/renderer/core/dom/dom_exception.h"
+#include "third_party/blink/renderer/core/execution_context/execution_context.h"
 #include "third_party/blink/renderer/core/streams/writable_stream.h"
 #include "third_party/blink/renderer/core/streams/writable_stream_default_writer.h"
 #include "third_party/blink/renderer/modules/peerconnection/rtc_encoded_video_frame.h"
 #include "third_party/blink/renderer/modules/peerconnection/rtc_encoded_video_frame_delegate.h"
+#include "third_party/blink/renderer/modules/peerconnection/rtc_encoded_video_receiver_sink_optimizer.h"
+#include "third_party/blink/renderer/modules/peerconnection/rtc_encoded_video_sender_sink_optimizer.h"
 #include "third_party/blink/renderer/platform/bindings/exception_code.h"
 #include "third_party/blink/renderer/platform/bindings/exception_state.h"
 #include "third_party/blink/renderer/platform/heap/garbage_collected.h"
 #include "third_party/blink/renderer/platform/peerconnection/rtc_encoded_video_stream_transformer.h"
 #include "third_party/blink/renderer/platform/testing/task_environment.h"
 #include "third_party/blink/renderer/platform/testing/testing_platform_support.h"
+#include "third_party/blink/renderer/platform/wtf/cross_thread_functional.h"
 #include "third_party/blink/renderer/platform/wtf/functional.h"
 #include "third_party/webrtc/api/frame_transformer_interface.h"
 #include "third_party/webrtc/api/scoped_refptr.h"
@@ -187,4 +192,34 @@
   EXPECT_FALSE(dummy_exception_state.HadException());
 }
 
+TEST_F(RTCEncodedVideoUnderlyingSinkTest,
+       ReceiverOptimizerAfterContextDestroyedReturnsNull) {
+  V8TestingScope v8_scope;
+  base::MockOnceCallback<void(RTCEncodedVideoUnderlyingSink*)>
+      set_underlying_sink;
+  RtcEncodedVideoReceiverSinkOptimizer optimizer(
+      CrossThreadBindOnce(set_underlying_sink.Get()), transformer_.GetBroker());
+
+  v8_scope.GetExecutionContext()->NotifyContextDestroyed();
+
+  EXPECT_CALL(set_underlying_sink, Run(_)).Times(0);
+  EXPECT_EQ(optimizer.PerformInProcessOptimization(v8_scope.GetScriptState()),
+            nullptr);
+}
+
+TEST_F(RTCEncodedVideoUnderlyingSinkTest,
+       SenderOptimizerAfterContextDestroyedReturnsNull) {
+  V8TestingScope v8_scope;
+  base::MockOnceCallback<void(RTCEncodedVideoUnderlyingSink*)>
+      set_underlying_sink;
+  RtcEncodedVideoSenderSinkOptimizer optimizer(
+      CrossThreadBindOnce(set_underlying_sink.Get()), transformer_.GetBroker());
+
+  v8_scope.GetExecutionContext()->NotifyContextDestroyed();
+
+  EXPECT_CALL(set_underlying_sink, Run(_)).Times(0);
+  EXPECT_EQ(optimizer.PerformInProcessOptimization(v8_scope.GetScriptState()),
+            nullptr);
+}
+
... (file diff truncated)
Loading diff…

Regression Test / PoC

shipped with the fix
diff --git a/third_party/blink/renderer/modules/peerconnection/rtc_encoded_audio_underlying_sink_test.cc b/third_party/blink/renderer/modules/peerconnection/rtc_encoded_audio_underlying_sink_test.cc
index 3b5e3be..126fdab2 100644
--- a/third_party/blink/renderer/modules/peerconnection/rtc_encoded_audio_underlying_sink_test.cc
+++ b/third_party/blink/renderer/modules/peerconnection/rtc_encoded_audio_underlying_sink_test.cc
@@ -13,6 +13,7 @@
 #include "base/memory/ptr_util.h"
 #include "base/memory/scoped_refptr.h"
 #include "base/task/single_thread_task_runner.h"
+#include "base/test/mock_callback.h"
 #include "testing/gmock/include/gmock/gmock.h"
 #include "testing/gtest/include/gtest/gtest.h"
 #include "third_party/blink/public/platform/scheduler/test/renderer_scheduler_test_support.h"
@@ -22,16 +23,20 @@
 #include "third_party/blink/renderer/bindings/core/v8/v8_dom_exception.h"
 #include "third_party/blink/renderer/bindings/modules/v8/v8_rtc_encoded_audio_frame.h"
 #include "third_party/blink/renderer/core/dom/dom_exception.h"
+#include "third_party/blink/renderer/core/execution_context/execution_context.h"
 #include "third_party/blink/renderer/core/streams/writable_stream.h"
 #include "third_party/blink/renderer/core/streams/writable_stream_default_writer.h"
 #include "third_party/blink/renderer/modules/peerconnection/rtc_encoded_audio_frame.h"
 #include "third_party/blink/renderer/modules/peerconnection/rtc_encoded_audio_frame_delegate.h"
+#include "third_party/blink/renderer/modules/peerconnection/rtc_encoded_audio_receiver_sink_optimizer.h"
+#include "third_party/blink/renderer/modules/peerconnection/rtc_encoded_audio_sender_sink_optimizer.h"
 #include "third_party/blink/renderer/platform/bindings/exception_code.h"
 #include "third_party/blink/renderer/platform/bindings/exception_state.h"
 #include "third_party/blink/renderer/platform/heap/garbage_collected.h"
 #include "third_party/blink/renderer/platform/peerconnection/rtc_encoded_audio_stream_transformer.h"
 #include "third_party/blink/renderer/platform/testing/task_environment.h"
 #include "third_party/blink/renderer/platform/testing/testing_platform_support.h"
+#include "third_party/blink/renderer/platform/wtf/cross_thread_functional.h"
 #include "third_party/blink/renderer/platform/wtf/functional.h"
 #include "third_party/blink/renderer/platform/wtf/vector.h"
 #include "third_party/webrtc/api/frame_transformer_interface.h"
@@ -191,4 +196,34 @@
   EXPECT_FALSE(dummy_exception_state.HadException());
 }
 
+TEST_F(RTCEncodedAudioUnderlyingSinkTest,
+       ReceiverOptimizerAfterContextDestroyedReturnsNull) {
+  V8TestingScope v8_scope;
+  base::MockOnceCallback<void(RTCEncodedAudioUnderlyingSink*)>
+      set_underlying_sink;
+  RtcEncodedAudioReceiverSinkOptimizer optimizer(
+      CrossThreadBindOnce(set_underlying_sink.Get()), transformer_.GetBroker());
+
+  v8_scope.GetExecutionContext()->NotifyContextDestroyed();
+
+  EXPECT_CALL(set_underlying_sink, Run(_)).Times(0);
+  EXPECT_EQ(optimizer.PerformInProcessOptimization(v8_scope.GetScriptState()),
+            nullptr);
+}
+
+TEST_F(RTCEncodedAudioUnderlyingSinkTest,
+       SenderOptimizerAfterContextDestroyedReturnsNull) {
+  V8TestingScope v8_scope;
+  base::MockOnceCallback<void(RTCEncodedAudioUnderlyingSink*)>
+      set_underlying_sink;
+  RtcEncodedAudioSenderSinkOptimizer optimizer(
+      CrossThreadBindOnce(set_underlying_sink.Get()), transformer_.GetBroker());
+
+  v8_scope.GetExecutionContext()->NotifyContextDestroyed();
+
+  EXPECT_CALL(set_underlying_sink, Run(_)).Times(0);
+  EXPECT_EQ(optimizer.PerformInProcessOptimization(v8_scope.GetScriptState()),
+            nullptr);
+}
+
 }  // namespace blink
diff --git a/third_party/blink/renderer/modules/peerconnection/rtc_encoded_audio_underlying_source_test.cc b/third_party/blink/renderer/modules/peerconnection/rtc_encoded_audio_underlying_source_test.cc
index 8594917..14e3cc4 100644
--- a/third_party/blink/renderer/modules/peerconnection/rtc_encoded_audio_underlying_source_test.cc
+++ b/third_party/blink/renderer/modules/peerconnection/rtc_encoded_audio_underlying_source_test.cc
@@ -15,9 +15,12 @@
 #include "third_party/blink/renderer/bindings/core/v8/v8_binding_for_testing.h"
 #include "third_party/blink/renderer/bindings/core/v8/v8_readable_stream_read_result.h"
 #include "third_party/blink/renderer/bindings/modules/v8/v8_rtc_encoded_audio_frame.h"
+#include "third_party/blink/renderer/core/execution_context/execution_context.h"
 #include "third_party/blink/renderer/core/streams/readable_stream.h"
 #include "third_party/blink/renderer/core/streams/readable_stream_default_controller_with_script_scope.h"
 #include "third_party/blink/renderer/modules/peerconnection/rtc_encoded_audio_frame.h"
+#include "third_party/blink/renderer/modules/peerconnection/rtc_encoded_audio_receiver_source_optimizer.h"
+#include "third_party/blink/renderer/modules/peerconnection/rtc_encoded_audio_sender_source_optimizer.h"
 #include "third_party/blink/renderer/platform/bindings/exception_state.h"
 #include "third_party/blink/renderer/platform/heap/garbage_collected.h"
 #include "third_party/blink/renderer/platform/heap/persistent.h"
@@ -194,4 +197,40 @@
   task_environment_.RunUntilIdle();
 }
 
+TEST_F(RTCEncodedAudioUnderlyingSourceTest,
+       ReceiverOptimizerAfterContextDestroyedDisconnectsAndReturnsNull) {
+  V8TestingScope v8_scope;
+  base::MockRepeatingCallback<void(RTCEncodedAudioUnderlyingSource*,
+                                   scoped_refptr<base::SingleThreadTaskRunner>)>
+      set_underlying_source;
+  RtcEncodedAudioReceiverSourceOptimizer optimizer(
+      CrossThreadBindRepeating(set_underlying_source.Get()),
+      CrossThreadBindOnce(disconnect_callback_.Get()));
+
+  v8_scope.GetExecutionContext()->NotifyContextDestroyed();
+
+  EXPECT_CALL(set_underlying_source, Run(testing::_, testing::_)).Times(0);
+  EXPECT_CALL(disconnect_callback_, Run()).Times(1);
+  EXPECT_EQ(optimizer.PerformInProcessOptimization(v8_scope.GetScriptState()),
+            nullptr);
+}
+
+TEST_F(RTCEncodedAudioUnderlyingSourceTest,
+       SenderOptimizerAfterContextDestroyedDisconnectsAndReturnsNull) {
+  V8TestingScope v8_scope;
+  base::MockRepeatingCallback<void(RTCEncodedAudioUnderlyingSource*,
+                                   scoped_refptr<base::SingleThreadTaskRunner>)>
+      set_underlying_source;
+  RtcEncodedAudioSenderSourceOptimizer optimizer(
+      CrossThreadBindRepeating(set_underlying_source.Get()),
+      CrossThreadBindOnce(disconnect_callback_.Get()));
+
+  v8_scope.GetExecutionContext()->NotifyContextDestroyed();
+
+  EXPECT_CALL(set_underlying_source, Run(testing::_, testing::_)).Times(0);
+  EXPECT_CALL(disconnect_callback_, Run()).Times(1);
+  EXPECT_EQ(optimizer.PerformInProcessOptimization(v8_scope.GetScriptState()),
+            nullptr);
+}
+
 }  // namespace blink
diff --git a/third_party/blink/renderer/modules/peerconnection/rtc_encoded_video_underlying_sink_test.cc b/third_party/blink/renderer/modules/peerconnection/rtc_encoded_video_underlying_sink_test.cc
index 834c4ad..e370d769 100644
--- a/third_party/blink/renderer/modules/peerconnection/rtc_encoded_video_underlying_sink_test.cc
+++ b/third_party/blink/renderer/modules/peerconnection/rtc_encoded_video_underlying_sink_test.cc
@@ -10,6 +10,7 @@
 
 #include "base/memory/scoped_refptr.h"
 #include "base/task/single_thread_task_runner.h"
+#include "base/test/mock_callback.h"
 #include "testing/gmock/include/gmock/gmock.h"
 #include "testing/gtest/include/gtest/gtest.h"
 #include "third_party/blink/public/platform/scheduler/test/renderer_scheduler_test_support.h"
@@ -19,16 +20,20 @@
 #include "third_party/blink/renderer/bindings/core/v8/v8_dom_exception.h"
 #include "third_party/blink/renderer/bindings/modules/v8/v8_rtc_encoded_video_frame.h"
 #include "third_party/blink/renderer/core/dom/dom_exception.h"
+#include "third_party/blink/renderer/core/execution_context/execution_context.h"
 #include "third_party/blink/renderer/core/streams/writable_stream.h"
 #include "third_party/blink/renderer/core/streams/writable_stream_default_writer.h"
 #include "third_party/blink/renderer/modules/peerconnection/rtc_encoded_video_frame.h"
 #include "third_party/blink/renderer/modules/peerconnection/rtc_encoded_video_frame_delegate.h"
+#include "third_party/blink/renderer/modules/peerconnection/rtc_encoded_video_receiver_sink_optimizer.h"
+#include "third_party/blink/renderer/modules/peerconnection/rtc_encoded_video_sender_sink_optimizer.h"
 #include "third_party/blink/renderer/platform/bindings/exception_code.h"
 #include "third_party/blink/renderer/platform/bindings/exception_state.h"
 #include "third_party/blink/renderer/platform/heap/garbage_collected.h"
 #include "third_party/blink/renderer/platform/peerconnection/rtc_encoded_video_stream_transformer.h"
 #include "third_party/blink/renderer/platform/testing/task_environment.h"
 #include "third_party/blink/renderer/platform/testing/testing_platform_support.h"
+#include "third_party/blink/renderer/platform/wtf/cross_thread_functional.h"
 #include "third_party/blink/renderer/platform/wtf/functional.h"
 #include "third_party/webrtc/api/frame_transformer_interface.h"
 #include "third_party/webrtc/api/scoped_refptr.h"
@@ -187,4 +192,34 @@
   EXPECT_FALSE(dummy_exception_state.HadException());
 }
 
+TEST_F(RTCEncodedVideoUnderlyingSinkTest,
+       ReceiverOptimizerAfterContextDestroyedReturnsNull) {
+  V8TestingScope v8_scope;
+  base::MockOnceCallback<void(RTCEncodedVideoUnderlyingSink*)>
+      set_underlying_sink;
+  RtcEncodedVideoReceiverSinkOptimizer optimizer(
+      CrossThreadBindOnce(set_underlying_sink.Get()), transformer_.GetBroker());
+
+  v8_scope.GetExecutionContext()->NotifyContextDestroyed();
+
+  EXPECT_CALL(set_underlying_sink, Run(_)).Times(0);
+  EXPECT_EQ(optimizer.PerformInProcessOptimization(v8_scope.GetScriptState()),
+            nullptr);
+}
+
+TEST_F(RTCEncodedVideoUnderlyingSinkTest,
+       SenderOptimizerAfterContextDestroyedReturnsNull) {
+  V8TestingScope v8_scope;
+  base::MockOnceCallback<void(RTCEncodedVideoUnderlyingSink*)>
+      set_underlying_sink;
+  RtcEncodedVideoSenderSinkOptimizer optimizer(
+      CrossThreadBindOnce(set_underlying_sink.Get()), transformer_.GetBroker());
+
+  v8_scope.GetExecutionContext()->NotifyContextDestroyed();
+
+  EXPECT_CALL(set_underlying_sink, Run(_)).Times(0);
+  EXPECT_EQ(optimizer.PerformInProcessOptimization(v8_scope.GetScriptState()),
+            nullptr);
+}
+
 }  // namespace blink
diff --git a/third_party/blink/renderer/modules/peerconnection/rtc_encoded_video_underlying_source_test.cc b/third_party/blink/renderer/modules/peerconnection/rtc_encoded_video_underlying_source_test.cc
index 39083334..78c1d76 100644
--- a/third_party/blink/renderer/modules/peerconnection/rtc_encoded_video_underlying_source_test.cc
+++ b/third_party/blink/renderer/modules/peerconnection/rtc_encoded_video_underlying_source_test.cc
@@ -14,8 +14,11 @@
 #include "third_party/blink/renderer/bindings/core/v8/script_promise_tester.h"
 #include "third_party/blink/renderer/bindings/core/v8/v8_binding_for_testing.h"
 #include "third_party/blink/renderer/bindings/core/v8/v8_readable_stream_read_result.h"
+#include "third_party/blink/renderer/core/execution_context/execution_context.h"
 #include "third_party/blink/renderer/core/streams/readable_stream.h"
 #include "third_party/blink/renderer/core/streams/readable_stream_default_controller_with_script_scope.h"
+#include "third_party/blink/renderer/modules/peerconnection/rtc_encoded_video_receiver_source_optimizer.h"
+#include "third_party/blink/renderer/modules/peerconnection/rtc_encoded_video_sender_source_optimizer.h"
 #include "third_party/blink/renderer/platform/bindings/exception_state.h"
 #include "third_party/blink/renderer/platform/heap/garbage_collected.h"
 #include "third_party/blink/renderer/platform/heap/persistent.h"
@@ -184,4 +187,40 @@
   task_environment_.RunUntilIdle();
 }
 
+TEST_F(RTCEncodedVideoUnderlyingSourceTest,
+       ReceiverOptimizerAfterContextDestroyedDisconnectsAndReturnsNull) {
+  V8TestingScope v8_scope;
+  base::MockRepeatingCallback<void(RTCEncodedVideoUnderlyingSource*,
+                                   scoped_refptr<base::SingleThreadTaskRunner>)>
+      set_underlying_source;
+  RtcEncodedVideoReceiverSourceOptimizer optimizer(
+      CrossThreadBindRepeating(set_underlying_source.Get()),
+      CrossThreadBindOnce(disconnect_callback_.Get()));
+
+  v8_scope.GetExecutionContext()->NotifyContextDestroyed();
+
+  EXPECT_CALL(set_underlying_source, Run(testing::_, testing::_)).Times(0);
+  EXPECT_CALL(disconnect_callback_, Run()).Times(1);
+  EXPECT_EQ(optimizer.PerformInProcessOptimization(v8_scope.GetScriptState()),
+            nullptr);
+}
+
+TEST_F(RTCEncodedVideoUnderlyingSourceTest,
+       SenderOptimizerAfterContextDestroyedDisconnectsAndReturnsNull) {
+  V8TestingScope v8_scope;
+  base::MockRepeatingCallback<void(RTCEncodedVideoUnderlyingSource*,
+                                   scoped_refptr<base::SingleThreadTaskRunner>)>
+      set_underlying_source;
+  RtcEncodedVideoSenderSourceOptimizer optimizer(
+      CrossThreadBindRepeating(set_underlying_source.Get()),
+      CrossThreadBindOnce(disconnect_callback_.Get()));
+
+  v8_scope.GetExecutionContext()->NotifyContextDestroyed();
+
+  EXPECT_CALL(set_underlying_source, Run(testing::_, testing::_)).Times(0);
+  EXPECT_CALL(disconnect_callback_, Run()).Times(1);
+  EXPECT_EQ(optimizer.PerformInProcessOptimization(v8_scope.GetScriptState()),
+            nullptr);
+}
+
 }  // namespace blink
Loading diff…

Original Bug Report

The reporter's bug is still restricted on the tracker. Chrome de-restricts security bugs ~30–90 days after the fix ships; a later run will backfill it here.