Moderate firefox Logic Error 🔧 Commit mapped

Overview

Moderate
Severity
—
CVSS
No
Exploited ITW
Fixed
Fix Status
Impactmoderate
DescriptionIncorrect boundary conditions in the WebRTC component
ComponentCore
Bug ClassLogic Error
Tracker2081535
Fix commit0dbb25c1a880 (firefox) +73/-43
CISA KEVNot listed
CreditedMozilla
Disclosed2026-10-27

Changed Functions

FunctionChangeNotes
for
third_party/libwebrtc/video/video_receive_stream2.cc
modified
if
third_party/libwebrtc/video/video_receive_stream2.cc
modified
TEST_P
third_party/libwebrtc/video/video_receive_stream2_unittest.cc
modified

Files Changed

  • third_party/libwebrtc/README.mozilla.last-vendor
  • third_party/libwebrtc/moz-patch-stack/s0037.patch
  • third_party/libwebrtc/moz-patch-stack/s0041.patch
  • third_party/libwebrtc/moz-patch-stack/s0056.patch
  • third_party/libwebrtc/video/video_receive_stream2.cc
  • third_party/libwebrtc/video/video_receive_stream2.h
  • third_party/libwebrtc/video/video_receive_stream2_unittest.cc
diff --git a/third_party/libwebrtc/README.mozilla.last-vendor b/third_party/libwebrtc/README.mozilla.last-vendor
index e6be0752bc7..981fdecbb84 100644
--- a/third_party/libwebrtc/README.mozilla.last-vendor
+++ b/third_party/libwebrtc/README.mozilla.last-vendor
@@ -1,4 +1,4 @@
 # ./mach python dom/media/webrtc/third_party_build/loop-ff.py
-libwebrtc updated from /home/apehrson/firefox/firefox/.moz-fast-forward/moz-libwebrtc commit mozpatches on 2026-10-20T11:17:26.444830+00:00.
+libwebrtc updated from /home/apehrson/firefox/firefox/.moz-fast-forward/moz-libwebrtc commit mozpatches on 2026-10-20T11:28:14.527015+00:00.
 # base of lastest vendoring
-ce13eaa8e5
+2a990ce0cc
diff --git a/third_party/libwebrtc/moz-patch-stack/s0037.patch b/third_party/libwebrtc/moz-patch-stack/s0037.patch
index 8f219e2c2a3..6c34e547ac0 100644
--- a/third_party/libwebrtc/moz-patch-stack/s0037.patch
+++ b/third_party/libwebrtc/moz-patch-stack/s0037.patch
@@ -53,10 +53,10 @@ index 7b133968cf..b7fa03a961 100644
    // Implements RtpVideoFrameReceiver.
    void ManageFrame(std::unique_ptr<RtpFrameObject> frame) override;
 diff --git a/video/video_receive_stream2.cc b/video/video_receive_stream2.cc
-index e30453cca8..b2d0e40948 100644
+index 1d4f86e090..20eb1ca073 100644
 --- a/video/video_receive_stream2.cc
 +++ b/video/video_receive_stream2.cc
-@@ -729,6 +729,14 @@ VideoReceiveStreamInterface::Stats VideoReceiveStream2::GetStats() const {
+@@ -747,6 +747,14 @@ VideoReceiveStreamInterface::Stats VideoReceiveStream2::GetStats() const {
          non_sender_rtt_stats->round_trip_time_measurements;
      stats.total_round_trip_time = non_sender_rtt_stats->total_round_trip_time;
    }
diff --git a/third_party/libwebrtc/moz-patch-stack/s0041.patch b/third_party/libwebrtc/moz-patch-stack/s0041.patch
index bed9fbdc66f..595cb00a3e7 100644
--- a/third_party/libwebrtc/moz-patch-stack/s0041.patch
+++ b/third_party/libwebrtc/moz-patch-stack/s0041.patch
@@ -153,10 +153,10 @@ index b7fa03a961..f18cdaa55d 100644
   private:
    // Implements RtpVideoFrameReceiver.
 diff --git a/video/video_receive_stream2.cc b/video/video_receive_stream2.cc
-index b2d0e40948..4c998da9f3 100644
+index 20eb1ca073..05fb700936 100644
 --- a/video/video_receive_stream2.cc
 +++ b/video/video_receive_stream2.cc
-@@ -735,7 +735,8 @@ VideoReceiveStreamInterface::Stats VideoReceiveStream2::GetStats() const {
+@@ -753,7 +753,8 @@ VideoReceiveStreamInterface::Stats VideoReceiveStream2::GetStats() const {
    // seem to be any support for these stats right now. So, we hack this in.
    rtp_video_stream_receiver_.RemoteRTCPSenderInfo(
        &stats.rtcp_sender_packets_sent, &stats.rtcp_sender_octets_sent,
diff --git a/third_party/libwebrtc/moz-patch-stack/s0056.patch b/third_party/libwebrtc/moz-patch-stack/s0056.patch
index fd7280b1a68..455b10f08a3 100644
--- a/third_party/libwebrtc/moz-patch-stack/s0056.patch
+++ b/third_party/libwebrtc/moz-patch-stack/s0056.patch
@@ -272,7 +272,7 @@ index f18cdaa55d..68889ae661 100644
    // h26x_packet_buffer_ is applicable to H.264 and H.265. For H.265 it is
    // always used but for H.264 it is only used if WebRTC-Video-H26xPacketBuffer
 diff --git a/video/video_receive_stream2.cc b/video/video_receive_stream2.cc
-index 4c998da9f3..44a22df92f 100644
+index 05fb700936..280b8cbda9 100644
 --- a/video/video_receive_stream2.cc
 +++ b/video/video_receive_stream2.cc
 @@ -272,6 +272,7 @@ VideoReceiveStream2::VideoReceiveStream2(
diff --git a/third_party/libwebrtc/video/video_receive_stream2.cc b/third_party/libwebrtc/video/video_receive_stream2.cc
index 44a22df92f2..280b8cbda9c 100644
--- a/third_party/libwebrtc/video/video_receive_stream2.cc
+++ b/third_party/libwebrtc/video/video_receive_stream2.cc
@@ -575,23 +575,32 @@ VideoReceiveStream2::GetReceiveCodecConfig() const {
   return receive_codecs;
 }
 
-void VideoReceiveStream2::RegisterCodecsOnReceiver(
+bool VideoReceiveStream2::RegisterCodecsOnReceiver(
     const std::vector<Decoder>& old_decoders,
     const std::vector<Decoder>& new_decoders) {
-  // Deregister all external decoders from the previous configuration, also
-  // the unchanged ones. DeregisterReceiveCodecs() below causes the active
-  // decoder to be released before the next frame is decoded, and a released
-  // decoder must not be configured again since not all implementations
-  // support that (e.g. hardware decoders may fall back to software). New
-  // decoder instances get created on demand instead.
+  // Deregister decoders from the previous configuration that are removed or
+  // updated in the new configuration. Unchanged decoders are left as is, so
+  // that a decoder that is in use keeps running. Releasing and configuring it
+  // again would require a keyframe and isn't supported by all implementations
+  // (e.g. hardware decoders may fall back to software).
+  bool removed_decoder_instance = false;
   for (const Decoder& old_decoder : old_decoders) {
-    video_receiver_.RegisterExternalDecoder(nullptr, old_decoder.payload_type);
+    if (absl::c_linear_search(new_decoders, old_decoder)) {
+      continue;
+    }
+    if (video_receiver_.IsExternalDecoderRegistered(old_decoder.payload_type)) {
+      video_receiver_.RegisterExternalDecoder(nullptr,
+                                              old_decoder.payload_type);
+      removed_decoder_instance = true;
+    }
+    video_receiver_.DeregisterReceiveCodec(old_decoder.payload_type);
   }
 
-  video_receiver_.DeregisterReceiveCodecs();
-
-  // Register settings for the new decoder configuration.
+  // Register settings for added or updated decoders.
   for (const Decoder& decoder : new_decoders) {
+    if (absl::c_linear_search(old_decoders, decoder)) {
+      continue;
+    }
     VideoDecoder::Settings settings;
     settings.set_codec_type(
         PayloadStringToCodecType(decoder.video_format.name));
@@ -601,6 +610,8 @@ void VideoReceiveStream2::RegisterCodecsOnReceiver(
 
     video_receiver_.RegisterReceiveCodec(decoder.payload_type, settings);
   }
+
+  return removed_decoder_instance;
 }
 
 void VideoReceiveStream2::ConfigureCodecs() {
@@ -630,19 +641,26 @@ void VideoReceiveStream2::SetDecoders(std::vector<Decoder> decoders) {
   // if Stop() is called, it synchronously flushes decode_queue_ before setting
   // decoder_running_ to false, avoiding a clash with ConfigureCodecs().
   decode_queue_->PostTask([this, old_decoders = std::move(old_decoders),
-                           new_decoders = config_.decoders]() mutable {
+                           new_decoders = config_.decoders,
+                           task_safety_flag = task_safety_.flag()]() mutable {
     RTC_DCHECK_RUN_ON(&decode_sequence_checker_);
-    RegisterCodecsOnReceiver(old_decoders, new_decoders);
+    const bool removed_decoder_instance =
+        RegisterCodecsOnReceiver(old_decoders, new_decoders);
     active_decoders_ = std::move(new_decoders);
+    if (!removed_decoder_instance) {
+      return;
+    }
+    // The decoder that was in use has been removed or replaced. Request a
+    // keyframe right away since delta frames currently in the jitter buffer or
+    // arriving before the keyframe will fail to decode due to missing state.
+    call_->worker_thread()->PostTask(
+        SafeTask(std::move(task_safety_flag), [this] {
+          RTC_DCHECK_RUN_ON(&worker_sequence_checker_);
+          RequestKeyFrame(env_.clock().CurrentTime());
+        }));
   });
 
   rtp_video_stream_receiver_.SetReceiveCodecs(GetReceiveCodecConfig());
-
-  // Request a keyframe immediately because changing the decoder configuration
-  // resets the decoder database and underlying decoders. Delta frames currently
-  // in the jitter buffer or arriving before the keyframe will fail to decode
-  // due to missing state.
-  RequestKeyFrame(env_.clock().CurrentTime());
 }
 
 void VideoReceiveStream2::SetRawPayloadTypes(std::set<int> raw_payload_types) {
diff --git a/third_party/libwebrtc/video/video_receive_stream2.h b/third_party/libwebrtc/video/video_receive_stream2.h
index 4ff12a7fb15..a55b3488c37 100644
--- a/third_party/libwebrtc/video/video_receive_stream2.h
+++ b/third_party/libwebrtc/video/video_receive_stream2.h
@@ -255,10 +255,12 @@ class VideoReceiveStream2
 
   void UpdateHistograms();
   void ConfigureCodecs() RTC_RUN_ON(worker_sequence_checker_);
-  // Registers new decoders with the internal VideoReceiver2 database and
-  // deregisters all external decoder instances from `old_decoders` so that new
-  // instances get created on demand.
-  void RegisterCodecsOnReceiver(const std::vector<Decoder>& old_decoders,
+  // Applies the difference between `old_decoders` and `new_decoders` to the
+  // internal VideoReceiver2 database. Removed or updated decoders, including
+  // their external decoder instances, are deregistered and added or updated
+  // decoders are registered. Unchanged decoders are left as is. Returns true if
+  // an external decoder instance was deregistered.
+  bool RegisterCodecsOnReceiver(const std::vector<Decoder>& old_decoders,
                                 const std::vector<Decoder>& new_decoders);
   std::vector<RtpVideoStreamReceiver2::ReceiveCodec> GetReceiveCodecConfig()
       const RTC_RUN_ON(worker_sequence_checker_);
diff --git a/third_party/libwebrtc/video/video_receive_stream2_unittest.cc b/third_party/libwebrtc/video/video_receive_stream2_unittest.cc
index 87eddc0c390..70b974e40c5 100644
--- a/third_party/libwebrtc/video/video_receive_stream2_unittest.cc
+++ b/third_party/libwebrtc/video/video_receive_stream2_unittest.cc
@@ -899,6 +899,7 @@ TEST_P(VideoReceiveStream2Test,
                                              .AsLast()
                                              .Build());
   time_controller_.AdvanceTime(TimeDelta::Millis(100));
+  const int pli_packets = rtcp_packet_parser_.pli()->num_packets();
 
   // Reconfigure PT kH264PayloadType with modified video_format parameters.
   VideoReceiveStreamInterface::Decoder new_decoder;
@@ -908,6 +909,9 @@ TEST_P(VideoReceiveStream2Test,
   video_receive_stream_->SetDecoders({new_decoder});
   time_controller_.AdvanceTime(TimeDelta::Millis(100));
 
+  // Replacing the decoder that is in use requires a keyframe.
+  EXPECT_EQ(rtcp_packet_parser_.pli()->num_packets(), pli_packets + 1);
+
   // Verify that modified format causes old decoder to be deregistered and a
   // new decoder created.
   EXPECT_CALL(mock_decoder_factory_,
@@ -927,11 +931,11 @@ TEST_P(VideoReceiveStream2Test,
   time_controller_.AdvanceTime(TimeDelta::Millis(100));
 }
 
-// Changing the set of decoders while running releases the active decoder.
-// Verify that a new instance is created for an unchanged decoder, instead of
-// configuring the released instance again. Not all decoders support that, e.g.
-// hardware decoders may fall back to software decoding.
-TEST_P(VideoReceiveStream2Test, SetDecodersRecreatesUnchangedDecoder) {
+// Changing the set of decoders while running should not affect an unchanged
+// decoder that is in use. Releasing and configuring it again would require a
+// keyframe and isn't supported by all decoders, e.g. hardware decoders may fall
+// back to software decoding.
+TEST_P(VideoReceiveStream2Test, SetDecodersKeepsUnchangedDecoder) {
   video_receive_stream_->Start();
   time_controller_.AdvanceTime(TimeDelta::Zero());
 
@@ -949,6 +953,14 @@ TEST_P(VideoReceiveStream2Test, SetDecodersRecreatesUnchangedDecoder) {
                                              .AsLast()
                                              .Build());
   time_controller_.AdvanceTime(TimeDelta::Millis(100));
+  const int pli_packets = rtcp_packet_parser_.pli()->num_packets();
+
+  // Verify that the AV1 decoder keeps decoding without being released,
+  // configured again or recreated.
+  EXPECT_CALL(mock_decoder_factory_, Create).Times(0);
+  EXPECT_CALL(mock_decoder_, Release).Times(0);
+  EXPECT_CALL(mock_decoder_, Configure).Times(0);
+  EXPECT_CALL(mock_decoder_, Decode(_, _));
 
   // Add a decoder while keeping the AV1 decoder unchanged, as happens when the
   // receiving side creates an offer listing all of its receive codecs.
@@ -961,21 +973,19 @@ TEST_P(VideoReceiveStream2Test, SetDecodersRecreatesUnchangedDecoder) {
   video_receive_stream_->SetDecoders(std::move(decoders));
   time_controller_.AdvanceTime(TimeDelta::Millis(100));
 
-  // Verify that a new AV1 decoder is created.
-  EXPECT_CALL(mock_decoder_factory_,
-              Create(_, Field(&SdpVideoFormat::name, Eq("AV1"))));
-  EXPECT_CALL(mock_decoder_, Configure);
-  EXPECT_CALL(mock_decoder_, RegisterDecodeCompleteCallback);
-  EXPECT_CALL(mock_decoder_, Decode(_, _));
-
   video_receive_stream_->OnCompleteFrame(test::FakeFrameBuilder()
                                              .Id(1)
                                              .PayloadType(kAv1PayloadType)
                                              .Time(12345)
                                              .AsLast()
                                              .Build());
-
   time_controller_.AdvanceTime(TimeDelta::Millis(100));
+
+  // No keyframe is needed since the decoder state is intact.
+  EXPECT_EQ(rtcp_packet_parser_.pli()->num_packets(), pli_packets);
+
+  // Allow the decoder to be released when the stream is stopped.
+  EXPECT_TRUE(testing::Mock::VerifyAndClearExpectations(&mock_decoder_));
 }
 
 TEST_P(VideoReceiveStream2Test, SetDecodersWhileStoppedUpdatesCodecs) {
Loading diff…