Overview

High
Severity
CVSS
No
Exploited ITW
Fixed
Fix Status
ImpactUse after free in WebRTC
DescriptionUse after free in WebRTC
ComponentWebRTC
Bug ClassUAF
Tracker504612429
Fix commitebdeef338b47 (chromium/src) +12/-15
CISA KEVNot listed
CreditedGoogle
Disclosed2026-05-05

Changed Functions

FunctionChangeNotes
if
third_party/blink/renderer/platform/peerconnection/rtc_video_encoder.cc
modified
Impl
third_party/blink/renderer/platform/peerconnection/rtc_video_encoder.h
modified

Files Changed

  • third_party/blink/renderer/platform/peerconnection/rtc_video_encoder.cc
  • third_party/blink/renderer/platform/peerconnection/rtc_video_encoder.h
From ebdeef338b474242acdfb5c138bd1e6e1528c033 Mon Sep 17 00:00:00 2001
From: Ilya Nikolaevskiy <ilnik@chromium.org>
Date: Tue, 21 Apr 2026 05:37:39 -0700
Subject: [PATCH] Pass SimToSvcConverter in RtcVideoEncoder::Impl initalization

This was the only piece which was set separately after the initialization.
This was causing a data race.

Now it's sent in the same path as everyting else and is set on the correct
thread.

Alternative was to pass it in vea_config instead as a new functions parameter,
but this is a RtcVideoEncoder detail, so it was not reasonable to include
it in VEA::config.

Fixed: 504612429
Change-Id: Iacbf82cd844e85c1119f9d20f48e9b96057cc4f5
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/7780805
Reviewed-by: Guido Urdaneta <guidou@chromium.org>
Commit-Queue: Ilya Nikolaevskiy <ilnik@chromium.org>
Cr-Commit-Position: refs/heads/main@{#1618101}
---

diff --git a/third_party/blink/renderer/platform/peerconnection/rtc_video_encoder.cc b/third_party/blink/renderer/platform/peerconnection/rtc_video_encoder.cc
index 662f22c0..4ee951f 100644
--- a/third_party/blink/renderer/platform/peerconnection/rtc_video_encoder.cc
+++ b/third_party/blink/renderer/platform/peerconnection/rtc_video_encoder.cc
@@ -77,7 +77,6 @@
 #include "third_party/webrtc/modules/video_coding/codecs/h264/include/h264.h"
 #include "third_party/webrtc/modules/video_coding/include/video_error_codes.h"
 #include "third_party/webrtc/modules/video_coding/svc/create_scalability_structure.h"
-#include "third_party/webrtc/modules/video_coding/svc/simulcast_to_svc_converter.h"
 #include "third_party/webrtc/rtc_base/time_utils.h"
 
 namespace {
@@ -723,6 +722,7 @@
   // its own thread, hence the |init_event| argument.
   void CreateAndInitializeVEA(
       const media::VideoEncodeAccelerator::Config& vea_config,
+      std::optional<webrtc::SimulcastToSvcConverter> simulcast_to_svc_converter,
       SignaledValue init_event);
 
   // Enqueue a frame from WebRTC for encoding. This function is called
@@ -768,9 +768,6 @@
   void Drain(SignaledValue event);
   void DrainCompleted(bool success);
 
-  void SetSimulcastToSvcConverter(std::optional<webrtc::SimulcastToSvcConverter>
-                                      simulcast_to_svc_converter);
-
  private:
   enum {
     kInputBufferExtraCount = 1,  // The number of input buffers allocated, more
@@ -1080,11 +1077,13 @@
 
 void RTCVideoEncoder::Impl::CreateAndInitializeVEA(
     const media::VideoEncodeAccelerator::Config& vea_config,
+    std::optional<webrtc::SimulcastToSvcConverter> simulcast_to_svc_converter,
     SignaledValue init_event) {
   TRACE_EVENT0("webrtc", "RTCVideoEncoder::Impl::CreateAndInitializeVEA");
   DVLOG(3) << __func__;
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
 
+  simulcast_to_svc_converter_ = std::move(simulcast_to_svc_converter);
   status_ = WEBRTC_VIDEO_CODEC_UNINITIALIZED;
   async_init_event_ = ScopedSignaledValue(std::move(init_event));
 
@@ -1286,11 +1285,6 @@
   }
 }
 
-void RTCVideoEncoder::Impl::SetSimulcastToSvcConverter(
-    std::optional<webrtc::SimulcastToSvcConverter> simulcast_to_svc_converter) {
-  simulcast_to_svc_converter_ = std::move(simulcast_to_svc_converter);
-}
-
 void RTCVideoEncoder::Impl::UseOutputBitstreamBuffer(
     int32_t bitstream_buffer_id) {
   TRACE_EVENT0("webrtc", "RTCVideoEncoder::Impl::UseOutputBitstreamBuffer");
@@ -2607,7 +2601,8 @@
 }
 
 int32_t RTCVideoEncoder::InitializeEncoder(
-    const media::VideoEncodeAccelerator::Config& vea_config) {
+    const media::VideoEncodeAccelerator::Config& vea_config,
+    std::optional<webrtc::SimulcastToSvcConverter> simulcast_to_svc_converter) {
   DCHECK_CALLED_ON_VALID_SEQUENCE(webrtc_sequence_checker_);
   TRACE_EVENT1("webrtc", "RTCVideoEncoder::InitEncode", "config",
                vea_config.AsHumanReadableString());
@@ -2627,7 +2622,7 @@
         *gpu_task_runner_.get(), FROM_HERE,
         CrossThreadBindOnce(
             &RTCVideoEncoder::Impl::CreateAndInitializeVEA, weak_impl_,
-            vea_config,
+            vea_config, std::move(simulcast_to_svc_converter),
             SignaledValue(&initialization_waiter, &initialization_retval)));
     // webrtc::VideoEncoder expects this call to be synchronous.
     initialization_waiter.Wait();
@@ -2878,12 +2873,11 @@
         media::VideoEncodeAccelerator::Config::EncoderType::kNoPreference;
   }
 
-  int32_t initialization_ret = InitializeEncoder(vea_config);
+  int32_t initialization_ret =
+      InitializeEncoder(vea_config, std::move(simulcast_to_svc_converter));
   if (initialization_ret != WEBRTC_VIDEO_CODEC_OK) {
     ReleaseImpl();
     CHECK(!impl_);
-  } else {
-    impl_->SetSimulcastToSvcConverter(std::move(simulcast_to_svc_converter));
   }
   return initialization_ret;
 }
diff --git a/third_party/blink/renderer/platform/peerconnection/rtc_video_encoder.h b/third_party/blink/renderer/platform/peerconnection/rtc_video_encoder.h
index 6be01325..c089928 100644
--- a/third_party/blink/renderer/platform/peerconnection/rtc_video_encoder.h
+++ b/third_party/blink/renderer/platform/peerconnection/rtc_video_encoder.h
@@ -25,6 +25,7 @@
 #include "third_party/blink/renderer/platform/wtf/functional.h"
 #include "third_party/webrtc/api/video/video_bitrate_allocation.h"
 #include "third_party/webrtc/modules/video_coding/include/video_codec_interface.h"
+#include "third_party/webrtc/modules/video_coding/svc/simulcast_to_svc_converter.h"
 #include "ui/gfx/geometry/size.h"
 
 #if BUILDFLAG(RTC_USE_H265)
@@ -95,7 +96,9 @@
   class Impl;
 
   int32_t InitializeEncoder(
-      const media::VideoEncodeAccelerator::Config& vea_config);
+      const media::VideoEncodeAccelerator::Config& vea_config,
+      std::optional<webrtc::SimulcastToSvcConverter>
+          simulcast_to_svc_converter);
   void UpdateEncoderInfo(
       media::VideoEncoderInfo encoder_info,
       std::vector<webrtc::VideoFrameBuffer::Type> preferred_pixel_formats);
Loading diff…

Original Bug Report

reported by vm...@google.com

Potential UAF via race condition in RTCVideoEncoder::Impl::SetSimulcastToSvcConverter

Project Fortify, an experimental security project, has identified the following potential security issue. If you’re a feature owner CC-ed on this bug, please do your best to review these reports without the Chrome Security team. Please see https://chromium.googlesource.com/chromium/src/+/main/docs/security/ai-generated-security-bugs-faq.md for more information.

Overview: A race condition between the WebRTC encoder thread and the GPU task runner in RTCVideoEncoder allows concurrent access to simulcast_to_svc_converter_ without locking. If an attacker triggers an encoder reconfiguration while input frames are queued, a task ordering race can result in a Heap Use-After-Free during a virtual method call. This vulnerability could lead to arbitrary code execution within the renderer process.

Affected files:

  • third_party/blink/renderer/platform/peerconnection/rtc_video_encoder.cc

Estimated timestamp from git blame: 2024-09-20

Summary

A potential race condition in RTCVideoEncoder::Impl::SetSimulcastToSvcConverter can lead to a heap use-after-free (UAF) in the renderer process. The vulnerability occurs due to unsynchronized cross-thread access to the simulcast_to_svc_converter_ member between the WebRTC encoder thread and the GPU task runner during encoder reconfiguration.

Root Cause Analysis

In third_party/blink/renderer/platform/peerconnection/rtc_video_encoder.cc, the simulcast_to_svc_converter_ member is accessed by two distinct threads without synchronization:

  1. Read: The GPU task runner reads it inside RTCVideoEncoder::Impl::EncodeOneFrame and makes virtual method calls on its owned objects.
  2. Write: The WebRTC encoder thread move-assigns it directly inside RTCVideoEncoder::Impl::SetSimulcastToSvcConverter during InitEncode.

A race window opens when an RTCPeerConnection video track is reconfigured (e.g., via applyConstraints()) while the encoder has frames waiting in pending_frames_.

During reconfiguration, the WebRTC thread calls DrainEncoderAndUpdateFrameSize, posting an Impl::Drain task to the GPU task runner and blocking on drain_waiter.Wait(). Impl::Drain issues a Mojo Flush command to the hardware encoder.

As the hardware encoder finishes previous Encode calls, it releases its references to the input media::VideoFrame objects. The destruction observer for these frames (RTCVideoEncoder::Impl::InputBufferReleased) is posted back to the GPU task runner via base::BindPostTaskToCurrentDefault(). Because it is posted, it lands at the end of the GPU task runner’s queue.

When the Flush operation completes, Mojo directly executes the RTCVideoEncoder::Impl::DrainCompleted callback. This means DrainCompleted is executed by the GPU task runner before the asynchronously posted InputBufferReleased task.

DrainCompleted signals drain_waiter, immediately unblocking the WebRTC thread. The WebRTC thread then returns to InitEncode and executes impl_->SetSimulcastToSvcConverter(std::move(...)), destroying the old SimulcastToSvcConverter object.

Concurrently, the GPU task runner finishes DrainCompleted and executes InputBufferReleased. It finds frames in pending_frames_, calls EncodeOneFrame, and attempts to execute virtual methods on the ScalableVideoController objects owned by the SimulcastToSvcConverter that the WebRTC thread is currently destroying.

Impact

This race results in a heap Use-After-Free targeting std::vector<LayerState> elements and the std::unique_ptr<webrtc::ScalableVideoController> objects they contain. Because the UAF targets an object with virtual methods (ScalableVideoController::OnEncodeDone), an attacker capable of manipulating the heap could potentially hijack the virtual call and achieve arbitrary code execution in the renderer process.

This code resides partially in the WebRTC codebase (third_party/webrtc/modules/video_coding/svc/), where std::unique_ptr is used instead of base::raw_ptr. As such, this vulnerability is not mitigated by MiraclePtr (BackupRefPtr).

Potential Trigger Path

Please note: These are theoretical steps; our automated tooling has not verified this with an executable Proof-of-Concept.

  1. A page creates an RTCPeerConnection using VP9 on Windows with a hardware encoder supporting SVC scalability.
  2. The page provides frames to the encoder faster than it can process them, causing the pending_frames_ queue to fill and hardware input buffers to become exhausted.
  3. The page triggers a same-layer-count reconfiguration (e.g., track.applyConstraints()) that allows the RTCVideoEncoder::Impl instance to be reused.
  4. During the drain/flush sequence, the race between the InputBufferReleased task and the WebRTC thread’s SetSimulcastToSvcConverter call occurs, triggering the UAF.

Suggested Fix

Ensure that all access to simulcast_to_svc_converter_ is synchronized.

One approach is to use the existing lock_ mutex inside RTCVideoEncoder::Impl to protect read/write access to simulcast_to_svc_converter_. Alternatively, ensure that SetSimulcastToSvcConverter is executed strictly on the GPU task runner (via PostCrossThreadTask), just like other state modifications in RTCVideoEncoder::Impl.

Evaluated with Chrome root at commit: 7353d249d9cacf9c7218e1d7b8a39cf39c72d646


Results so far have been promising, but there can be wrong deductions. If this proves to be a false positive, please close as WAI; data from false positives will be used to improve accuracy over time. And please feel free to reach out to me directly if you have concerns or feedback on the project.

View on issue tracker