Medium watch CVSS 8.8 chrome OOB 📄 Reporter bug report 🔧 Commit mapped

Overview

Medium
Severity
8.8
CVSS
No
Exploited ITW
Fixed
Fix Status
ImpactOut of bounds read in Media
DescriptionOut of bounds read in Media
ComponentMedia
Sub-componentInternals>Media
Bug ClassOOB
Action tierwatch — information disclosure — security-relevant, below the RCE-class bar
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.
Tracker392375312
Fix commitfccd12a6c506 (chromium/src) +22/-4
CWECWE-125 (Out-of-bounds read)
CVSS vectorCVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:U/C:H/I:H/A:H
CISA KEVNot listed
Credited@Bl1nnnk and @Pisanbao
Reported2025-01-26 · bounty $2000 (per vendor advisory)
Fix landed2025-01-30 (public git)
Disclosed2025-03-04
Patch gap33 days — fix public in source before the advisory

Background

Mojo / mojom type converter
code in media/mojo/common/media_type_converters.cc that deserializes an IPC-transported mojom AudioBuffer struct into a native media::AudioBuffer, where the sender may be a less-trusted process.
`AudioBuffer`
a media object carrying raw sample data plus metadata (frame_count, channel_count, sample_format, channel_layout) describing how many bytes that data should contain.
`SampleFormatToBytesPerChannel()`
a helper mapping a media::SampleFormat to the number of bytes each channel occupies per frame (0 for kUnknownFormat).
`base::CheckMul` / `ValueOrDefault`
checked-arithmetic utilities that compute a product and yield a fallback (here 0u) instead of silently overflowing.

Root Cause Analysis

When converting a mojom AudioBuffer into a media::AudioBuffer, the pre-patch code trusted the attacker-controlled input->channel_count and input->frame_count/sample_format metadata without verifying that input->data actually held enough bytes. It computed size_per_channel = input->data.size() / input->channel_count and set up channel_ptrs pointing at input->data.data() + i * size_per_channel, then handed those to AudioBuffer::CopyFrom(), which copies according to the declared frame_count/channel_count/sample_format rather than the real buffer size. If the declared geometry implied more data than input->data contained, CopyFrom() would read past the end of the buffer.

The fix computes bytes_per_channel from the (already-validated) sample_format and derives min_data_size = frame_count * channel_count * bytes_per_channel using base::CheckMul (defaulting to 0 on overflow), then rejects the buffer with AudioBuffer::CreateEOSBuffer() whenever input->data.size() < min_data_size, so the copy only proceeds when the buffer is provably large enough. This guard runs before the channel_ptrs setup and the CopyFrom() call, closing the OOB read.

Key insight
The converter never checked that the transported data buffer was large enough for the declared frame/channel/format geometry, and the fix adds a checked-arithmetic min_data_size lower-bound validation that returns an EOS buffer before any pointer arithmetic or copy occurs.

Attack Path

  1. Send a crafted `mojom` `AudioBuffer` A compromised or malicious sender process transmits an AudioBuffer whose frame_count, channel_count, and sample_format describe more sample bytes than the accompanying input->data actually holds.
  2. Pass existing metadata checks The buffer satisfies the pre-existing channel_layout/channel_count validation, so conversion continues past the early EOS return.
  3. Build under-sized channel pointers The converter computes size_per_channel from the too-small data.size() and fills channel_ptrs, which no longer reflect the declared geometry.
  4. Over-read in `CopyFrom()` media::AudioBuffer::CopyFrom() copies according to the declared frame_count/channel_count/sample_format, reading past the end of input->data and producing the out-of-bounds read in Media (inferred sink: CopyFrom is the consumer of the under-sized buffer).

Impact Assessment

An attacker controlling the sending end of the media Mojo IPC can cause an out-of-bounds read when the receiving process deserializes an AudioBuffer, potentially exposing adjacent heap memory in that process. The advisory describes this as an out-of-bounds read in Media; escalation beyond the read (info leak used for further exploitation) is (inferred). The precondition is the ability to send a malformed AudioBuffer over the media mojom interface with mismatched size metadata.

Changed Functions

FunctionChangeNotes
for
media/mojo/common/media_type_converters.cc
modified

Files Changed

  • media/mojo/common/media_type_converters.cc

Audit Directions

  • Trusted size metadata across IPC
    Audit other mojom type converters and deserializers that size copies from sender-supplied frame_count/channel_count/length fields without validating them against the actual received buffer length.
  • Product-of-dimensions without overflow checks
    Look for a * b * c byte-size computations on untrusted inputs that should use base::CheckMul/CheckedNumeric and treat overflow as invalid rather than wrapping.
  • Copy sinks keyed on declared vs. actual size
    Review callers of AudioBuffer::CopyFrom() and similar CopyFrom/memcpy-style APIs to confirm the source buffer is validated to hold at least the number of bytes the declared geometry implies.
From fccd12a6c506421818ae24afa19ac73b59c7732f Mon Sep 17 00:00:00 2001
From: Thomas Guilbert <tguilbert@chromium.org>
Date: Wed, 29 Jan 2025 16:22:59 -0800
Subject: [PATCH] Add mojom AudioBuffer data size checks

See attached bug for details.

Fixed: 392375312
Change-Id: Ie70e603470b8c5cf1b8c12286169e2cb7fedc9d8
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/6210759
Reviewed-by: Daniel Cheng <dcheng@chromium.org>
Commit-Queue: Thomas Guilbert <tguilbert@chromium.org>
Reviewed-by: Dale Curtis <dalecurtis@chromium.org>
Cr-Commit-Position: refs/heads/main@{#1413238}
---

diff --git a/media/mojo/common/media_type_converters.cc b/media/mojo/common/media_type_converters.cc
index 9321f16e..b142d718 100644
--- a/media/mojo/common/media_type_converters.cc
+++ b/media/mojo/common/media_type_converters.cc
@@ -9,15 +9,15 @@
 
 #include "media/mojo/common/media_type_converters.h"
 
-#include <stddef.h>
-#include <stdint.h>
 #include <memory>
 
 #include "base/logging.h"
+#include "base/numerics/checked_math.h"
 #include "base/numerics/safe_conversions.h"
 #include "media/base/audio_buffer.h"
 #include "media/base/decoder_buffer.h"
 #include "media/base/decrypt_config.h"
+#include "media/base/sample_format.h"
 #include "media/base/subsample_entry.h"
 #include "mojo/public/cpp/system/buffer.h"
 
@@ -227,7 +227,7 @@
       static_cast<size_t>(input->channel_layout) > media::CHANNEL_LAYOUT_MAX ||
       ChannelLayoutToChannelCount(input->channel_layout) !=
           input->channel_count) {
-    LOG(ERROR) << "Receive an invalid audio buffer, replace it with EOS.";
+    DLOG(ERROR) << "Receive an invalid audio buffer, replace it with EOS.";
     return media::AudioBuffer::CreateEOSBuffer();
   }
 
@@ -239,13 +239,31 @@
         input->timestamp);
   }
 
+  // Safe to cast, since we already checked `sample_format` doesn't exceed
+  // media::kSampleFormatMax above.
+  const size_t bytes_per_channel = SampleFormatToBytesPerChannel(
+      static_cast<media::SampleFormat>(input->sample_format));
+
+  // `bytes_per_channel` could be 0 if we received a kUnknownFormat. In that
+  // case, and in the case of a overflow below, `min_data_size` will be 0,
+  // and we will return an EOS below.
+  const size_t min_data_size =
+      base::CheckMul(input->frame_count,
+                     base::CheckMul(input->channel_count, bytes_per_channel))
+          .ValueOrDefault(0u);
+  if (input->data.size() < min_data_size) {
+    DLOG(ERROR) << "Received invalid AudioBuffer, replace it with EOS.";
+    return media::AudioBuffer::CreateEOSBuffer();
+  }
+
   // Setup channel pointers.  AudioBuffer::CopyFrom() will only use the first
   // one in the case of interleaved data.
   std::vector<const uint8_t*> channel_ptrs(input->channel_count, nullptr);
   const size_t size_per_channel = input->data.size() / input->channel_count;
   DCHECK_EQ(0u, input->data.size() % input->channel_count);
-  for (int i = 0; i < input->channel_count; ++i)
+  for (int i = 0; i < input->channel_count; ++i) {
     channel_ptrs[i] = input->data.data() + i * size_per_channel;
+  }
 
   return media::AudioBuffer::CopyFrom(
       input->sample_format, input->channel_layout, input->channel_count,
Loading diff…

Original Bug Report

reported by bl...@gmail.com

Information Leak via Out-of-Bounds Read in media::AudioBuffer

Steps to reproduce the problem

  1. Build the chrome:
gn gen out/MediaMojoRelease --args="is_debug=false symbol_level=2 enable_nacl=false is_component_build=true dcheck_always_on=false"
  1. Launch chrome with mojo enable:
Release\chrome.exe  --enable-blink-features=MojoJS,MojoJSTest
  1. Attach the GPU process with a debugger(I don’t know why ASAN can’t capture this case).
  2. Visit the poc.html to trigger crash.

Problem Description

There is an insufficient validation of the frame_count parameter when creating an AudioBuffer from mojo data.

And the frame_count is used to calculate data_size_per_channel, which represents the size of the data per audio channel.

The data_size_per_channel value is subsequently used as the size parameter in a memcpy, , resulting in an out-of-bounds memory read.

Additionally, the out-of-bounds data located on the heap can be transmitted back to the renderer process after encoding, leading to potential data leakage or further exploitation opportunities.

Additional Comments

BISECT

The vulnerability may exist in the first commit of AudioBuffer: https://source.chromium.org/chromium/chromium/src/+/55de3108f586fc00efa0a1352f7b63960fb8d15d:media/base/audio_buffer.cc;dlc=fd45eed1e4973593cc55c45424e1fde808bd6d9d

Summary

Information Leak via Out-of-Bounds Read in media::AudioBuffer

Custom Questions

Type of crash:

GPU Process

Crash state:

00 0000008a`c97fc838 00007fff`09cd0762     VCRUNTIME140!memcpy_avx_ermsb_Intel+0x2e9 [D:\a\_work\1\s\src\vctools\crt\vcruntime\src\string\amd64\Intel\memcpy_avx_ermsb_aligned.asm @ 363] 
01 0000008a`c97fc840 00007fff`09cd1767     media!media::AudioBuffer::AudioBuffer+0x7a2 [F:\Chromium\src\media\base\audio_buffer.cc @ 211] 
*** WARNING: Unable to verify checksum for D:\Chromium\dev_6876\Release\media_mojo_services.dll
02 0000008a`c97fc940 00007ffe`9319e359     media!media::AudioBuffer::CopyFrom+0xd7 [F:\Chromium\src\media\base\audio_buffer.cc @ 305] 
03 0000008a`c97fca30 00007ffe`92fe96e1     media_mojo_services!mojo::TypeConverter<scoped_refptr<media::AudioBuffer>,mojo::StructPtr<media::mojom::AudioBuffer> >::Convert+0x679 [F:\Chromium\src\media\mojo\common\media_type_converters.cc @ 250] 
04 (Inline Function) --------`--------     media_mojo_services!mojo::StructPtr<media::mojom::AudioBuffer>::To+0x8 [F:\Chromium\src\mojo\public\cpp\bindings\struct_ptr.h @ 67] 
05 0000008a`c97fcc40 00007ffe`9306879c     media_mojo_services!media::MojoAudioEncoderService::Encode+0x61 [F:\Chromium\src\media\mojo\services\mojo_audio_encoder_service.cc @ 52] 
06 0000008a`c97fcd00 00007ffe`92fd9d9c     media_mojo_services!media::mojom::AudioEncoderStubDispatch::AcceptWithResponder+0x59c [F:\Chromium\src\out\Release\gen\media\mojo\mojom\audio_encoder.mojom.cc @ 1005] 
*** WARNING: Unable to verify checksum for D:\Chromium\dev_6876\Release\mojo_public_cpp_bindings.dll
07 0000008a`c97fce80 00007fff`c14841e7     media_mojo_services!media::mojom::AudioEncoderStub<mojo::UniquePtrImplRefTraits<media::mojom::AudioEncoder,std::__Cr::default_delete<media::mojom::AudioEncoder> > >::AcceptWithResponder+0x3c [F:\Chromium\src\out\Release\gen\media\mojo\mojom\audio_encoder.mojom.h @ 229] 
08 0000008a`c97fced0 00007fff`c148e13d     mojo_public_cpp_bindings!mojo::InterfaceEndpointClient::HandleValidatedMessage+0x727 [F:\Chromium\src\mojo\public\cpp\bindings\lib\interface_endpoint_client.cc @ 1032] 
09 0000008a`c97fd100 00007fff`c1486b85     mojo_public_cpp_bindings!mojo::MessageDispatcher::Accept+0xed [F:\Chromium\src\mojo\public\cpp\bindings\lib\message_dispatcher.cc @ 48] 
0a 0000008a`c97fd1b0 00007fff`c1493c9a     mojo_public_cpp_bindings!mojo::InterfaceEndpointClient::HandleIncomingMessage+0x375 [F:\Chromium\src\mojo\public\cpp\bindings\lib\interface_endpoint_client.cc @ 751] 
0b 0000008a`c97fd370 00007fff`c14931a8     mojo_public_cpp_bindings!mojo::internal::MultiplexRouter::ProcessIncomingMessage+0x5ca [F:\Chromium\src\mojo\public\cpp\bindings\lib\multiplex_router.cc @ 1120] 
0c 0000008a`c97fd4a0 00007fff`c148e189     mojo_public_cpp_bindings!mojo::internal::MultiplexRouter::Accept+0x1f8 [F:\Chromium\src\mojo\public\cpp\bindings\lib\multiplex_router.cc @ 737] 
0d 0000008a`c97fd6f0 00007fff`c147861b     mojo_public_cpp_bindings!mojo::MessageDispatcher::Accept+0x139 [F:\Chromium\src\mojo\public\cpp\bindings\lib\message_dispatcher.cc @ 43] 
0e 0000008a`c97fd7a0 00007fff`c147926d     mojo_public_cpp_bindings!mojo::Connector::DispatchMessageW+0x38b [F:\Chromium\src\mojo\public\cpp\bindings\lib\connector.cc @ 561] 
0f 0000008a`c97fd930 00007fff`c1478ef7     mojo_public_cpp_bindings!mojo::Connector::ReadAllAvailableMessages+0x14d [F:\Chromium\src\mojo\public\cpp\bindings\lib\connector.cc @ 620] 
10 (Inline Function) --------`--------     mojo_public_cpp_bindings!mojo::Connector::OnHandleReadyInternal+0x2c [F:\Chromium\src\mojo\public\cpp\bindings\lib\connector.cc @ 452] 
11 0000008a`c97fd9e0 00007fff`c1479d8a     mojo_public_cpp_bindings!mojo::Connector::OnWatcherHandleReady+0x57 [F:\Chromium\src\mojo\public\cpp\bindings\lib\connector.cc @ 418] 
*** WARNING: Unable to verify checksum for D:\Chromium\dev_6876\Release\mojo_public_system_cpp.dll
12 0000008a`c97fda30 00007fff`56c1d002     mojo_public_cpp_bindings!base::RepeatingCallback<void (unsigned int)>::Run+0xaa [F:\Chromium\src\base\functional\callback.h @ 345] 
13 0000008a`c97fdac0 00007fff`56c1ce19     mojo_public_system_cpp!base::RepeatingCallback<void (unsigned int, const mojo::HandleSignalsState &)>::Run+0xb2 [F:\Chromium\src\base\functional\callback.h @ 345] 
*** WARNING: Unable to verify checksum for D:\Chromium\dev_6876\Release\base.dll
14 0000008a`c97fdb50 00007fff`26601b64     mojo_public_system_cpp!mojo::SimpleWatcher::OnHandleReady+0x139 [F:\Chromium\src\mojo\public\cpp\system\simple_watcher.cc @ 279] 
15 0000008a`c97fdbf0 00007fff`266d5614     base!base::OnceCallback<void ()>::Run+0x94 [F:\Chromium\src\base\functional\callback.h @ 156] 
16 0000008a`c97fdc70 00007fff`2673a373     base!base::TaskAnnotator::RunTaskImpl+0x154 [F:\Chromium\src\base\task\common\task_annotator.cc @ 209] 
17 (Inline Function) --------`--------     base!base::TaskAnnotator::RunTask+0x54 [F:\Chromium\src\base\task\common\task_annotator.h @ 106] 
18 (Inline Function) --------`--------     base!base::internal::TaskTracker::RunTaskImpl+0x78 [F:\Chromium\src\base\task\thread_pool\task_tracker.cc @ 677] 
19 0000008a`c97fdd10 00007fff`267396e5     base!base::internal::TaskTracker::RunSkipOnShutdown+0xc3 [F:\Chromium\src\base\task\thread_pool\task_tracker.cc @ 662] 
1a (Inline Function) --------`--------     base!base::internal::TaskTracker::RunTaskWithShutdownBehavior+0x43 [F:\Chromium\src\base\task\thread_pool\task_tracker.cc @ 692] 
1b 0000008a`c97fddd0 00007fff`26738ea0     base!base::internal::TaskTracker::RunTask+0x495 [F:\Chromium\src\base\task\thread_pool\task_tracker.cc @ 520] 
1c 0000008a`c97ff770 00007fff`267481c1     base!base::internal::TaskTracker::RunAndPopNextTask+0x310 [F:\Chromium\src\base\task\thread_pool\task_tracker.cc @ 417] 
1d 0000008a`c97ffa20 00007fff`26747cd8     base!base::internal::WorkerThread::RunWorker+0x3c1 [F:\Chromium\src\base\task\thread_pool\worker_thread.cc @ 493] 
1e 0000008a`c97ffbd0 00007fff`267cbc47     base!base::internal::WorkerThread::RunSharedWorker+0x18 [F:\Chromium\src\base\task\thread_pool\worker_thread.cc @ 390] 
1f 0000008a`c97ffc10 00007ff8`0053e8d7     base!base::`anonymous namespace'::ThreadFunc+0xe7 [F:\Chromium\src\base\threading\platform_thread_win.cc @ 118] 
20 0000008a`c97ffc90 00007ff8`01d9fbcc     KERNEL32!BaseThreadInitThunk+0x17
21 0000008a`c97ffcc0 00000000`00000000     ntdll!RtlUserThreadStart+0x2c

Reporter credit:

@Bl1nnnk and @Pisanbao

Additional Data

Category: Security
Chrome Channel: Dev
Regression: N/A

View on issue tracker
Uploaded PoC files
Links in the report