CVE-2026-2650
Overview
Changed Functions
| Function | Change | Notes |
|---|---|---|
formedia/base/channel_layout.cc |
modified | |
ifmedia/base/channel_layout.cc |
modified |
Files Changed
media/base/BUILD.gnmedia/base/channel_layout.ccmedia/base/channel_layout.hmedia/base/channel_layout_unittest.cc
Patch
From ec1030c9323c82affd75cc3facbc9c602859e3bb Mon Sep 17 00:00:00 2001
From: Jordan Bayles <jophba@chromium.org>
Date: Fri, 06 Feb 2026 13:38:00 -0800
Subject: [PATCH] [Fuzzer] Fix overflow in media::AudioBuffer::AudioBuffer
Fuzzing discovered a bug in which audio streams that return buffers
with a different channel count than the config has can result in an
overflow since, when the decoder copies to the output buffer, it is
expecting a different amount of data than it actually received.
This patch resolves this by updating the channel layout to match
the channel bitmask returned by Symphonia whenever the channel
count changes.
Bug: 476461867
Change-Id: Id14fc6e49bdfee06c5190ba4b00b8731fcc447b7
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/7499455
Reviewed-by: Thomas Guilbert <tguilbert@chromium.org>
Commit-Queue: Jordan Bayles <jophba@chromium.org>
Cr-Commit-Position: refs/heads/main@{#1581099}
---
diff --git a/media/base/BUILD.gn b/media/base/BUILD.gn
index 3a00a32..2b72ebdc 100644
--- a/media/base/BUILD.gn
+++ b/media/base/BUILD.gn
@@ -630,6 +630,7 @@
"callback_registry_unittest.cc",
"callback_timeout_helpers_unittest.cc",
"capture_version_unittest.cc",
+ "channel_layout_unittest.cc",
"channel_mixer_unittest.cc",
"channel_mixing_matrix_unittest.cc",
"container_names_unittest.cc",
diff --git a/media/base/channel_layout.cc b/media/base/channel_layout.cc
index d7e4a024..1c023c72 100644
--- a/media/base/channel_layout.cc
+++ b/media/base/channel_layout.cc
@@ -6,6 +6,7 @@
#include <stddef.h>
+#include <algorithm>
#include <array>
#include "base/check_op.h"
@@ -15,6 +16,8 @@
namespace media {
+namespace {
+
constexpr auto kLayoutToChannels = std::to_array<int>({
0, // CHANNEL_LAYOUT_NONE
0, // CHANNEL_LAYOUT_UNSUPPORTED
@@ -60,8 +63,7 @@
// surround sound channel in FFmpeg's 5.1 layout is in the 5th position (because
// the order is L, R, C, LFE, LS, RS), so
// kChannelOrderings[CHANNEL_LAYOUT_5_1][SIDE_LEFT] = 4;
-const std::array<std::array<const int, CHANNELS_MAX + 1>,
- CHANNEL_LAYOUT_MAX + 1>
+constexpr std::array<std::array<int, CHANNELS_MAX + 1>, CHANNEL_LAYOUT_MAX + 1>
kChannelOrderings = {{
// FL | FR | FC | LFE | BL | BR | FLofC | FRofC | BC | SL | SR
@@ -180,6 +182,30 @@
{0, 1, -1, 2, -1, -1, -1, -1, 3, -1, -1},
}};
+// Helper to compute bitmask for a layout at compile-time.
+constexpr ChannelMask ComputeChannelMask(ChannelLayout layout) {
+ ChannelMask mask = 0;
+ for (int c = 0; c <= Channels::CHANNELS_MAX; ++c) {
+ if (kChannelOrderings[layout][c] != -1) {
+ mask |= 1ULL << c;
+ }
+ }
+ return mask;
+}
+
+// Map of all channel layouts to their respective masks.
+constexpr auto kChannelMaskToLayoutMap = []() {
+ std::array<std::pair<ChannelMask, ChannelLayout>, CHANNEL_LAYOUT_MAX + 1>
+ entries;
+ for (int i = 0; i <= CHANNEL_LAYOUT_MAX; ++i) {
+ ChannelLayout layout = static_cast<ChannelLayout>(i);
+ entries[i] = {ComputeChannelMask(layout), layout};
+ }
+ return entries;
+}();
+
+} // namespace
+
int ChannelLayoutToChannelCount(ChannelLayout layout) {
DCHECK_LT(static_cast<size_t>(layout), std::size(kLayoutToChannels));
DCHECK_LE(kLayoutToChannels[layout], kMaxConcurrentChannels);
@@ -216,6 +242,17 @@
return CHANNEL_LAYOUT_UNSUPPORTED;
}
+ChannelLayout ChannelMaskToLayout(ChannelMask channel_mask) {
+ for (const auto& entry : kChannelMaskToLayoutMap) {
+ if (entry.first == channel_mask) {
+ return entry.second;
+ }
+ }
+ // If we don't find a standard ChannelLayout associated with the mask, return
+ // a DISCRETE layout so that we can still handle the raw channel data.
+ return CHANNEL_LAYOUT_DISCRETE;
+}
+
int ChannelOrder(ChannelLayout layout, Channels channel) {
DCHECK_LT(static_cast<size_t>(layout), std::size(kChannelOrderings));
DCHECK_LT(static_cast<size_t>(channel), std::size(kChannelOrderings[0]));
diff --git a/media/base/channel_layout.h b/media/base/channel_layout.h
index f3ae4e8f..63294aa 100644
--- a/media/base/channel_layout.h
+++ b/media/base/channel_layout.h
@@ -5,6 +5,8 @@
#ifndef MEDIA_BASE_CHANNEL_LAYOUT_H_
#define MEDIA_BASE_CHANNEL_LAYOUT_H_
+#include <stdint.h>
+
#include "media/base/media_export.h"
namespace media {
@@ -132,21 +134,30 @@
CHANNEL_LAYOUT_MAX = CHANNEL_LAYOUT_3_1_BACK
};
+// The channel order matches the order of the bitmask in the Windows
+// WAVEFORMATEXTENSIBLE format. The value of the enum corresponds to the bit
+// position in the mask (e.g. LEFT is bit 0, RIGHT is bit 1, etc.).
+//
+// This standard is used by Windows (WASAPI), FFmpeg (legacy layouts), and
+// SMPTE.
+//
// Note: Do not reorder or reassign these values; other code depends on their
-// ordering to operate correctly. E.g., CoreAudio channel layout computations.
+// ordering to operate correctly. E.g., CoreAudio channel layout computations
+// and ChannelMaskToLayout().
enum Channels {
LEFT = 0,
- RIGHT,
- CENTER,
- LFE,
- BACK_LEFT,
- BACK_RIGHT,
- LEFT_OF_CENTER,
- RIGHT_OF_CENTER,
- BACK_CENTER,
- SIDE_LEFT,
- SIDE_RIGHT,
- CHANNELS_MAX = SIDE_RIGHT, // Must always equal the largest value ever logged.
+ RIGHT = 1,
+ CENTER = 2,
+ LFE = 3,
+ BACK_LEFT = 4,
+ BACK_RIGHT = 5,
+ LEFT_OF_CENTER = 6,
+ RIGHT_OF_CENTER = 7,
+ BACK_CENTER = 8,
+ SIDE_LEFT = 9,
+ SIDE_RIGHT = 10,
+ CHANNELS_MAX =
+ SIDE_RIGHT, // Must always equal the largest value ever logged.
};
// The maximum number of concurrently active channels for all possible layouts.
@@ -170,6 +181,15 @@
// or return CHANNEL_LAYOUT_UNSUPPORTED if there is no good match.
MEDIA_EXPORT ChannelLayout GuessChannelLayout(int channels);
+// Returns the channel layout for a given channel mask. This code assumes that
+// the mask uses the Channels enum as the position of each channel, e.g.
+// a `LEFT` channel would be represented as `1 << Channels::LEFT` or `0b1`.
+//
+// Returns CHANNEL_LAYOUT_DISCRETE if the bitmask does not match any known
+// channel layout.
+using ChannelMask = uint32_t;
+MEDIA_EXPORT ChannelLayout ChannelMaskToLayout(ChannelMask channel_mask);
+
// Returns a string representation of the channel layout.
MEDIA_EXPORT const char* ChannelLayoutToString(ChannelLayout layout);
diff --git a/media/base/channel_layout_unittest.cc b/media/base/channel_layout_unittest.cc
new file mode 100644
index 0000000..dad0438
--- /dev/null
+++ b/media/base/channel_layout_unittest.cc
@@ -0,0 +1,57 @@
+// Copyright 2026 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "media/base/channel_layout.h"
+
+#include "testing/gtest/include/gtest/gtest.h"
Regression Test / PoC
diff --git a/media/base/channel_layout_unittest.cc b/media/base/channel_layout_unittest.cc
new file mode 100644
index 0000000..dad0438
--- /dev/null
+++ b/media/base/channel_layout_unittest.cc
@@ -0,0 +1,57 @@
+// Copyright 2026 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "media/base/channel_layout.h"
+
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace media {
+
+TEST(ChannelLayoutTest, ChannelMaskToLayout_StandardLayouts) {
+ // Test standard layouts.
+ EXPECT_EQ(CHANNEL_LAYOUT_MONO, ChannelMaskToLayout(1 << CENTER));
+ EXPECT_EQ(CHANNEL_LAYOUT_STEREO,
+ ChannelMaskToLayout((1 << LEFT) | (1 << RIGHT)));
+ EXPECT_EQ(
+ CHANNEL_LAYOUT_5_1,
+ ChannelMaskToLayout((1 << LEFT) | (1 << RIGHT) | (1 << CENTER) |
+ (1 << LFE) | (1 << SIDE_LEFT) | (1 << SIDE_RIGHT)));
+}
+
+TEST(ChannelLayoutTest, ChannelMaskToLayout_DuplicateLayouts) {
+ // Layouts with a lower index should win.
+
+ // CHANNEL_LAYOUT_STEREO (3) vs CHANNEL_LAYOUT_STEREO_DOWNMIX (16)
+ EXPECT_EQ(CHANNEL_LAYOUT_STEREO,
+ ChannelMaskToLayout((1 << LEFT) | (1 << RIGHT)));
+
+ // CHANNEL_LAYOUT_5_1 (10) vs CHANNEL_LAYOUT_5_1_4_DOWNMIX (33)
+ EXPECT_EQ(CHANNEL_LAYOUT_5_1,
+ ChannelMaskToLayout(1 << LEFT | 1 << RIGHT | 1 << CENTER |
+ 1 << LFE | 1 << SIDE_LEFT | 1 << SIDE_RIGHT));
+ // CHANNEL_LAYOUT_SURROUND (5) vs CHANNEL_LAYOUT_STEREO_AND_KEYBOARD_MIC (30)
+ EXPECT_EQ(CHANNEL_LAYOUT_SURROUND,
+ ChannelMaskToLayout((1 << LEFT) | (1 << RIGHT) | (1 << CENTER)));
+}
+
+TEST(ChannelLayoutTest, ChannelMaskToLayout_NonstandardLayouts) {
+ EXPECT_EQ(CHANNEL_LAYOUT_DISCRETE, ChannelMaskToLayout(1UL << 31));
+ EXPECT_EQ(CHANNEL_LAYOUT_NONE, ChannelMaskToLayout(0));
+}
+
+TEST(ChannelLayoutTest, ChannelMaskToLayout_UnknownChannelsReturnDiscrete) {
+ constexpr uint32_t kUnknownSpeaker = 31;
+ // Ensure that the speaker type is not currently supported.
+ static_assert(kUnknownSpeaker > CHANNELS_MAX);
+
+ EXPECT_EQ(
+ CHANNEL_LAYOUT_DISCRETE,
+ ChannelMaskToLayout((1 << LEFT) | (1 << RIGHT) | (1 << kUnknownSpeaker)));
+
+ EXPECT_EQ(CHANNEL_LAYOUT_DISCRETE,
+ ChannelMaskToLayout((1 << LEFT) | (1 << RIGHT) | (1 << CENTER) |
+ (1 << LFE) | (1 << SIDE_LEFT) |
+ (1 << SIDE_RIGHT) | 1 << kUnknownSpeaker));
+}
+} // namespace media
Original Bug Report
media_pipeline_integration_fuzzer: Heap-buffer-overflow in media::AudioBuffer::AudioBuffer
Detailed Report: https://clusterfuzz.com/testcase?key=6619123802898432
Fuzzing Engine: libFuzzer Fuzz Target: media_pipeline_integration_fuzzer Job Type: chromeos_libfuzzer_chrome_asan Platform Id: linux
Crash Type: Heap-buffer-overflow READ {*} Crash Address: 0x7aa1e8107d00 Crash State: media::AudioBuffer::AudioBuffer media::AudioBuffer::CopyFrom media::SymphoniaAudioDecoder::ToMediaAudioBuffer
Sanitizer: address (ASAN)
Recommended Security Severity: Medium
Regressed: https://clusterfuzz.com/revisions?job=chromeos_libfuzzer_chrome_asan&range=1560045:1560111
Reproducer Testcase: https://clusterfuzz.com/download?testcase_id=6619123802898432
Issue filed automatically.
See https://chromium.googlesource.com/chromium/src/+/master/testing/libfuzzer/reproducing.md for instructions on reproducing this bug locally.