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 <[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;
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.