CVE-2025-1919
Overview
Background
- Mojo / mojom type converter
- code in
media/mojo/common/media_type_converters.ccthat deserializes an IPC-transportedmojomAudioBufferstruct into a nativemedia::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::SampleFormatto the number of bytes each channel occupies per frame (0 forkUnknownFormat). - `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.
min_data_size lower-bound validation that returns an EOS buffer before any pointer arithmetic or copy occurs.Attack Path
- Send a crafted `mojom` `AudioBuffer`
A compromised or malicious sender process transmits an
AudioBufferwhoseframe_count,channel_count, andsample_formatdescribe more sample bytes than the accompanyinginput->dataactually holds. - Pass existing metadata checks
The buffer satisfies the pre-existing
channel_layout/channel_countvalidation, so conversion continues past the early EOS return. - Build under-sized channel pointers
The converter computes
size_per_channelfrom the too-smalldata.size()and fillschannel_ptrs, which no longer reflect the declared geometry. - Over-read in `CopyFrom()`
media::AudioBuffer::CopyFrom()copies according to the declaredframe_count/channel_count/sample_format, reading past the end ofinput->dataand producing the out-of-bounds read in Media(inferred sink: CopyFrom is the consumer of the under-sized buffer).
Impact Assessment
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
| Function | Change | Notes |
|---|---|---|
formedia/mojo/common/media_type_converters.cc |
modified |
Files Changed
media/mojo/common/media_type_converters.cc
Audit Directions
- Trusted size metadata across IPCAudit other
mojomtype converters and deserializers that size copies from sender-suppliedframe_count/channel_count/length fields without validating them against the actual received buffer length. - Product-of-dimensions without overflow checksLook for
a * b * cbyte-size computations on untrusted inputs that should usebase::CheckMul/CheckedNumericand treat overflow as invalid rather than wrapping. - Copy sinks keyed on declared vs. actual sizeReview callers of
AudioBuffer::CopyFrom()and similarCopyFrom/memcpy-style APIs to confirm the source buffer is validated to hold at least the number of bytes the declared geometry implies.
Patch
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,
Original Bug Report
Information Leak via Out-of-Bounds Read in media::AudioBuffer
Steps to reproduce the problem
- 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"
- Launch chrome with mojo enable:
Release\chrome.exe --enable-blink-features=MojoJS,MojoJSTest
- Attach the GPU process with a debugger(I don’t know why ASAN can’t capture this case).
- 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
- https://source.chromium.org/chromium/chromium/src/+/55de3108f586fc00efa0a1352f7b63960fb8d15d:media/base/audio_buffer.cc;dlc=fd45eed1e4973593cc55c45424e1fde808bd6d9d
- https://source.chromium.org/chromium/chromium/src/+/main:media/base/audio_buffer.cc;bpv=1;bpt=1;l=207?gsn=memcpy&gs=KYTHE%3A%2F%2Fkythe%3A%2F%2Fchromium.googlesource.com%2Fcodesearch%2Fchromium%2Fsrc%2F%2Fmain%3Flang%3Dc%252B%252B%23memcpy%2523n%2523builtin
- https://source.chromium.org/chromium/chromium/src/+/main:media/base/audio_buffer.cc;bpv=1;bpt=1;l=207?gsn=memcpy&gs=KYTHE://kythe://chromium.googlesource.com/codesearch/chromium/src//main?lang=c%2B%2B#memcpy%23n%23builtin
- https://source.chromium.org/chromium/chromium/src/+/main:media/base/audio_buffer.cc;drc=27d34700b83f381c62e3a348de2e6dfdc08364b8;bpv=1;bpt=1;l=184?gsn=data_size_per_channel&gs=KYTHE%3A%2F%2Fkythe%3A%2F%2Fchromium.googlesource.com%2Fcodesearch%2Fchromium%2Fsrc%2F%2Fmain%3Flang%3Dc%252B%252B%3Fpath%3Dmedia%2Fbase%2Faudio_buffer.cc%23sB4c8_k1CpqMfD_STMuX97qisAXEK675vg36__jUHkw
- https://source.chromium.org/chromium/chromium/src/+/main:media/base/audio_buffer.cc;drc=27d34700b83f381c62e3a348de2e6dfdc08364b8;bpv=1;bpt=1;l=184?gsn=data_size_per_channel&gs=KYTHE://kythe://chromium.googlesource.com/codesearch/chromium/src//main?lang=c%2B%2B?path=media/base/audio_buffer.cc#sB4c8_k1CpqMfD_STMuX97qisAXEK675vg36__jUHkw
- https://source.chromium.org/chromium/chromium/src/+/main:media/base/audio_buffer.cc;drc=27d34700b83f381c62e3a348de2e6dfdc08364b8;l=123