Overview

Medium
Severity
CVSS
No
Exploited ITW
Fixed
Fix Status
ImpactHeap buffer overflow in Chromecast
DescriptionHeap buffer overflow in Chromecast
ComponentChromecast
Bug ClassOOB
Tracker495988507
Fix commit6c1a8f5cd53f (chromium/src) +76/-4
CISA KEVNot listed
CreditedGoogle
Disclosed2026-05-19

Changed Functions

FunctionChangeNotes
for
chromecast/media/audio/capture_service/message_parsing_utils.cc
modified
TEST
chromecast/media/audio/capture_service/message_parsing_utils_unittest.cc
modified

Files Changed

  • chromecast/media/audio/capture_service/message_parsing_utils.cc
  • chromecast/media/audio/capture_service/message_parsing_utils_unittest.cc
From 6c1a8f5cd53fdd686f668edd8ee2d66147439cfd Mon Sep 17 00:00:00 2001
From: Simeon Anfinrud <sanfin@chromium.org>
Date: Wed, 13 May 2026 15:02:14 -0700
Subject: [PATCH] [chromecast] Fix Potential heap buffer overflow in ConvertPlanarFloat via unchecked audio message size

The ConvertPlanarFloat and similar functions validated that the incoming
data size matched the pre-allocated AudioBus capacity using DCHECK_EQ.
Because DCHECK_EQ is compiled out in release builds, an oversized payload
could trigger a heap buffer overflow during std::copy. This commit
promotes the check to return false if the bounds mismatch.

Bug: 495988507
Test: Compiled and passed unit tests.
Change-Id: Ic3310085521d2a2db73c74d3ebdb62ae1a61e566
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/7765503
Commit-Queue: Simeon Anfinrud <sanfin@chromium.org>
Reviewed-by: Sandeep Vijayasekar <sandv@google.com>
Auto-Submit: Simeon Anfinrud <sanfin@chromium.org>
Cr-Commit-Position: refs/heads/main@{#1630250}
---

diff --git a/chromecast/media/audio/capture_service/message_parsing_utils.cc b/chromecast/media/audio/capture_service/message_parsing_utils.cc
index 2ec34267..ee1a40e 100644
--- a/chromecast/media/audio/capture_service/message_parsing_utils.cc
+++ b/chromecast/media/audio/capture_service/message_parsing_utils.cc
@@ -81,7 +81,17 @@
     return false;
   }
 
-  CHECK_EQ(frames, audio->frames());
+  if (frames != audio->frames()) {
+    LOG(ERROR) << "Audio frames (" << frames
+               << ") don't match audio bus frames (" << audio->frames() << ")";
+    return false;
+  }
+  if (channels > audio->channels()) {
+    LOG(ERROR) << "Audio channels (" << channels
+               << ") don't match audio bus channels (" << audio->channels()
+               << ")";
+    return false;
+  }
   audio->FromInterleaved<Traits>(
       reinterpret_cast<const typename Traits::ValueType*>(data), frames);
   return true;
@@ -98,7 +108,17 @@
     return false;
   }
 
-  CHECK_EQ(frames, audio->frames());
+  if (frames != audio->frames()) {
+    LOG(ERROR) << "Audio frames (" << frames
+               << ") don't match audio bus frames (" << audio->frames() << ")";
+    return false;
+  }
+  if (channels > audio->channels()) {
+    LOG(ERROR) << "Audio channels (" << channels
+               << ") don't match audio bus channels (" << audio->channels()
+               << ")";
+    return false;
+  }
   const typename Traits::ValueType* base_data =
       reinterpret_cast<const typename Traits::ValueType*>(data);
   for (int c = 0; c < channels; ++c) {
@@ -121,7 +141,17 @@
     return false;
   }
 
-  CHECK_EQ(frames, audio->frames());
+  if (frames != audio->frames()) {
+    LOG(ERROR) << "Audio frames (" << frames
+               << ") don't match audio bus frames (" << audio->frames() << ")";
+    return false;
+  }
+  if (channels > audio->channels()) {
+    LOG(ERROR) << "Audio channels (" << channels
+               << ") don't match audio bus channels (" << audio->channels()
+               << ")";
+    return false;
+  }
   const float* base_data = reinterpret_cast<const float*>(data);
   for (int c = 0; c < channels; ++c) {
     const float* source = UNSAFE_TODO(base_data + c * frames);
@@ -289,7 +319,12 @@
                         size_t size,
                         ::media::AudioBus* audio_bus) {
   DCHECK(audio_bus);
-  DCHECK_EQ(stream_info.num_channels, audio_bus->channels());
+  if (stream_info.num_channels > audio_bus->channels()) {
+    LOG(ERROR) << "Stream channels (" << stream_info.num_channels
+               << ") exceed audio bus channels (" << audio_bus->channels()
+               << ")";
+    return false;
+  }
   return ConvertData(stream_info.num_channels, stream_info.sample_format,
                      UNSAFE_TODO(data + kPcmAudioHeaderBytes),
                      size - kPcmAudioHeaderBytes, audio_bus);
diff --git a/chromecast/media/audio/capture_service/message_parsing_utils_unittest.cc b/chromecast/media/audio/capture_service/message_parsing_utils_unittest.cc
index 39b2c66..534d5e3 100644
--- a/chromecast/media/audio/capture_service/message_parsing_utils_unittest.cc
+++ b/chromecast/media/audio/capture_service/message_parsing_utils_unittest.cc
@@ -234,6 +234,43 @@
   EXPECT_FALSE(success);
 }
 
+TEST(MessageParsingUtilsTest, InvalidChannelCount) {
+  // Test where stream_info has more channels than the AudioBus
+  size_t data_size =
+      sizeof(PcmPacketHeader) / sizeof(float) + kFrames * (kChannels + 1);
+  std::vector<float> data(data_size, 1.0f);
+  PopulatePcmAudioHeader(reinterpret_cast<char*>(data.data()),
+                         data.size() * sizeof(float), kStreamInfo.stream_type,
+                         0);
+
+  auto audio_bus = ::media::AudioBus::Create(kChannels, kFrames);
+  StreamInfo malicious_stream_info = kStreamInfo;
+  malicious_stream_info.num_channels = kChannels + 1;
+  bool success = ReadDataToAudioBus(
+      malicious_stream_info,
+      UNSAFE_TODO(reinterpret_cast<char*>(data.data()) + sizeof(uint16_t)),
+      data_size * sizeof(float) - sizeof(uint16_t), audio_bus.get());
+  EXPECT_FALSE(success);
+}
+
+TEST(MessageParsingUtilsTest, InvalidDataLengthTooLarge) {
+  // Test an oversized payload that is a valid multiple of frame size.
+  // This used to cause a heap buffer overflow when DCHECKs were compiled out.
+  size_t data_size =
+      sizeof(PcmPacketHeader) / sizeof(float) + (kFrames + 1) * kChannels;
+  std::vector<float> data(data_size, 1.0f);
+  PopulatePcmAudioHeader(reinterpret_cast<char*>(data.data()),
+                         data.size() * sizeof(float), kStreamInfo.stream_type,
+                         0);
+
+  auto audio_bus = ::media::AudioBus::Create(kChannels, kFrames);
+  bool success = ReadDataToAudioBus(
+      kStreamInfo,
+      UNSAFE_TODO(reinterpret_cast<char*>(data.data()) + sizeof(uint16_t)),
+      data_size * sizeof(float) - sizeof(uint16_t), audio_bus.get());
+  EXPECT_FALSE(success);
+}
+
 TEST(MessageParsingUtilsTest, NotAlignedData) {
   size_t data_size =
       sizeof(PcmPacketHeader) / sizeof(float) + kFrames * kChannels + 1;
Loading diff…

Regression Test / PoC

shipped with the fix
diff --git a/chromecast/media/audio/capture_service/message_parsing_utils_unittest.cc b/chromecast/media/audio/capture_service/message_parsing_utils_unittest.cc
index 39b2c66..534d5e3 100644
--- a/chromecast/media/audio/capture_service/message_parsing_utils_unittest.cc
+++ b/chromecast/media/audio/capture_service/message_parsing_utils_unittest.cc
@@ -234,6 +234,43 @@
   EXPECT_FALSE(success);
 }
 
+TEST(MessageParsingUtilsTest, InvalidChannelCount) {
+  // Test where stream_info has more channels than the AudioBus
+  size_t data_size =
+      sizeof(PcmPacketHeader) / sizeof(float) + kFrames * (kChannels + 1);
+  std::vector<float> data(data_size, 1.0f);
+  PopulatePcmAudioHeader(reinterpret_cast<char*>(data.data()),
+                         data.size() * sizeof(float), kStreamInfo.stream_type,
+                         0);
+
+  auto audio_bus = ::media::AudioBus::Create(kChannels, kFrames);
+  StreamInfo malicious_stream_info = kStreamInfo;
+  malicious_stream_info.num_channels = kChannels + 1;
+  bool success = ReadDataToAudioBus(
+      malicious_stream_info,
+      UNSAFE_TODO(reinterpret_cast<char*>(data.data()) + sizeof(uint16_t)),
+      data_size * sizeof(float) - sizeof(uint16_t), audio_bus.get());
+  EXPECT_FALSE(success);
+}
+
+TEST(MessageParsingUtilsTest, InvalidDataLengthTooLarge) {
+  // Test an oversized payload that is a valid multiple of frame size.
+  // This used to cause a heap buffer overflow when DCHECKs were compiled out.
+  size_t data_size =
+      sizeof(PcmPacketHeader) / sizeof(float) + (kFrames + 1) * kChannels;
+  std::vector<float> data(data_size, 1.0f);
+  PopulatePcmAudioHeader(reinterpret_cast<char*>(data.data()),
+                         data.size() * sizeof(float), kStreamInfo.stream_type,
+                         0);
+
+  auto audio_bus = ::media::AudioBus::Create(kChannels, kFrames);
+  bool success = ReadDataToAudioBus(
+      kStreamInfo,
+      UNSAFE_TODO(reinterpret_cast<char*>(data.data()) + sizeof(uint16_t)),
+      data_size * sizeof(float) - sizeof(uint16_t), audio_bus.get());
+  EXPECT_FALSE(success);
+}
+
 TEST(MessageParsingUtilsTest, NotAlignedData) {
   size_t data_size =
       sizeof(PcmPacketHeader) / sizeof(float) + kFrames * kChannels + 1;
Loading diff…

Original Bug Report

reported by vm...@google.com

Potential heap buffer overflow in ConvertPlanarFloat via unchecked audio message size

Project Fortify, an experimental security project, has identified the following potential security issue.

Overview: A potential heap buffer overflow exists in ConvertPlanarFloat when handling audio capture messages over a UNIX domain socket. The function relies on a DCHECK_EQ to verify the incoming data size fits the AudioBus capacity, which is compiled out in release builds, allowing an attacker-controlled message to cause an out-of-bounds write via std::copy.

Affected files:

  • chromecast/media/audio/capture_service/message_parsing_utils.cc
  • chromecast/media/audio/cast_audio_input_stream.cc
  • chromecast/media/audio/net/audio_socket_service_uds.cc

Estimated timestamp from git blame: 2025-12-11

Vulnerability Analysis

A potential heap buffer overflow exists in the ConvertPlanarFloat function within chromecast/media/audio/capture_service/message_parsing_utils.cc. This function is responsible for copying float audio data received over a socket into a media::AudioBus.

The function calculates the number of audio frames directly from the size of the received data payload:

const int frames = CheckAudioData<float>(channels, data, data_size);

It then verifies that this number of frames matches the capacity of the target AudioBus using a DCHECK_EQ:

DCHECK_EQ(frames, audio->frames());

Because DCHECK_EQ is compiled out in release builds, there is no active bounds checking in production. The code proceeds to copy the data into the AudioBus channels using std::copy and a raw pointer obtained via .data():

for (int c = 0; c < channels; ++c) {
  const float* source = UNSAFE_TODO(base_data + c * frames);
  std::copy(source, UNSAFE_TODO(source + frames), audio->channel(c).data());
}

If the incoming data_size implies a frames count larger than the AudioBus capacity, std::copy will write past the end of the AudioBus channel’s backing memory, causing a heap buffer overflow.

Attack Vector

The vulnerability is reachable by an attacker who can impersonate the audio capture service. The service communicates via an abstract-namespace Unix Domain Socket (UDS) located at \0/tmp/capture-service (defined as kDefaultUnixDomainSocketPath). Because abstract-namespace sockets are not protected by filesystem permissions, any local process that shares the host network namespace (such as a compromised GPU or renderer process) can bind to this address if the legitimate service hasn’t already done so.

Potential steps to trigger the vulnerability:

  1. A local attacker binds to \0/tmp/capture-service before the legitimate capture service.
  2. A browser component, such as CastAudioManagerAlsa, requests audio input, instantiating a CastAudioInputStream and connecting to the socket.
  3. The browser process sends a handshake request. The attacker responds with a handshake acknowledgement, specifying the sample_format as PLANAR_FLOAT (value 5).
  4. The browser unconditionally accepts the PLANAR_FLOAT format.
  5. The attacker sends a PCM audio message (kPcmAudio) with a payload size that exceeds the pre-allocated AudioBus capacity (e.g., sending several kilobytes for a small buffer).
  6. The browser process reads the message via SmallMessageSocket and passes the oversized payload to ConvertPlanarFloat.
  7. In release builds, the DCHECK_EQ is bypassed, and std::copy overflows the AudioBus heap buffer with attacker-controlled data.

(Note: These are potential steps as the AI agent cannot execute code to verify a full exploit chain.)

Suggested Fix

Replace the DCHECK_EQ with a standard CHECK_EQ or a conditional return to enforce the bounds check in all builds. Additionally, consider using base::span::copy_from to ensure memory safety when copying into the AudioBus channel.

  const int frames = CheckAudioData<float>(channels, data, data_size);
  if (frames <= 0 || frames != audio->frames()) {
    return false;
  }

Evaluated with Chrome root at commit: 0eb4855bda702feaaa8b899336664f97e3df88b8


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. Please feel free to reach out to me if you have concerns or feedback.

View on issue tracker