Overview

Medium
Severity
CVSS
No
Exploited ITW
Fixed
Fix Status
ImpactOut of bounds read in WebRTC
DescriptionOut of bounds read in WebRTC
ComponentWebRTC
Bug ClassOOB
Tracker500296311
Fix commit626adaa56e77 (chromium/src) +10/-7
CISA KEVNot listed
CreditedGoogle
Disclosed2026-06-02

Changed Functions

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

Files Changed

  • third_party/blink/renderer/platform/peerconnection/rtc_video_encoder.cc
From 626adaa56e77945e51227c1189746d1b90076e79 Mon Sep 17 00:00:00 2001
From: Ilya Nikolaevskiy <[email protected]>
Date: Fri, 10 Apr 2026 07:15:19 -0700
Subject: [PATCH] Use correct dimensions in CreateNV12SharedImageFrame in RtcVideoEncoder

The existing code doesn't take into account that ToI420() may
already crop and downscale the image.

Fixed: 500296311
Change-Id: I64aa22f26c9021346c18edd92af6405149df4623
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/7735747
Reviewed-by: Dale Curtis <[email protected]>
Commit-Queue: Ilya Nikolaevskiy <[email protected]>
Cr-Commit-Position: refs/heads/main@{#1612839}
---

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 68ea3218..5ec5b6a 100644
--- a/third_party/blink/renderer/platform/peerconnection/rtc_video_encoder.cc
+++ b/third_party/blink/renderer/platform/peerconnection/rtc_video_encoder.cc
@@ -2147,13 +2147,16 @@
   CHECK(!input_buffers_free_.empty());
   TRACE_EVENT1("webrtc", "RTCVideoEncoder::Impl::CreateNV12SharedImageFrame",
                "visible_rect", frame->visible_rect().ToString());
+
+  // ToI420() below may rescale the image. It will always output the
+  // resolution reported by VideoFrameBuffer.
+  const gfx::Size frame_size(frame_buffer.width(), frame_buffer.height());
+
   const int index = input_buffers_free_.back();
   scoped_refptr<gpu::ClientSharedImage>& nv12_shared_image =
       input_buffers_[index].nv12_shared_image;
-  if (!nv12_shared_image ||
-      nv12_shared_image->size() != frame->visible_rect().size()) {
-    nv12_shared_image =
-        CreateClientSharedImage(gpu_factories_, frame->visible_rect().size());
+  if (!nv12_shared_image || nv12_shared_image->size() != frame_size) {
+    nv12_shared_image = CreateClientSharedImage(gpu_factories_, frame_size);
     if (!nv12_shared_image) {
       NotifyErrorStatus({media::EncoderStatus::Codes::kSystemAPICallError,
                          "Failed to allocate shared image"});
@@ -2180,8 +2183,8 @@
   uint8_t* dst_uv = mapping->GetMemoryForPlane(1).data();
   const size_t dst_y_stride = mapping->Stride(0);
   const size_t dst_uv_stride = mapping->Stride(1);
-  const size_t width = frame->visible_rect().width();
-  const size_t height = frame->visible_rect().height();
+  const size_t width = frame_size.width();
+  const size_t height = frame_size.height();
   if (libyuv::I420ToNV12(i420_buffer->DataY(), i420_buffer->StrideY(),
                          i420_buffer->DataU(), i420_buffer->StrideU(),
                          i420_buffer->DataV(), i420_buffer->StrideV(), dst_y,
@@ -2201,7 +2204,7 @@
   // The timestamp is set later in EncodeOneFrameWithNativeInput().
   frame = media::VideoFrame::WrapMappableSharedImage(
       nv12_shared_image, sync_token, base::NullCallback(),
-      frame->visible_rect(), frame->visible_rect().size(), base::TimeDelta());
+      gfx::Rect(frame_size), frame_size, base::TimeDelta());
   if (!frame) {
     NotifyErrorStatus({media::EncoderStatus::Codes::kEncoderFailedEncode,
                        "Failed to create video frame"});
Loading diff…

Original Bug Report

reported by [email protected]

Potential Heap OOB Read in RTCVideoEncoder::Impl::CreateNV12SharedImageFrame

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 security team.

Overview: A potential heap out-of-bounds read exists in RTCVideoEncoder when processing a crafted VideoFrame. If a frame has a visible_rect larger than its natural_size, the encoder scales the frame down but attempts to read from the scaled buffer using the original, larger dimensions, resulting in an OOB read. This leaked memory can be encoded and transmitted over WebRTC.

Affected files:

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

Estimated timestamp from git blame: 2025-10-02

Description

A potential heap out-of-bounds (OOB) read vulnerability exists in the renderer process on platforms supporting hardware video encoding with native input (e.g., ChromeOS). The issue is located in RTCVideoEncoder::Impl::CreateNV12SharedImageFrame within third_party/blink/renderer/platform/peerconnection/rtc_video_encoder.cc.

The vulnerability is triggered when a VideoFrame is created with a visible_rect that is significantly larger than its natural_size (the display size). While Blink and the media layer validate that visible_rect fits within coded_size, they do not enforce that visible_rect must be smaller than or equal to natural_size.

When such a frame is routed to the hardware encoder (e.g., via a MediaStreamTrackGenerator), RTCVideoEncoder::Impl::EncodeOneFrameWithNativeInput may decide to convert it to an NV12 SharedImage.

  1. It calls CreateNV12SharedImageFrame.
  2. The method allocates a new NV12 SharedImage using the dimensions of the original frame’s visible_rect.
  3. It then extracts an I420 buffer by calling frame_buffer.ToI420(). Because natural_size differs from visible_rect, the ConvertToWebRtcVideoFrameBuffer utility downscales the frame to match natural_size. The returned I420Buffer is only large enough to hold the natural_size dimensions.
  4. However, CreateNV12SharedImageFrame extracts the width and height for the copy operation from the original frame’s visible_rect:
    const size_t width = frame->visible_rect().width();
    const size_t height = frame->visible_rect().height();
    
  5. libyuv::I420ToNV12 is then invoked with these large dimensions, causing it to read far past the bounds of the small, downscaled I420Buffer.

Impact

This is a renderer-process information leak. The massive OOB read copies adjacent renderer heap memory into the NV12 SharedImage. This image is then passed to the hardware encoder, compressed, and sent over the active RTCPeerConnection. A remote attacker can decode the stream to view or extract the leaked heap contents.

Suggested Reproduction Steps

Note: These are potential steps as our tooling cannot execute a full Proof-of-Concept.

  1. On a platform with a hardware WebRTC encoder (e.g., ChromeOS), host a page that creates a large 1920x1080 canvas and generates an ImageBitmap.
  2. Use WebCodecs to create a VideoFrame from the bitmap with mismatched dimensions:
    • visibleRect: {x: 0, y: 0, width: 1920, height: 1080}
    • displayWidth: 640
    • displayHeight: 480
  3. Push this frame into a MediaStreamTrackGenerator configured for video.
  4. Add the track to an RTCPeerConnection and connect to a remote peer.
  5. Observe an ASAN heap-buffer-overflow READ crash in libyuv::I420ToNV12 (or underlying copy functions) within the renderer process. On a non-ASAN build, observe the leaked memory in the decoded video stream on the remote peer.

Suggested Fix

In RTCVideoEncoder::Impl::CreateNV12SharedImageFrame, do not use the original frame->visible_rect() to determine the copy dimensions. Instead, use the actual dimensions of the extracted i420_buffer:

const size_t width = i420_buffer->width();
const size_t height = i420_buffer->height();

Additionally, the nv12_shared_image allocation and the final WrapMappableSharedImage call should also use these dimensions to ensure consistency across the conversion process.

Evaluated with Chrome root at commit: f200f57a19490707ff8bc7aa5de3cbc443a3afad


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