Medium watch ✔︎ verified CVSS 7.5 chrome OOB 📄 Reporter bug report 🔧 Commit mapped

Overview

Medium
Severity
7.5
CVSS
No
Exploited ITW
Fixed
Fix Status
ImpactHeap buffer overflow in Chromecast
DescriptionHeap buffer overflow in Chromecast
ComponentChromecast
Sub-componentChromecast
Bug ClassOOB
Action tierwatch — corruption primitive but gated (local) — not default-reachable — factors set by independent review by AI
Computed by PatchGap's deterministic exploitability ruler from the fixing commit. patch-now = RCE-class & attacker-reachable in a default config, or exploited in the wild; watch = real security signal below that bar; noise = no security relevance.
Independent review by AIConfirmed
Reviewer notes: none; analysis correctly contradicts the advisory/reporter's compiled-out frame-DCHECK framing (pre-fix shows hard CHECK_EQ at :84/:101/:124) and identifies the real live defect as the unchecked attacker-controlled channel count (DCHECK_EQ at :292 + only kMaxChannels check at :328), confirmed by the new InvalidChannelCount test.
A second, independent AI reviewer (not a human) re-derived this analysis from the fixing commit and the vendor advisory, looking for errors.
Tracker495988507
Fix commit6c1a8f5cd53f (chromium/src) +76/-4
CWECWE-122 (Heap buffer overflow)
CVSS vectorCVSS:3.1/AV:A/AC:H/PR:N/UI:N/S:U/C:H/I:H/A:H
CISA KEVNot listed
CreditedGoogle
Reported2026-03-25 (per vendor advisory)
Fix landed2026-05-13 (public git)
Disclosed2026-05-19
Patch gap6 days — fix public in source before the advisory

Background

Capture service message parsing
message_parsing_utils.cc decodes audio-capture messages received over a socket into a media::AudioBus.
`AudioBus`
a fixed-size multi-channel audio buffer; channels() and frames() report the capacities allocated at creation.
`ConvertPlanarFloat` / `ConvertPlanarData` / `ConvertInterleavedData`
the per-format converters that copy received samples into the AudioBus.

Root Cause Analysis

ReadDataToAudioBus (chromecast/media/audio/capture_service/message_parsing_utils.cc:287-296) passes stream_info.num_channels straight to ConvertData, guarding the channel count against the destination only with DCHECK_EQ(stream_info.num_channels, audio_bus->channels()) at :292 — a DCHECK that is compiled out in release builds. num_channels originates from the attacker-supplied handshake and is validated only against ::media::limits::kMaxChannels in ReadHandshakeMessage at :328, never against the target AudioBus’s channel count. ConvertPlanarFloat (:115-131) then computes frames = CheckAudioData<float>(channels, data, data_size) at :119 and loops for (int c = 0; c < channels; ++c) at :126, copying into audio->channel(c) at :128; ConvertPlanarData (:90-113) and ConvertInterleavedData (:73-88) behave the same. If channels exceeds audio->channels(), audio->channel(c) indexes past the bus’s channel array — a heap out-of-bounds access. (The shown pre-fix already uses CHECK_EQ(frames, audio->frames()) at :84/:101/:124, so a frame-count mismatch aborts rather than overflows; this contradicts the advisory’s claim of a compiled-out frame DCHECK_EQ, and the code wins — the live vector is the unchecked channel count.)

The fix converts each CHECK_EQ(frames, audio->frames()) into a logging return false and, crucially, adds if (channels > audio->channels()) { ...; return false; } to each converter, and turns the ReadDataToAudioBus DCHECK_EQ into a if (stream_info.num_channels > audio_bus->channels()) return false;, restoring an enforced bound in all builds.

Key insight
The channel count from the untrusted handshake was only checked against the destination AudioBus by a release-stripped DCHECK_EQ, so an oversized channel count overran the bus; the patch adds real channels > audio->channels() guards that return failure in every build.

Attack Path

  1. Impersonate the capture service A local process that can answer on the capture-service socket completes the handshake with a victim browser audio-input stream. (inferred: the socket/impersonation details are from the report, not the shown source.)
  2. Advertise excess channels It reports a num_channels that passes the kMaxChannels check at :328 but exceeds the pre-allocated AudioBus’s channel count.
  3. Overflow on copy It sends a PCM audio message; ReadDataToAudioBus skips the compiled-out DCHECK_EQ at :292 and a converter loops audio->channel(c) beyond the bus’s channels, writing out of bounds on the heap.

Impact Assessment

A local process able to feed the audio-capture socket can drive a heap out-of-bounds write in the browser/host process via an oversized channel count. The advisory rates this medium severity with no in-the-wild exploitation; escalation beyond the out-of-bounds write is (inferred).

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

Audit Directions

  • DCHECK on untrusted sizes
    Any DCHECK/DCHECK_EQ validating an attacker-controlled count or length against a buffer must be promoted to a build-independent check with a failure path.
  • Validate against the destination
    Channel/frame counts from IPC or sockets must be bounded against the actual target buffer, not only against a global maximum like kMaxChannels.
  • Per-channel indexing
    Review loops that index channel(c)/plane(i) by an externally supplied count to confirm the count cannot exceed the container’s capacity.
From 6c1a8f5cd53fdd686f668edd8ee2d66147439cfd Mon Sep 17 00:00:00 2001
From: Simeon Anfinrud <[email protected]>
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 <[email protected]>
Reviewed-by: Sandeep Vijayasekar <[email protected]>
Auto-Submit: Simeon Anfinrud <[email protected]>
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 [email protected]

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