High chrome Integer Overflow 📄 Reporter bug report 🔧 Commit mapped

Overview

High
Severity
CVSS
No
Exploited ITW
Fixed
Fix Status
ImpactInteger overflow in Chromecast
DescriptionInteger overflow in Chromecast
ComponentChromecast
Bug ClassInteger Overflow
Tracker517519352
Fix commit55122e96e412 (chromium/src) +74/-1
CISA KEVNot listed
CreditedGoogle
Disclosed2026-08-25

Changed Functions

FunctionChangeNotes
TEST_F
chromecast/starboard/media/renderer/demuxer_stream_reader_test.cc
modified
if
chromecast/starboard/media/renderer/demuxer_stream_reader_test.cc
modified

Files Changed

  • chromecast/starboard/media/renderer/demuxer_stream_reader.cc
  • chromecast/starboard/media/renderer/demuxer_stream_reader_test.cc
From 55122e96e41286a8775dd1ae82eca0de37c92487 Mon Sep 17 00:00:00 2001
From: Richard Nichols <[email protected]>
Date: Fri, 17 Jul 2026 12:41:47 -0700
Subject: [PATCH] [starboard] Reject DecoderBuffers that exceed sample_info limits

DemuxerStreamReader::OnReadBuffer assigns DecoderBuffer::size() (a
size_t) directly into StarboardSampleInfo::buffer_size, which is an int.
For buffers of 2 GiB or more this silently truncates to a negative value
that is then handed to SbPlayerWriteSample*.

Bound-check the size with base::IsValueInRangeForNumericType and report
PIPELINE_ERROR_DECODE for anything that does not fit, and make the
remaining narrowing conversion explicit. Adds a unit test that backs a
DecoderBuffer with an mmap(MAP_NORESERVE) reservation larger than
INT_MAX and verifies that the buffer callback is not invoked and the
client receives an error.

Bug: 517519352
Change-Id: I0feac5e7ea3792708f4e8c1851a1ca5f05aafa60
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/8117188
Reviewed-by: Shawn Quereshi <[email protected]>
Commit-Queue: Richard Nichols <[email protected]>
Reviewed-by: Simeon Anfinrud <[email protected]>
Cr-Commit-Position: refs/heads/main@{#1664118}
---

diff --git a/chromecast/starboard/media/renderer/demuxer_stream_reader.cc b/chromecast/starboard/media/renderer/demuxer_stream_reader.cc
index 5334cc14..b388fd3 100644
--- a/chromecast/starboard/media/renderer/demuxer_stream_reader.cc
+++ b/chromecast/starboard/media/renderer/demuxer_stream_reader.cc
@@ -12,6 +12,7 @@
 #include "base/functional/bind.h"
 #include "base/hash/hash.h"
 #include "base/logging.h"
+#include "base/numerics/safe_conversions.h"
 #include "base/task/bind_post_task.h"
 #include "base/task/sequenced_task_runner.h"
 #include "chromecast/base/metrics/cast_metrics_helper.h"
@@ -243,10 +244,19 @@
     buffer = convert_audio_fn_.Run(std::move(buffer));
   }
 
+  // StarboardSampleInfo::buffer_size is an int, so reject anything that does
+  // not fit rather than silently truncating the value.
+  if (!base::IsValueInRangeForNumericType<int>(buffer->size())) {
+    LOG(ERROR) << "DecoderBuffer size (" << buffer->size()
+               << ") exceeds the maximum supported sample size.";
+    client_->OnError(::media::PIPELINE_ERROR_DECODE);
+    return;
+  }
+
   StarboardSampleInfo sample_info = {};
   sample_info.type = type;
   sample_info.buffer = base::span(*buffer).data();
-  sample_info.buffer_size = buffer->size();
+  sample_info.buffer_size = static_cast<int>(buffer->size());
   sample_info.timestamp = buffer->timestamp().InMicroseconds();
   sample_info.side_data = base::span<const StarboardSampleSideData>();
 
diff --git a/chromecast/starboard/media/renderer/demuxer_stream_reader_test.cc b/chromecast/starboard/media/renderer/demuxer_stream_reader_test.cc
index 15437e3..239f1614 100644
--- a/chromecast/starboard/media/renderer/demuxer_stream_reader_test.cc
+++ b/chromecast/starboard/media/renderer/demuxer_stream_reader_test.cc
@@ -4,9 +4,12 @@
 
 #include "chromecast/starboard/media/renderer/demuxer_stream_reader.h"
 
+#include <sys/mman.h>
+
 #include <array>
 #include <cstdint>
 #include <functional>
+#include <limits>
 #include <string>
 #include <string_view>
 #include <tuple>
@@ -27,6 +30,7 @@
 #include "media/base/encryption_scheme.h"
 #include "media/base/mock_filters.h"
 #include "media/base/sample_format.h"
+#include "media/base/test_helpers.h"
 #include "media/base/video_transformation.h"
 #include "testing/gmock/include/gmock/gmock.h"
 #include "testing/gtest/include/gtest/gtest.h"
@@ -180,6 +184,65 @@
   RunPendingTasks();
 }
 
+TEST_F(DemuxerStreamReaderTest, RejectsVideoBufferLargerThanIntMax) {
+  // StarboardSampleInfo::buffer_size is an int, so a DecoderBuffer whose size
+  // does not fit in an int must be reported as an error rather than being
+  // forwarded with a truncated size.
+  constexpr int kSeekTicket = 7;
+  constexpr size_t kBufferSize =
+      static_cast<size_t>(std::numeric_limits<int>::max()) + 2;
+
+  // Reserve address space without committing physical pages so that the test
+  // does not require gigabytes of RAM.
+  void* mapping = mmap(nullptr, kBufferSize, PROT_READ,
+                       MAP_PRIVATE | MAP_ANONYMOUS | MAP_NORESERVE, -1, 0);
+  if (mapping == MAP_FAILED) {
+    GTEST_SKIP() << "Unable to reserve " << kBufferSize
+                 << " bytes of address space.";
+  }
+
+  auto external_memory = std::make_unique<DecoderBuffer::UnownedExternalMemory>(
+      // SAFETY: `mapping` refers to `kBufferSize` bytes returned by mmap above
+      // and remains valid until the matching munmap below.
+      UNSAFE_BUFFERS(base::span<const uint8_t>(
+          static_cast<const uint8_t*>(mapping), kBufferSize)));
+  scoped_refptr<DecoderBuffer> buffer =
+      DecoderBuffer::FromExternalMemory(std::move(external_memory));
+  ASSERT_EQ(buffer->size(), kBufferSize);
+
+  StarboardVideoSampleInfo video_sample_info =
+      CreateVideoSample(kBufferData).video_sample_info;
+
+  EXPECT_CALL(handle_eos_cb_, Call).Times(0);
+  EXPECT_CALL(handle_buffer_cb_, Call).Times(0);
+  EXPECT_CALL(renderer_client_,
+              OnError(HasStatusCode(::media::PIPELINE_ERROR_DECODE)))
+      .Times(1);
+  base::OnceCallback<void(
+      DemuxerStream::Status status,
+      std::vector<scoped_refptr<::media::DecoderBuffer>> buffers)>
+      read_cb;
+  EXPECT_CALL(video_stream_, OnRead).WillOnce(SaveArgByMove<0>(&read_cb));
+
+  DemuxerStreamReader stream_reader(
+      /*audio_stream=*/nullptr, &video_stream_,
+      /*audio_sample_info=*/std::nullopt, video_sample_info,
+      base::BindLambdaForTesting(handle_buffer_cb_.AsStdFunction()),
+      base::BindLambdaForTesting(handle_eos_cb_.AsStdFunction()),
+      &renderer_client_, &metrics_helper_);
+  stream_reader.ReadBuffer(kSeekTicket,
+                           StarboardMediaType::kStarboardMediaTypeVideo);
+
+  // Simulate the DemuxerStream providing the oversized buffer.
+  ASSERT_FALSE(read_cb.is_null());
+  std::move(read_cb).Run(DemuxerStream::Status::kOk, {buffer});
+
+  RunPendingTasks();
+
+  buffer.reset();
+  munmap(mapping, kBufferSize);
+}
+
 TEST_F(DemuxerStreamReaderTest, ReadsVideoBufferAndCallsEosCb) {
   constexpr int kSeekTicket = 7;
   StarboardVideoSampleInfo video_sample_info = {};
Loading diff…

Regression Test / PoC

shipped with the fix
diff --git a/chromecast/starboard/media/renderer/demuxer_stream_reader_test.cc b/chromecast/starboard/media/renderer/demuxer_stream_reader_test.cc
index 15437e3..239f1614 100644
--- a/chromecast/starboard/media/renderer/demuxer_stream_reader_test.cc
+++ b/chromecast/starboard/media/renderer/demuxer_stream_reader_test.cc
@@ -4,9 +4,12 @@
 
 #include "chromecast/starboard/media/renderer/demuxer_stream_reader.h"
 
+#include <sys/mman.h>
+
 #include <array>
 #include <cstdint>
 #include <functional>
+#include <limits>
 #include <string>
 #include <string_view>
 #include <tuple>
@@ -27,6 +30,7 @@
 #include "media/base/encryption_scheme.h"
 #include "media/base/mock_filters.h"
 #include "media/base/sample_format.h"
+#include "media/base/test_helpers.h"
 #include "media/base/video_transformation.h"
 #include "testing/gmock/include/gmock/gmock.h"
 #include "testing/gtest/include/gtest/gtest.h"
@@ -180,6 +184,65 @@
   RunPendingTasks();
 }
 
+TEST_F(DemuxerStreamReaderTest, RejectsVideoBufferLargerThanIntMax) {
+  // StarboardSampleInfo::buffer_size is an int, so a DecoderBuffer whose size
+  // does not fit in an int must be reported as an error rather than being
+  // forwarded with a truncated size.
+  constexpr int kSeekTicket = 7;
+  constexpr size_t kBufferSize =
+      static_cast<size_t>(std::numeric_limits<int>::max()) + 2;
+
+  // Reserve address space without committing physical pages so that the test
+  // does not require gigabytes of RAM.
+  void* mapping = mmap(nullptr, kBufferSize, PROT_READ,
+                       MAP_PRIVATE | MAP_ANONYMOUS | MAP_NORESERVE, -1, 0);
+  if (mapping == MAP_FAILED) {
+    GTEST_SKIP() << "Unable to reserve " << kBufferSize
+                 << " bytes of address space.";
+  }
+
+  auto external_memory = std::make_unique<DecoderBuffer::UnownedExternalMemory>(
+      // SAFETY: `mapping` refers to `kBufferSize` bytes returned by mmap above
+      // and remains valid until the matching munmap below.
+      UNSAFE_BUFFERS(base::span<const uint8_t>(
+          static_cast<const uint8_t*>(mapping), kBufferSize)));
+  scoped_refptr<DecoderBuffer> buffer =
+      DecoderBuffer::FromExternalMemory(std::move(external_memory));
+  ASSERT_EQ(buffer->size(), kBufferSize);
+
+  StarboardVideoSampleInfo video_sample_info =
+      CreateVideoSample(kBufferData).video_sample_info;
+
+  EXPECT_CALL(handle_eos_cb_, Call).Times(0);
+  EXPECT_CALL(handle_buffer_cb_, Call).Times(0);
+  EXPECT_CALL(renderer_client_,
+              OnError(HasStatusCode(::media::PIPELINE_ERROR_DECODE)))
+      .Times(1);
+  base::OnceCallback<void(
+      DemuxerStream::Status status,
+      std::vector<scoped_refptr<::media::DecoderBuffer>> buffers)>
+      read_cb;
+  EXPECT_CALL(video_stream_, OnRead).WillOnce(SaveArgByMove<0>(&read_cb));
+
+  DemuxerStreamReader stream_reader(
+      /*audio_stream=*/nullptr, &video_stream_,
+      /*audio_sample_info=*/std::nullopt, video_sample_info,
+      base::BindLambdaForTesting(handle_buffer_cb_.AsStdFunction()),
+      base::BindLambdaForTesting(handle_eos_cb_.AsStdFunction()),
+      &renderer_client_, &metrics_helper_);
+  stream_reader.ReadBuffer(kSeekTicket,
+                           StarboardMediaType::kStarboardMediaTypeVideo);
+
+  // Simulate the DemuxerStream providing the oversized buffer.
+  ASSERT_FALSE(read_cb.is_null());
+  std::move(read_cb).Run(DemuxerStream::Status::kOk, {buffer});
+
+  RunPendingTasks();
+
+  buffer.reset();
+  munmap(mapping, kBufferSize);
+}
+
 TEST_F(DemuxerStreamReaderTest, ReadsVideoBufferAndCallsEosCb) {
   constexpr int kSeekTicket = 7;
   StarboardVideoSampleInfo video_sample_info = {};
Loading diff…

Original Bug Report

reported by [email protected]

Potential integer narrowing of DecoderBuffer size in Starboard DemuxerStreamReader

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. Please see https://chromium.googlesource.com/chromium/src/+/main/docs/security/ai-generated-security-bugs-faq.md for more information.

Overview: In Chromecast/Starboard builds, DemuxerStreamReader assigns a size_t buffer size to a signed 32-bit int. If a compromised renderer provides a buffer size exceeding 2 GiB, this value narrows to a negative integer. The negative size is then passed with a valid heap pointer to proprietary Starboard player APIs in the browser process.

Affected files:

  • chromecast/starboard/media/renderer/demuxer_stream_reader.cc
  • chromecast/starboard/media/media/starboard_api_wrapper.h
  • chromecast/starboard/media/media/starboard_api_wrapper_base.cc

Estimated timestamp from git blame: 2025-06-05

Potential Vulnerability Details

In Chromecast/Starboard media playback configurations, the StarboardRenderer runs inside the browser process’s in-process MediaService. A potential integer narrowing defect exists in DemuxerStreamReader::OnReadBuffer where a size_t value is implicitly cast into a signed int when copying to StarboardSampleInfo.

In chromecast/starboard/media/renderer/demuxer_stream_reader.cc:

StarboardSampleInfo sample_info = {};
sample_info.type = type;
sample_info.buffer = base::span(*buffer).data();
sample_info.buffer_size = buffer->size();  // Narrowing from size_t to int!

In chromecast/starboard/media/media/starboard_api_wrapper.h, the StarboardSampleInfo struct is defined as:

struct StarboardSampleInfo {
  int type;
  const void* buffer;
  int buffer_size; // Signed int
  ...
};

If a compromised renderer sends a Mojo DataDecoderBuffer with a data_size of 0x80000001 (approx. 2 GiB) or larger, this size is successfully promoted to size_t during conversion/deserialization. However, when assigned to sample_info.buffer_size, the implicit narrowing to a signed int results in a negative value (e.g., -2147483647). This negative value is then forwarded verbatim to SbPlayerWriteSample2 or SbPlayerWriteSamples in the closed-source proprietary platform/vendor library (libcast_starboard_api.so).

Potential Attack Steps

  1. From a compromised renderer, bind the browser-side media.mojom.Renderer (StarboardRenderer).
  2. Supply a renderer-hosted mojom::DemuxerStream during initialization.
  3. Reply to a DemuxerStream::Read request with a mojom::DecoderBuffer whose DataDecoderBuffer.data_size is in the range [0x80000000, 0xFFFFFFFF].
  4. Stream the data through the data pipe to complete the read in the browser.
  5. DemuxerStreamReader::OnReadBuffer narrows the size_t to a negative int, which is then sent to the vendor Starboard APIs.

Real-world Mitigations & Practical Exploitability

Note: Our security analysis tools do not have the capability to run code or execute a live proof of concept on physical hardware. These steps are theoretical.

In practice, this bug is highly unlikely to be exploitable due to physical memory constraints:

  • Allocating a contiguous 2 GiB buffer via base::HeapArray<uint8_t>::Uninit on typical 32-bit or memory-constrained embedded Chromecast/Starboard architectures will fail and trigger an immediate out-of-memory (OOM) crash.
  • Even if virtual memory allocation succeeds, streaming 2 GiB of data through a Mojo data pipe will consume physical memory/swap resources, triggering the OS Out-of-Memory (OOM) killer before the read callback completes. This limits the real-world impact of this code flaw to a Denial of Service (DoS) stability crash.

Suggested Remediation

To prevent potential integer narrowing, use base::checked_cast or validate the size of incoming buffers to enforce a reasonable upper boundary on DecoderBuffer sizes before processing:

sample_info.buffer_size = base::checked_cast<int>(buffer->size());

Evaluated with Chrome root at commit: 5133b93d189b383c37805b1cf3a9d2dbfe8d7379


Results so far have been promising, but there can be wrong deductions. Feel free to adjust as follows:

  • If you are familiar with the severity guidelines, you may adjust the severity.
  • If this is a false positive, and there’s no work to be done, please close as WAI.
  • If there is work to do here but not a vulnerability, please change the issue type to Task/Bug/FR.

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