CVE-2026-5884
Overview
Changed Functions
| Function | Change | Notes |
|---|---|---|
formedia/base/video_frame_layout.cc |
modified | |
ifmedia/base/video_frame_layout.cc |
modified | |
formedia/mojo/mojom/video_frame_mojom_traits.cc |
modified | |
TEST_Fmedia/mojo/mojom/video_frame_mojom_traits_unittest.cc |
modified | |
formedia/mojo/mojom/video_frame_mojom_traits_unittest.cc |
modified |
Files Changed
media/base/video_frame_layout.ccmedia/base/video_frame_layout_unittest.ccmedia/mojo/mojom/media_types.mojommedia/mojo/mojom/video_frame_mojom_traits.ccmedia/mojo/mojom/video_frame_mojom_traits_unittest.cc
Patch
From d0a80fe50c1e778fdb9e2d5283e7e5ad193bffcd Mon Sep 17 00:00:00 2001
From: Eugene Zemtsov <eugene@chromium.org>
Date: Mon, 02 Mar 2026 17:17:06 -0800
Subject: [PATCH] media: Prevent passing of frames with absurdly large strides via Mojo
- Make strides unsigned in mojo, as they are in VideoFrame
- Validate that each plane's footprint (offset + stride * rows)
fits in the buffer.
- Remove support for interleaved planes. IMC4 pixel format.
Bug: 484547633, 378046071
Change-Id: I8e5dbebddc434041bd7c31c2b16c2b5963314061
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/7614704
Reviewed-by: Xiaohan Wang <xhwang@chromium.org>
Reviewed-by: Dale Curtis <dalecurtis@chromium.org>
Commit-Queue: Eugene Zemtsov <eugene@chromium.org>
Reviewed-by: Matthew Denton <mpdenton@chromium.org>
Cr-Commit-Position: refs/heads/main@{#1592880}
---
diff --git a/media/base/video_frame_layout.cc b/media/base/video_frame_layout.cc
index 5120fc36..3d6ff38f 100644
--- a/media/base/video_frame_layout.cc
+++ b/media/base/video_frame_layout.cc
@@ -190,7 +190,8 @@
return false;
}
- for (const auto& plane : planes_) {
+ for (size_t plane_idx = 0; plane_idx < planes_.size(); ++plane_idx) {
+ const auto& plane = planes_[plane_idx];
if (plane.offset > data_size || plane.size > data_size) {
return false;
}
@@ -201,6 +202,14 @@
if (!plane_end.IsValid() || plane_end.ValueOrDie() > data_size) {
return false;
}
+
+ size_t rows = VideoFrame::Rows(plane_idx, format_, coded_size_.height());
+ // Offset + stride * rows: furthermost byte that can be reasonably read
+ // during copying or conversion of the plane.
+ auto read_end = base::CheckMul(plane.stride, rows) + plane.offset;
+ if (!read_end.IsValid() || read_end.ValueOrDie() > data_size) {
+ return false;
+ }
}
return true;
diff --git a/media/base/video_frame_layout_unittest.cc b/media/base/video_frame_layout_unittest.cc
index 7b83496..e4351ab 100644
--- a/media/base/video_frame_layout_unittest.cc
+++ b/media/base/video_frame_layout_unittest.cc
@@ -318,8 +318,8 @@
auto coded_size = gfx::Size(320, 180);
std::vector<size_t> strides = {384, 192, 192};
- std::vector<size_t> offsets = {0, 200, 300};
- std::vector<size_t> sizes = {200, 100, 100};
+ std::vector<size_t> offsets = {0, 70000, 90000};
+ std::vector<size_t> sizes = {70000, 20000, 20000};
std::vector<ColorPlaneLayout> planes(strides.size());
for (size_t i = 0; i < strides.size(); i++) {
planes[i].stride = strides[i];
diff --git a/media/mojo/mojom/media_types.mojom b/media/mojo/mojom/media_types.mojom
index d5e2654..1b88d88 100644
--- a/media/mojo/mojom/media_types.mojom
+++ b/media/mojo/mojom/media_types.mojom
@@ -488,7 +488,7 @@
// Stride and offsets for each plane. Offsets are relative to the start
// of |frame_data|.
- array<int32> strides;
+ array<uint32> strides;
array<uint32> offsets;
};
diff --git a/media/mojo/mojom/video_frame_mojom_traits.cc b/media/mojo/mojom/video_frame_mojom_traits.cc
index 058a12b4..e97b860 100644
--- a/media/mojo/mojom/video_frame_mojom_traits.cc
+++ b/media/mojo/mojom/video_frame_mojom_traits.cc
@@ -39,7 +39,7 @@
base::ReadOnlySharedMemoryRegion CreateRegion(const media::VideoFrame& frame,
std::vector<uint32_t>& offsets,
- std::vector<int32_t>& strides) {
+ std::vector<uint32_t>& strides) {
TRACE_EVENT0("media", "VideoFrameDataPtr::CreateRegion");
size_t num_planes = media::VideoFrame::NumPlanes(frame.format());
DCHECK_LE(num_planes, 3u);
@@ -105,7 +105,7 @@
input->storage_type() == media::VideoFrame::STORAGE_UNOWNED_MEMORY ||
input->storage_type() == media::VideoFrame::STORAGE_OWNED_MEMORY) {
std::vector<uint32_t> offsets;
- std::vector<int32_t> strides;
+ std::vector<uint32_t> strides;
auto region = CreateRegion(*input, offsets, strides);
if (!region.IsValid()) {
DLOG(ERROR) << "Failed to create region from VideoFrame";
@@ -272,7 +272,7 @@
mojo::ArrayDataView<uint32_t> offsets;
shared_memory_data.GetOffsetsDataView(&offsets);
- mojo::ArrayDataView<int32_t> strides;
+ mojo::ArrayDataView<uint32_t> strides;
shared_memory_data.GetStridesDataView(&strides);
base::ReadOnlySharedMemoryMapping mapping = region.Map();
@@ -288,7 +288,6 @@
}
auto mapped_region = mapping.GetMemoryAsSpan<uint8_t>();
- std::array<base::span<const uint8_t>, 3> plane_data;
std::vector<media::ColorPlaneLayout> planes(num_planes);
for (size_t i = 0; i < num_planes; i++) {
if (offsets[i] > mapped_region.size()) {
@@ -300,16 +299,8 @@
planes[i].stride = strides[i];
planes[i].offset = base::strict_cast<size_t>(offsets[i]);
- const size_t space_till_mapping_end = mapping.size() - offsets[i];
- const size_t calculated_plane_size =
+ planes[i].size =
media::VideoFrame::Rows(i, format, coded_size.height()) * strides[i];
-
- // TODO(crbug.com/378046071) For H.264 content Widevine outputs planes
- // in IMC4 pixel format. Since Y and V planes in IMC4 overlap,
- // the distance to the next plane can't be used to determent the size of
- // the current plane.
- planes[i].size = std::min(calculated_plane_size, space_till_mapping_end);
- plane_data[i] = mapped_region.subspan(offsets[i], planes[i].size);
}
auto layout = media::VideoFrameLayout::CreateWithPlanes(format, coded_size,
@@ -319,6 +310,12 @@
return false;
}
+ std::array<base::span<const uint8_t>, 3> plane_data;
+ for (size_t i = 0; i < num_planes; i++) {
+ plane_data[i] = mapped_region.subspan(layout->planes()[i].offset,
+ layout->planes()[i].size);
+ }
+
if (media::IsYuvPlanar(format) && media::IsOpaque(format)) {
frame = media::VideoFrame::WrapExternalYuvDataWithLayout(
*layout, visible_rect, natural_size, plane_data[0], plane_data[1],
diff --git a/media/mojo/mojom/video_frame_mojom_traits_unittest.cc b/media/mojo/mojom/video_frame_mojom_traits_unittest.cc
index b5c92fd..755fade 100644
--- a/media/mojo/mojom/video_frame_mojom_traits_unittest.cc
+++ b/media/mojo/mojom/video_frame_mojom_traits_unittest.cc
@@ -195,79 +195,6 @@
}
}
-TEST_F(VideoFrameStructTraitsTest, InterleavedPlanes) {
- constexpr VideoFrame::StorageType storage_type = VideoFrame::STORAGE_SHMEM;
- constexpr VideoPixelFormat format = PIXEL_FORMAT_I420;
- constexpr gfx::Size kCodedSize(100, 100);
- constexpr gfx::Rect kVisibleRect(kCodedSize);
- constexpr gfx::Size kNaturalSize = kCodedSize;
- constexpr base::TimeDelta kTimestamp;
-
- scoped_refptr<media::VideoFrame> frame;
-
- std::vector<size_t> strides = VideoFrame::ComputeStrides(format, kCodedSize);
- ASSERT_EQ(strides[1], strides[2]);
-
- size_t aggregate_size = 0;
- std::array<size_t, 3> sizes = {};
- for (size_t i = 0; i < strides.size(); ++i) {
- sizes[i] =
- media::VideoFrame::Rows(i, format, kCodedSize.height()) * strides[i];
- aggregate_size += sizes[i];
- }
- auto region = base::WritableSharedMemoryRegion::Create(aggregate_size);
- ASSERT_TRUE(region.IsValid());
- auto mapping = region.MapAt(0, aggregate_size);
-
- auto [y_plane, uv_plane] =
- mapping.GetMemoryAsSpan<uint8_t>().split_at(sizes[0]);
- std::ranges::fill(y_plane, 1);
-
- // Setup memory layout where U and V planes occupy the same space, but have
- // interleaving U and V rows. This is achieved by doubling the stride.
- size_t normal_stride = strides[1];
- size_t uv_stride = normal_stride * 2;
-
- int yu_rows = media::VideoFrame::Rows(1, format, kCodedSize.height());
- auto uv_plane2 = uv_plane; // Loop below is destructive.
- for (int i = 0; i < yu_rows; ++i) {
- const auto [u, v] = uv_plane2.take_first(uv_stride).split_at(normal_stride);
- std::ranges::fill(u, 2);
- std::ranges::fill(v, 3);
- }
-
- frame = media::VideoFrame::WrapExternalYuvData(
- format, kCodedSize, kVisibleRect, kNaturalSize, strides[0], uv_stride,
- uv_stride, y_plane, uv_plane, uv_plane.subspan(normal_stride),
- kTimestamp);
Regression Test / PoC
diff --git a/media/base/video_frame_layout_unittest.cc b/media/base/video_frame_layout_unittest.cc
index 7b83496..e4351ab 100644
--- a/media/base/video_frame_layout_unittest.cc
+++ b/media/base/video_frame_layout_unittest.cc
@@ -318,8 +318,8 @@
auto coded_size = gfx::Size(320, 180);
std::vector<size_t> strides = {384, 192, 192};
- std::vector<size_t> offsets = {0, 200, 300};
- std::vector<size_t> sizes = {200, 100, 100};
+ std::vector<size_t> offsets = {0, 70000, 90000};
+ std::vector<size_t> sizes = {70000, 20000, 20000};
std::vector<ColorPlaneLayout> planes(strides.size());
for (size_t i = 0; i < strides.size(); i++) {
planes[i].stride = strides[i];
diff --git a/media/mojo/mojom/video_frame_mojom_traits_unittest.cc b/media/mojo/mojom/video_frame_mojom_traits_unittest.cc
index b5c92fd..755fade 100644
--- a/media/mojo/mojom/video_frame_mojom_traits_unittest.cc
+++ b/media/mojo/mojom/video_frame_mojom_traits_unittest.cc
@@ -195,79 +195,6 @@
}
}
-TEST_F(VideoFrameStructTraitsTest, InterleavedPlanes) {
- constexpr VideoFrame::StorageType storage_type = VideoFrame::STORAGE_SHMEM;
- constexpr VideoPixelFormat format = PIXEL_FORMAT_I420;
- constexpr gfx::Size kCodedSize(100, 100);
- constexpr gfx::Rect kVisibleRect(kCodedSize);
- constexpr gfx::Size kNaturalSize = kCodedSize;
- constexpr base::TimeDelta kTimestamp;
-
- scoped_refptr<media::VideoFrame> frame;
-
- std::vector<size_t> strides = VideoFrame::ComputeStrides(format, kCodedSize);
- ASSERT_EQ(strides[1], strides[2]);
-
- size_t aggregate_size = 0;
- std::array<size_t, 3> sizes = {};
- for (size_t i = 0; i < strides.size(); ++i) {
- sizes[i] =
- media::VideoFrame::Rows(i, format, kCodedSize.height()) * strides[i];
- aggregate_size += sizes[i];
- }
- auto region = base::WritableSharedMemoryRegion::Create(aggregate_size);
- ASSERT_TRUE(region.IsValid());
- auto mapping = region.MapAt(0, aggregate_size);
-
- auto [y_plane, uv_plane] =
- mapping.GetMemoryAsSpan<uint8_t>().split_at(sizes[0]);
- std::ranges::fill(y_plane, 1);
-
- // Setup memory layout where U and V planes occupy the same space, but have
- // interleaving U and V rows. This is achieved by doubling the stride.
- size_t normal_stride = strides[1];
- size_t uv_stride = normal_stride * 2;
-
- int yu_rows = media::VideoFrame::Rows(1, format, kCodedSize.height());
- auto uv_plane2 = uv_plane; // Loop below is destructive.
- for (int i = 0; i < yu_rows; ++i) {
- const auto [u, v] = uv_plane2.take_first(uv_stride).split_at(normal_stride);
- std::ranges::fill(u, 2);
- std::ranges::fill(v, 3);
- }
-
- frame = media::VideoFrame::WrapExternalYuvData(
- format, kCodedSize, kVisibleRect, kNaturalSize, strides[0], uv_stride,
- uv_stride, y_plane, uv_plane, uv_plane.subspan(normal_stride),
- kTimestamp);
- auto ro_region =
- base::WritableSharedMemoryRegion::ConvertToReadOnly(std::move(region));
- frame->BackWithSharedMemory(&ro_region);
-
- EXPECT_TRUE(frame);
- EXPECT_EQ(frame->storage_type(), storage_type);
- EXPECT_TRUE(RoundTrip(&frame));
- EXPECT_TRUE(frame);
- EXPECT_EQ(frame->format(), format);
- EXPECT_EQ(frame->coded_size(), kCodedSize);
-
- auto plane_1 = frame->GetVisiblePlaneData(1);
- auto plane_2 = frame->GetVisiblePlaneData(2);
- // Bytes between the visible edge and the full stride are not considered part
- // of the visible plane, and may not be accessible through the above spans.
- const size_t row_bytes_1 =
- VideoFrame::RowBytes(1, format, kCodedSize.width());
- const size_t row_bytes_2 =
- VideoFrame::RowBytes(2, format, kCodedSize.width());
- for (int i = 0; i < yu_rows; ++i) {
- const auto [u, v] = uv_plane.take_first(uv_stride).split_at(normal_stride);
- EXPECT_EQ(plane_1.subspan(i * frame->stride(1), row_bytes_1),
- u.first(row_bytes_1));
- EXPECT_EQ(plane_2.subspan(i * frame->stride(2), row_bytes_2),
- v.first(row_bytes_2));
- }
-}
-
TEST_F(VideoFrameStructTraitsTest, InvalidOffsets) {
constexpr auto kFormat = PIXEL_FORMAT_I420;
Original Bug Report
VideoFrame Mojo deserialization accepts negative stride β OOB read in video encoders
Steps to reproduce the problem
== Environment == Tested on: Chromium trunk (Linux x64), ASAN build Affected platforms: All (Linux, macOS, Windows, ChromeOS, Android) The vulnerable code is platform-independent.
== Build Configuration (ASAN) == is_asan = true is_debug = false is_component_build = false target_cpu = “x64”
== Reproduction via ASAN Unit Test ==
The vulnerability is in the Mojo deserialization of SharedMemory-backed VideoFrames. A unit test that exercises the real deserialization code path with a crafted negative stride value triggers heap-buffer-overflow under ASAN.
Steps:
-
Build media_unittests with ASAN: autoninja -C out/ASAN media_unittests
-
Run the NegativeStride test: ASAN_OPTIONS=“detect_odr_violation=0:halt_on_error=1”
out/ASAN/media_unittests
–gtest_filter="NegativeStride"
–single-process-tests -
Observe ASAN heap-buffer-overflow report.
== Reproduction via MojoJS PoC (requires –enable-blink-features=MojoJS) ==
-
Apply the attached poc_patch.diff to add a stride probe after deserialization (needed because headless Linux has no GPU encoder backend; the probe simulates the same memory access pattern as VpxVideoEncoder).
-
Serve poc_negative_stride.html on a local HTTP server: python3 -m http.server 8787
-
Run Chrome with ASAN: ./out/ASAN/chrome
–enable-blink-features=MojoJS,MojoJSTest
–no-sandbox –disable-gpu-sandbox
–headless=new –ozone-platform=headless –disable-gpu
–use-fake-device-for-media-stream
–enable-features=UseOutOfProcessVideoEncoding
–enable-logging=stderr
“http://127.0.0.1:8787/poc_negative_stride.html” -
Observe SEGV_MAPERR crash at the deserialization site.
== What the PoC does == The PoC uses MojoJS to call VideoEncodeAccelerator.Encode() with a VideoFrame whose strides[] are set to -1 (0xFFFFFFFF as int32_t). The frame is sent via Mojo IPC and deserialized in the browser/GPU process. The negative stride passes all existing validation checks and produces a VideoFrame with stride = SIZE_MAX, which causes backward out-of-bounds reads when any consumer (VPX encoder, AV1 encoder, libyuv, etc.) iterates over pixel rows.
Problem Description
== Summary == media/mojo/mojom/video_frame_mojom_traits.cc:301 assigns an int32_t stride value from Mojo IPC directly to a size_t field without checking for negative values. A compromised renderer can send stride = -1, which becomes SIZE_MAX (0xFFFFFFFFFFFFFFFF) after implicit sign extension. Downstream consumers (video encoders, libyuv) truncate this to int(-1) and use it for pointer arithmetic, causing backward out-of-bounds heap reads.
== Root Cause == In StructTraits<VideoFrameDataView>::Read(), SharedMemory path:
planes[i].stride = strides[i]; // int32_t β size_t, no negative check
The existing FitsInContiguousBufferOfSize() check does NOT validate stride. It only checks (plane.offset + plane.size <= data_size). The plane.size is computed as: Rows(i, format, height) * strides[i] When strides[i] = -1, this multiplies size_t * int32_t(-1), causing integer overflow that wraps to a small value. std::min() then clamps it further. So plane.size and plane.offset are both reasonable β the check passes. But planes[i].stride = SIZE_MAX is never validated.
Notably, the DMA-buf deserialization path in the SAME FILE (line ~196) correctly uses base::IsValueInRangeForNumericType<size_t>(data.stride()) to reject negative values. The SharedMemory path is missing this check β this is clearly an oversight.
== Affected Consumers == Any code that calls VideoFrame::stride() and uses it for pixel row traversal:
- VpxVideoEncoder (vpx_video_encoder.cc:306-308): stride[VPX_PLANE_Y] = frame.stride(kY) β vpx_image_t::stride is int[4], SIZE_MAX truncates to -1 β libvpx OOB
- Av1VideoEncoder (av1_video_encoder.cc): same pattern with aom_image_t
- OpenH264VideoEncoder: same pattern
- libyuv color conversion: stride passed as int parameter
- WebMediaPlayerMSCompositor: stride used for pixel copy
== Security Impact ==
- Type: Out-of-bounds read (heap-buffer-overflow / heap underflow)
- Attack surface: Compromised renderer β browser/GPU process via Mojo IPC
- Threat model: Standard Chrome “compromised renderer” model. A renderer exploit (e.g., V8 bug) can craft arbitrary Mojo messages including negative strides.
- Impact: Cross-process information disclosure (reading heap data before the VideoFrame buffer) or process crash (SEGV on unmapped page).
- No MojoJS required for real exploitation β MojoJS is used only for PoC convenience.
== ASAN Crash == heap-buffer-overflow on address 0x7dc7994ef1ff READ of size 1 at 0x7dc7994ef1ff thread T0 0x7dc7994ef1ff is located 1 bytes before 19072-byte region [0x7dc7994ef200,0x7dc7994f3c80)
== Suggested Fix == Add a negative stride check before the assignment: if (strides[i] < 0) { DLOG(ERROR) << “Negative stride at plane " << i; return false; } planes[i].stride = static_cast<size_t>(strides[i]);
This matches the existing DMA-buf path validation pattern.
Additional Comments
== Environment == Chromium commit: b3acbdcb7bbe7eb076ed9509d3c5f2e10587a5b7 (2026-02-12, trunk/main branch) Build configs: Linux x64, both Default (non-ASAN) and ASAN builds (see args.gn details in attached bug_report.md Β§8)
== Why a PoC patch is needed ==
The PoC patch (poc_patch.diff) adds a ~30-line read-only probe to video_frame_mojom_traits.cc, immediately after the deserialized VideoFrame is created. This patch is needed because:
-
Our test server is headless Linux with no GPU. The natural consumer of the poisoned VideoFrame is VpxVideoEncoder (or AV1/OpenH264/VideoToolbox encoders), but VideoEncodeAccelerator requires a hardware backend (VAAPI/V4L2/VideoToolbox) to initialize. On our server, VEA.Initialize() fails β Encode() is never called β the poisoned frame is never consumed.
-
The vulnerability itself (int32_t β size_t assignment at line 301) is fully exercised WITHOUT the patch. The deserialization succeeds, FitsInContiguousBufferOfSize() is bypassed (integer overflow), and the VideoFrame is created with stride = SIZE_MAX. The patch merely performs the same memory access that the encoder would perform, to trigger ASAN detection in our GPU-less environment.
-
On macOS or any machine with a GPU, the patch is unnecessary β the real VPX/AV1/VideoToolbox encoder will consume the frame and crash naturally.
== What the patch simulates and why it’s a valid reproduction ==
The patch mirrors the exact code path in vpx_video_encoder.cc:
Patch code VpxVideoEncoder real code ββββββββββββββββββββββββββββββββββ ββββββββββββββββββββββββββββββββββ y_plane = frame->visible_data(kY) planes[Y] = frame.visible_data(kY) (line 301) y_stride = frame->stride(kY) stride[Y] = frame.stride(kY) (line 307) *(y_plane + y_stride) libvpx: planes[Y] + row * stride[Y] (vpx_codec_encode)
The type conversion chain is identical: frame->stride(kY) returns size_t = SIZE_MAX β assigned to int variable β truncated to -1 β pointer arithmetic: y_plane + (-1) β backward OOB read
== Why the OOB access is reachable without any security checks in between ==
From the deserialization point (line 301) to the consumer access, there are NO intervening security checks on the stride value:
- Line 301: planes[i].stride = strides[i] β int32(-1) β size_t(SIZE_MAX), no check
- Line 306: FitsInContiguousBufferOfSize() β checks (offset + size β€ buffer_size) only. The “size” is computed as Rows * strides[i], which overflows (size_t * int32_t(-1) wraps), then std::min() clamps it to a small value. The stride field itself is NEVER validated.
- Line 334: frame->BackWithOwnedSharedMemory() β stores the mapping, no stride check
- VideoFrame::stride() β a simple getter, returns planes_[i].stride directly
- VpxVideoEncoder::SetupStandardYuvPlanes() β copies stride to vpx_image_t::stride (int), no range check
- vpx_codec_encode() β uses stride for row traversal, no bounds check
The DMA-buf deserialization path in the SAME file (line ~196) correctly validates: if (!base::IsValueInRangeForNumericType<size_t>(data.stride())) return false; The SharedMemory path is missing this check β clearly an oversight.
== Attached files ==
- poc_negative_stride.html β MojoJS PoC (sends negative stride via VEA Mojo)
- poc_patch.diff β Read-only probe patch for GPU-less reproduction
- poc_patch_report.md β Detailed analysis report
- bug_report_VideoFrame_NegativeStride.md β Full technical report with crash logs
- asan_unittest_crash.log β Complete ASAN unit test output
- fix_negative_stride.diff β Suggested fix (add negative stride check)
Summary
VideoFrame Mojo deserialization accepts negative stride β OOB read in video encoders
Custom Questions
Type of crash:
Browser/GPU process crash (the VideoFrame deserialization and video encoding happen in the browser or GPU process, not the renderer/tab process). When UseOutOfProcessVideoEncoding is enabled, the crash occurs in the dedicated video encoding utility process. Otherwise it occurs in the GPU process. The renderer (tab) process is the attacker β it sends the malicious Mojo IPC message. The crash is cross-process.
Crash state:
== ASAN Crash (media_unittests –gtest_filter=NegativeStride) ==
==========================================================//
// ==1484417==ERROR: AddressSanitizer: heap-buffer-overflow on address 0x7dc7994ef1ff at pc 0x561e67f6deee bp 0x7ffd6ed28f90 sp 0x7ffd6ed28f88 READ of size 1 at 0x7dc7994ef1ff thread T0 #0 0x561e67f6deed in media::VideoFrameStructTraitsTest_NegativeStride_Test::TestBody() media/mojo/mojom/video_frame_mojom_traits_unittest.cc:496:17 #1 0x561e6399763b in testing::Test::Run() #2 0x561e6399a7fa in testing::TestInfo::Run()
0x7dc7994ef1ff is located 1 bytes before 19072-byte region [0x7dc7994ef200,0x7dc7994f3c80) allocated by thread T0 here: #0 0x561e60decc5d in operator new[](unsigned long) #1 0x561e67f6db23 in media::VideoFrameStructTraitsTest_NegativeStride_Test::TestBody()
SUMMARY: AddressSanitizer: heap-buffer-overflow media/mojo/mojom/video_frame_mojom_traits_unittest.cc:496:17 in media::VideoFrameStructTraitsTest_NegativeStride_Test::TestBody()
== Non-ASAN Crash (MojoJS PoC in headless Chrome) ==
Received signal 11 SEGV_MAPERR 7f0d51061fff #4 0x7f0d64589e07 mojo::StructTraits<>::Read() [../../media/mojo/mojom/video_frame_mojom_traits.cc:357:36] #5 VideoEncodeAcceleratorStubDispatch::AcceptWithResponder()
Crash address: 0x7f0d51061fff (1 byte before shared memory mapping region) Signal: SIGSEGV (SEGV_MAPERR) β access to unmapped page
== Key type conversion trace == stride input: int32_t(-1) = 0xFFFFFFFF after assign: size_t = 0xFFFFFFFFFFFFFFFF (SIZE_MAX = 18446744073709551615) encoder cast: (int)SIZE_MAX = -1 pointer math: y_plane + (-1) β 1 byte before buffer start β heap underflow
Reporter credit:
xmzyshypnc
Additional Data
Category: Security
Chrome Channel: Stable
Regression: N/A \