Overview

Medium
Severity
CVSS
No
Exploited ITW
Fixed
Fix Status
ImpactOut of bounds read in WebCodecs
DescriptionOut of bounds read in WebCodecs
ComponentMedia
Bug ClassOOB
Tracker441917796
Fix commitdd13b8a2ae1d (chromium/src) +188/-20
CISA KEVNot listed
CreditedJakob Košir
Disclosed2025-10-07

Changed Functions

FunctionChangeNotes
for
media/base/video_frame.cc
modified
TEST
media/base/video_frame_unittest.cc
modified
for
media/base/video_frame_unittest.cc
modified
if
media/base/video_frame_unittest.cc
modified

Files Changed

  • media/base/video_frame.cc
  • media/base/video_frame.h
  • media/base/video_frame_unittest.cc
From dd13b8a2ae1d7a883958aecd964b90fae0fec263 Mon Sep 17 00:00:00 2001
From: Dale Curtis <dalecurtis@chromium.org>
Date: Fri, 29 Aug 2025 20:13:53 -0700
Subject: [PATCH] Use input visible size when calling CopyPlane()

The output coded size may be adjusted which causes the row_bytes()
and rows() methods on VideoFrame to return values which may be
outside the range of source.

Fixed: 441917796
Change-Id: I8bb6c7e70060717b9b28688a6424e0b032fb99ce
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/6900663
Commit-Queue: Eugene Zemtsov <eugene@chromium.org>
Reviewed-by: Eugene Zemtsov <eugene@chromium.org>
Auto-Submit: Dale Curtis <dalecurtis@chromium.org>
Cr-Commit-Position: refs/heads/main@{#1508722}
---

diff --git a/media/base/video_frame.cc b/media/base/video_frame.cc
index 8fb80072..ac36168 100644
--- a/media/base/video_frame.cc
+++ b/media/base/video_frame.cc
@@ -1270,20 +1270,26 @@
 
 // static
 void VideoFrame::UpdateHashWithFrameForTesting(crypto::hash::Hasher& hasher,
-                                               const VideoFrame& frame) {
+                                               const VideoFrame& frame,
+                                               bool visible_data_only) {
   for (size_t plane = 0; plane < NumPlanes(frame.format()); ++plane) {
-    for (int row = 0; row < frame.rows(plane); ++row) {
-      hasher.Update(frame.data_[plane].subspan(
-          base::checked_cast<size_t>(frame.stride(plane) * row),
-          base::checked_cast<size_t>(frame.row_bytes(plane))));
+    const size_t rows = base::checked_cast<size_t>(
+        visible_data_only ? frame.GetVisibleRows(plane) : frame.rows(plane));
+    const size_t row_bytes = base::checked_cast<size_t>(
+        visible_data_only ? frame.GetVisibleRowBytes(plane)
+                          : frame.row_bytes(plane));
+    const auto& plane_data = frame.data_[plane];
+    for (size_t row = 0; row < rows; ++row) {
+      hasher.Update(plane_data.subspan(frame.stride(plane) * row, row_bytes));
     }
   }
 }
 
 // static
-std::string VideoFrame::HexHashOfFrameForTesting(const VideoFrame& frame) {
+std::string VideoFrame::HexHashOfFrameForTesting(const VideoFrame& frame,
+                                                 bool visible_data_only) {
   crypto::hash::Hasher hasher(crypto::hash::HashKind::kSha256);
-  UpdateHashWithFrameForTesting(hasher, frame);  // IN-TEST
+  UpdateHashWithFrameForTesting(hasher, frame, visible_data_only);  // IN-TEST
   std::array<uint8_t, crypto::hash::kSha256Size> hash;
   hasher.Finish(hash);
   return base::ToLowerASCII(base::HexEncode(hash));
@@ -1462,6 +1468,14 @@
   return Rows(plane, format(), coded_size().height());
 }
 
+int VideoFrame::GetVisibleRowBytes(size_t plane) const {
+  return RowBytes(plane, format(), visible_rect().width());
+}
+
+int VideoFrame::GetVisibleRows(size_t plane) const {
+  return Rows(plane, format(), visible_rect().height());
+}
+
 int VideoFrame::columns(size_t plane) const {
   return Columns(plane, format(), coded_size().width());
 }
@@ -1482,7 +1496,6 @@
                           base::bits::AlignDownDeprecatedDoNotUse(
                               visible_rect_.y(), alignment.height()));
 
-  const int visible_plane_rows = Rows(plane, format(), visible_rect_.height());
   const int plane_stride = stride(plane);
   const gfx::Size subsample = SampleSize(format(), plane);
   DCHECK(offset.x() % subsample.width() == 0);
@@ -1494,8 +1507,8 @@
       BytesPerElement(format(), plane) * (offset.x() / subsample.width()));
   // In the last row, bytes between visible width and the full stride are not
   // the part of the visible plane.
-  size_t visible_plane_size = plane_stride * (visible_plane_rows - 1) +
-                              RowBytes(plane, format(), visible_rect_.width());
+  size_t visible_plane_size =
+      plane_stride * (GetVisibleRows(plane) - 1) + GetVisibleRowBytes(plane);
   return data.subspan(visible_plane_offset, visible_plane_size);
 }
 
diff --git a/media/base/video_frame.h b/media/base/video_frame.h
index 72b870c..56d47377 100644
--- a/media/base/video_frame.h
+++ b/media/base/video_frame.h
@@ -478,12 +478,16 @@
   static size_t Columns(size_t plane, VideoPixelFormat format, int width);
 
   // Given a crypto/hash Hasher, hash in the pixels from a single VideoFrame.
+  // If `visible_data_only` is true only the frame's visible area will be
+  // hashed, if false then the entire coded frame area will be hashed.
   static void UpdateHashWithFrameForTesting(crypto::hash::Hasher& hasher,
-                                            const VideoFrame& frame);
+                                            const VideoFrame& frame,
+                                            bool visible_data_only = true);
 
   // Convenience wrapper around UpdateHashWithFrameForTesting(): produces the
   // SHA-256 hash of a single video frame's pixels, as a lowercase hex string.
-  static std::string HexHashOfFrameForTesting(const VideoFrame& frame);
+  static std::string HexHashOfFrameForTesting(const VideoFrame& frame,
+                                              bool visible_data_only = true);
 
   // Returns true if |frame| is accessible mapped in the VideoFrame memory
   // space.
@@ -627,6 +631,10 @@
   int row_bytes(size_t plane) const;
   int rows(size_t plane) const;
 
+  // Similar to row_bytes() and rows(), but instead refers to the visible area.
+  int GetVisibleRowBytes(size_t plane) const;
+  int GetVisibleRows(size_t plane) const;
+
   // Returns the number of columns for a given plane.
   int columns(size_t plane) const;
 
diff --git a/media/base/video_frame_unittest.cc b/media/base/video_frame_unittest.cc
index 2b906fa..01280fe0 100644
--- a/media/base/video_frame_unittest.cc
+++ b/media/base/video_frame_unittest.cc
@@ -240,7 +240,9 @@
            frame->stride(plane) * frame->rows(plane));
   }
 
-  EXPECT_EQ(VideoFrame::HexHashOfFrameForTesting(*frame.get()), expected_hash);
+  EXPECT_EQ(VideoFrame::HexHashOfFrameForTesting(*frame,
+                                                 /*visible_data_only=*/false),
+            expected_hash);
 }
 
 TEST(VideoFrame, CreateFrame) {
@@ -785,6 +787,75 @@
   EXPECT_EQ(677, frame->coded_size().width());
 }
 
+TEST(VideoFrame, CreateFrame_OddSize) {
+  constexpr gfx::Size odd_size(677, 287);
+  constexpr auto kTimestamp = base::TimeDelta();
+
+  // I420A frames should have the Y, A planes aligned upward to an even size,
+  // since it's required by the subsampled U,V planes.
+  auto frame = VideoFrame::CreateFrame(
+      PIXEL_FORMAT_I420A, odd_size, gfx::Rect(odd_size), odd_size, kTimestamp);
+  ASSERT_TRUE(frame);
+  EXPECT_EQ(gfx::Size(678, 288), frame->coded_size());
+  for (int plane = 0; plane < 4; plane++) {
+    // CreateFrame() will always allocate to an even coded size for non-sampled
+    // planes, so the visible row data should be less than the coded row data.
+    if (plane == VideoFrame::Plane::kY || plane == VideoFrame::Plane::kA) {
+      EXPECT_LT(frame->GetVisibleRowBytes(plane), frame->row_bytes(plane));
+      EXPECT_LT(frame->GetVisibleRows(plane), frame->rows(plane));
+    } else {
+      EXPECT_EQ(frame->GetVisibleRowBytes(plane), frame->row_bytes(plane));
+      EXPECT_EQ(frame->GetVisibleRows(plane), frame->rows(plane));
+    }
+  }
+
+  // I444A frames don't have subsampling, so no adjustment is needed.
+  frame = VideoFrame::CreateFrame(PIXEL_FORMAT_I444A, odd_size,
+                                  gfx::Rect(odd_size), odd_size, kTimestamp);
+  ASSERT_TRUE(frame);
+  EXPECT_EQ(odd_size, frame->coded_size());
+  for (int plane = 0; plane < 4; plane++) {
+    EXPECT_EQ(frame->GetVisibleRowBytes(plane), frame->row_bytes(plane));
+    EXPECT_EQ(frame->GetVisibleRows(plane), frame->rows(plane));
+  }
+}
+
+TEST(VideoFrame, RowBytes) {
+  constexpr gfx::Size kCodedSize(16, 14);
+  constexpr gfx::Rect kVisibleRect(4, 4, 8, 8);
+
+  // Just spot test I420A for simplicity.
+  scoped_refptr<VideoFrame> frame =
+      VideoFrame::CreateFrame(PIXEL_FORMAT_I420A, kCodedSize, kVisibleRect,
+                              kVisibleRect.size(), base::TimeDelta());
+  ASSERT_TRUE(frame);
+  ASSERT_EQ(frame->row_bytes(VideoFrame::Plane::kY), kCodedSize.width());
+  ASSERT_EQ(frame->row_bytes(VideoFrame::Plane::kU), kCodedSize.width() / 2);
+  ASSERT_EQ(frame->row_bytes(VideoFrame::Plane::kV), kCodedSize.width() / 2);
+  ASSERT_EQ(frame->row_bytes(VideoFrame::Plane::kA), kCodedSize.width());
+  ASSERT_EQ(frame->rows(VideoFrame::Plane::kY), kCodedSize.height());
+  ASSERT_EQ(frame->rows(VideoFrame::Plane::kU), kCodedSize.height() / 2);
+  ASSERT_EQ(frame->rows(VideoFrame::Plane::kV), kCodedSize.height() / 2);
+  ASSERT_EQ(frame->rows(VideoFrame::Plane::kA), kCodedSize.height());
+
+  ASSERT_EQ(frame->GetVisibleRowBytes(VideoFrame::Plane::kY),
+            kVisibleRect.width());
+  ASSERT_EQ(frame->GetVisibleRowBytes(VideoFrame::Plane::kU),
+            kVisibleRect.width() / 2);
+  ASSERT_EQ(frame->GetVisibleRowBytes(VideoFrame::Plane::kV),
+            kVisibleRect.width() / 2);
Loading diff…

Regression Test / PoC

shipped with the fix
diff --git a/media/base/video_frame_unittest.cc b/media/base/video_frame_unittest.cc
index 2b906fa..01280fe0 100644
--- a/media/base/video_frame_unittest.cc
+++ b/media/base/video_frame_unittest.cc
@@ -240,7 +240,9 @@
            frame->stride(plane) * frame->rows(plane));
   }
 
-  EXPECT_EQ(VideoFrame::HexHashOfFrameForTesting(*frame.get()), expected_hash);
+  EXPECT_EQ(VideoFrame::HexHashOfFrameForTesting(*frame,
+                                                 /*visible_data_only=*/false),
+            expected_hash);
 }
 
 TEST(VideoFrame, CreateFrame) {
@@ -785,6 +787,75 @@
   EXPECT_EQ(677, frame->coded_size().width());
 }
 
+TEST(VideoFrame, CreateFrame_OddSize) {
+  constexpr gfx::Size odd_size(677, 287);
+  constexpr auto kTimestamp = base::TimeDelta();
+
+  // I420A frames should have the Y, A planes aligned upward to an even size,
+  // since it's required by the subsampled U,V planes.
+  auto frame = VideoFrame::CreateFrame(
+      PIXEL_FORMAT_I420A, odd_size, gfx::Rect(odd_size), odd_size, kTimestamp);
+  ASSERT_TRUE(frame);
+  EXPECT_EQ(gfx::Size(678, 288), frame->coded_size());
+  for (int plane = 0; plane < 4; plane++) {
+    // CreateFrame() will always allocate to an even coded size for non-sampled
+    // planes, so the visible row data should be less than the coded row data.
+    if (plane == VideoFrame::Plane::kY || plane == VideoFrame::Plane::kA) {
+      EXPECT_LT(frame->GetVisibleRowBytes(plane), frame->row_bytes(plane));
+      EXPECT_LT(frame->GetVisibleRows(plane), frame->rows(plane));
+    } else {
+      EXPECT_EQ(frame->GetVisibleRowBytes(plane), frame->row_bytes(plane));
+      EXPECT_EQ(frame->GetVisibleRows(plane), frame->rows(plane));
+    }
+  }
+
+  // I444A frames don't have subsampling, so no adjustment is needed.
+  frame = VideoFrame::CreateFrame(PIXEL_FORMAT_I444A, odd_size,
+                                  gfx::Rect(odd_size), odd_size, kTimestamp);
+  ASSERT_TRUE(frame);
+  EXPECT_EQ(odd_size, frame->coded_size());
+  for (int plane = 0; plane < 4; plane++) {
+    EXPECT_EQ(frame->GetVisibleRowBytes(plane), frame->row_bytes(plane));
+    EXPECT_EQ(frame->GetVisibleRows(plane), frame->rows(plane));
+  }
+}
+
+TEST(VideoFrame, RowBytes) {
+  constexpr gfx::Size kCodedSize(16, 14);
+  constexpr gfx::Rect kVisibleRect(4, 4, 8, 8);
+
+  // Just spot test I420A for simplicity.
+  scoped_refptr<VideoFrame> frame =
+      VideoFrame::CreateFrame(PIXEL_FORMAT_I420A, kCodedSize, kVisibleRect,
+                              kVisibleRect.size(), base::TimeDelta());
+  ASSERT_TRUE(frame);
+  ASSERT_EQ(frame->row_bytes(VideoFrame::Plane::kY), kCodedSize.width());
+  ASSERT_EQ(frame->row_bytes(VideoFrame::Plane::kU), kCodedSize.width() / 2);
+  ASSERT_EQ(frame->row_bytes(VideoFrame::Plane::kV), kCodedSize.width() / 2);
+  ASSERT_EQ(frame->row_bytes(VideoFrame::Plane::kA), kCodedSize.width());
+  ASSERT_EQ(frame->rows(VideoFrame::Plane::kY), kCodedSize.height());
+  ASSERT_EQ(frame->rows(VideoFrame::Plane::kU), kCodedSize.height() / 2);
+  ASSERT_EQ(frame->rows(VideoFrame::Plane::kV), kCodedSize.height() / 2);
+  ASSERT_EQ(frame->rows(VideoFrame::Plane::kA), kCodedSize.height());
+
+  ASSERT_EQ(frame->GetVisibleRowBytes(VideoFrame::Plane::kY),
+            kVisibleRect.width());
+  ASSERT_EQ(frame->GetVisibleRowBytes(VideoFrame::Plane::kU),
+            kVisibleRect.width() / 2);
+  ASSERT_EQ(frame->GetVisibleRowBytes(VideoFrame::Plane::kV),
+            kVisibleRect.width() / 2);
+  ASSERT_EQ(frame->GetVisibleRowBytes(VideoFrame::Plane::kA),
+            kVisibleRect.width());
+  ASSERT_EQ(frame->GetVisibleRows(VideoFrame::Plane::kY),
+            kVisibleRect.height());
+  ASSERT_EQ(frame->GetVisibleRows(VideoFrame::Plane::kU),
+            kVisibleRect.height() / 2);
+  ASSERT_EQ(frame->GetVisibleRows(VideoFrame::Plane::kV),
+            kVisibleRect.height() / 2);
+  ASSERT_EQ(frame->GetVisibleRows(VideoFrame::Plane::kA),
+            kVisibleRect.height());
+}
+
 TEST(VideoFrame, AllocationSize_OddSize) {
   const gfx::Size size(3, 5);
diff --git a/media/test/pipeline_integration_test.cc b/media/test/pipeline_integration_test.cc
index fb0c86b..a69a08b 100644
--- a/media/test/pipeline_integration_test.cc
+++ b/media/test/pipeline_integration_test.cc
@@ -3035,7 +3035,8 @@
   ASSERT_EQ(PIPELINE_OK, StartPipelineWithHlsManifest("hls/mp_ts_avc1.m3u8"));
   Play();
   ASSERT_TRUE(WaitUntilOnEnded());
-  EXPECT_EQ("00df0aa6796123f535402c39c20baa3fe1c2ad02fe1f015f84878977b30931d2",
+  // 320x192 video of which only 320x180 is visible.
+  EXPECT_EQ("9537d9d2592aa801cff8fceb2af9f6e3c5226df089e16f8f789d43e1fdec7ba2",
             GetVideoHash());
 }
 #endif
diff --git a/third_party/blink/renderer/modules/webcodecs/video_frame_test.cc b/third_party/blink/renderer/modules/webcodecs/video_frame_test.cc
index 7fa9293d..15030a2f 100644
--- a/third_party/blink/renderer/modules/webcodecs/video_frame_test.cc
+++ b/third_party/blink/renderer/modules/webcodecs/video_frame_test.cc
@@ -18,6 +18,7 @@
 #include "third_party/blink/renderer/bindings/modules/v8/v8_union_blob_htmlcanvaselement_htmlimageelement_htmlvideoelement_imagebitmap_imagedata_offscreencanvas_svgimageelement_videoframe.h"
 #include "third_party/blink/renderer/bindings/modules/v8/v8_union_cssimagevalue_htmlcanvaselement_htmlimageelement_htmlvideoelement_imagebitmap_offscreencanvas_svgimageelement_videoframe.h"
 #include "third_party/blink/renderer/bindings/modules/v8/v8_video_decoder_config.h"
+#include "third_party/blink/renderer/bindings/modules/v8/v8_video_frame_buffer_init.h"
 #include "third_party/blink/renderer/bindings/modules/v8/v8_video_frame_copy_to_options.h"
 #include "third_party/blink/renderer/bindings/modules/v8/v8_video_frame_init.h"
 #include "third_party/blink/renderer/bindings/modules/v8/v8_video_frame_metadata.h"
@@ -114,6 +115,78 @@
   EXPECT_EQ(nullptr, blink_frame->frame());
 }
 
+TEST_F(VideoFrameTest, ConstructorOddSize) {
+  V8TestingScope scope;
+
+  constexpr auto kOddSize = gfx::Size(61, 21);
+  const auto kOddUVSize = gfx::Size(std::ceil(kOddSize.width() / 2.0),
+                                    std::ceil(kOddSize.height() / 2.0));
+  const size_t allocation_size =
+      kOddSize.Area64() * 2 + kOddUVSize.Area64() * 2;
+
+  auto* array_buffer = DOMArrayBuffer::Create(allocation_size, 1);
+
+  // Fill buffer with random data for hash and extents testing.
+  base::RandBytes(array_buffer->ByteSpan());
+
+  std::string media_frame_hash;
+  {
+    const size_t kYAPlaneByteSize = kOddSize.Area64();
+    const size_t kUVPlaneByteSize = kOddUVSize.Area64();
+    auto src_media_frame = media::VideoFrame::WrapExternalYuvaData(
+        media::PIXEL_FORMAT_I420A, kOddSize, gfx::Rect(kOddSize), kOddSize,
+        kOddSize.width(), kOddUVSize.width(), kOddUVSize.width(),
+        kOddSize.width(), array_buffer->ByteSpan().first(kYAPlaneByteSize),
+        array_buffer->ByteSpan().subspan(kYAPlaneByteSize, kUVPlaneByteSize),
+        array_buffer->ByteSpan().subspan(kYAPlaneByteSize + kUVPlaneByteSize,
+                                         kUVPlaneByteSize),
+        array_buffer->ByteSpan().subspan(
+            kYAPlaneByteSize + kUVPlaneByteSize * 2, kYAPlaneByteSize),
+        base::TimeDelta());
+    ASSERT_TRUE(src_media_frame);
+    media_frame_hash =
+        media::VideoFrame::HexHashOfFrameForTesting(*src_media_frame);
+  }
+
+  auto* init = VideoFrameBufferInit::Create();
+  init->setTimestamp(0);
+  init->setCodedWidth(kOddSize.width());
+  init->setCodedHeight(kOddSize.height());
+  init->setFormat("I420A");
+  init->setDisplayWidth(kOddSize.width());
+  init->setDisplayHeight(kOddSize.height());
+
+  // Test non-transfer constructor first then the transfer constructor.
+  for (bool test_transfer : {false, true}) {
+    SCOPED_TRACE(test_transfer);
+    if (test_transfer) {
+      HeapVector<Member<DOMArrayBuffer>> transfer;
+      transfer.push_back(Member<DOMArrayBuffer>(array_buffer));
+      init->setTransfer(std::move(transfer));
+    }
+
+    VideoFrame* blink_frame = VideoFrame::Create(
+        scope.GetScriptState(),
+        MakeGarbageCollected<V8AllowSharedBufferSource>(array_buffer), init,
+        scope.GetExceptionState());
+    ASSERT_TRUE(blink_frame);
+
+    EXPECT_LE(static_cast<unsigned>(kOddSize.width()),
+              blink_frame->codedWidth());
+    EXPECT_LE(static_cast<unsigned>(kOddSize.height()),
+              blink_frame->codedHeight());
+    EXPECT_EQ(static_cast<unsigned>(kOddSize.width()),
+              blink_frame->displayWidth());
+    EXPECT_EQ(static_cast<unsigned>(kOddSize.height()),
+              blink_frame->displayHeight());
+
+    auto blink_media_frame = blink_frame->frame();
+    EXPECT_EQ(media_frame_hash,
+              media::VideoFrame::HexHashOfFrameForTesting(*blink_media_frame));
+    blink_frame->close();
+  }
+}
+
 TEST_F(VideoFrameTest, CopyToRGB) {
   V8TestingScope scope;
Loading diff…

Original Bug Report

reported by ko...@gmail.com

WebCodecs VideoFrame constructor crashes browser when non-even width and height are used

Steps to reproduce the problem

Constructing a VideoFrame from array buffer source with non-even width and height crashes the browser. If only one of width and height is non-even it works. Happens only on non-gpu accelerated ARM linux, e.g. AWS Graviton.

This code:

const width = 1139; const height = 641; const buffer = new Uint8Array(2widthheight + 2 * (Math.ceil(width / 2) * Math.ceil(height / 2))); const i420aFrame = new VideoFrame(buffer, { format: ‘I420A’, codedWidth: width, codedHeight: height, displayWidth: width, displayHeight: height, timestamp: 0, });

Potentially reproducible when running Chrome without GPU accelerated video decode on arm linux. Easiest to reproduce with puppeteer on arm linux, try opening this page: https://cache-ssl.celtra.io/api/blobs/3cf5aa9adedb3b14054869474264e73c004c1bbd472aa8ce052abd76fddbaf8a/reproduction.html. It’s the JS code from above, hosted on https so that WebCodecs API is available.

Puppeteer script for reproduction: https://gist.github.com/jkosir/3134cbe9c95df268ebbcda3c9475f03b . Must be run on arm linux, also note that puppeteer install does not support arm linux and chrome binary must be installed with system package manager or obtained in other way.

Problem Description

Constructing a VideoFrame from array buffer source with non-even width and height crashes the browser. If only one of width and height is non-even it works. Happens only on non-gpu accelerated ARM linux, e.g. AWS Graviton. Works as expected in 134.0.6998.165 (and some alter versions, was not able to bisect), reproducible in 138.0.7204.100 and above stable versions.

Stacktrace on crash: Received signal 11 SEGV_ACCERR fbec001c2000 #0 0xbc3885f311e8 base::debug::CollectStackTrace() #1 0xbc3885f20bc0 base::debug::StackTrace::StackTrace() #2 0xbc3885f31094 base::debug::(anonymous namespace)::StackDumpSignalHandler() #3 0xfd2981cfc8f8 ([vdso]+0x8f7) #4 0xbc38868bf3bc CopyRow_NEON #5 0xbc38868a41b0 CopyRow_Any_NEON #6 0xbc388689e8a0 CopyPlane #7 0xbc388987d778 blink::VideoFrame::Create() #8 0xbc38892bab8c blink::(anonymous namespace)::v8_video_frame::ConstructorOverload2() #9 0xbc38839d01ac v8::internal::FunctionCallbackArguments::CallOrConstruct() #10 0xbc38839cfcb0 v8::internal::(anonymous namespace)::HandleApiCallHelper<>() #11 0xbc38839cf5fc v8::internal::Builtin_HandleApiConstruct() #12 0xbc38f7eb30e4 <unknown> [end of stack trace]

I suspect it has something to do with this change in video_frame.cc: https://github.com/chromium/chromium/commit/a4a5b472dbe783f4a668916c28d0aa6c56680ce9. Tried building chromium with older libyuv (that implements the NEON copy) version, as used in 134.0.6998.165, same issue persists.

Summary

WebCodecs VideoFrame constructor crashes browser when non-even width and height are used

Additional Data

Category: JavaScript
Chrome Channel: Stable
Regression: Yes \

View on issue tracker