CVE-2026-9123
Overview
Changed Functions
| Function | Change | Notes |
|---|---|---|
forchromecast/media/audio/capture_service/message_parsing_utils.cc |
modified | |
TESTchromecast/media/audio/capture_service/message_parsing_utils_unittest.cc |
modified |
Files Changed
chromecast/media/audio/capture_service/message_parsing_utils.ccchromecast/media/audio/capture_service/message_parsing_utils_unittest.cc
Patch
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;
Regression Test / PoC
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;
Original Bug Report
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.ccchromecast/media/audio/cast_audio_input_stream.ccchromecast/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:
- A local attacker binds to
\0/tmp/capture-servicebefore the legitimate capture service. - A browser component, such as
CastAudioManagerAlsa, requests audio input, instantiating aCastAudioInputStreamand connecting to the socket. - The browser process sends a handshake request. The attacker responds with a handshake acknowledgement, specifying the
sample_formatasPLANAR_FLOAT(value 5). - The browser unconditionally accepts the
PLANAR_FLOATformat. - The attacker sends a PCM audio message (
kPcmAudio) with a payload size that exceeds the pre-allocatedAudioBuscapacity (e.g., sending several kilobytes for a small buffer). - The browser process reads the message via
SmallMessageSocketand passes the oversized payload toConvertPlanarFloat. - In release builds, the
DCHECK_EQis bypassed, andstd::copyoverflows theAudioBusheap 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.