Overview

Medium
Severity
CVSS
No
Exploited ITW
Fixed
Fix Status
ImpactHeap buffer overflow in Media
DescriptionHeap buffer overflow in Media
ComponentMedia
Bug ClassOOB
Tracker476461867
Fix commitec1030c9323c (chromium/src) +176/-19
CISA KEVNot listed
CreditedGoogle
Disclosed2026-02-18

Changed Functions

FunctionChangeNotes
for
media/base/channel_layout.cc
modified
if
media/base/channel_layout.cc
modified

Files Changed

  • media/base/BUILD.gn
  • media/base/channel_layout.cc
  • media/base/channel_layout.h
  • media/base/channel_layout_unittest.cc
From ec1030c9323c82affd75cc3facbc9c602859e3bb Mon Sep 17 00:00:00 2001
From: Jordan Bayles <[email protected]>
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 <[email protected]>
Commit-Queue: Jordan Bayles <[email protected]>
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"
Loading diff…

Regression Test / PoC

shipped with the fix
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
Loading diff…

Original Bug Report

reported by [email protected]

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.

View on issue tracker