CVE-2026-7905
Overview
Changed Functions
| Function | Change | Notes |
|---|---|---|
Convertmedia/mojo/common/media_type_converters.cc |
modified | |
ifmedia/mojo/common/media_type_converters.cc |
modified | |
formedia/mojo/common/media_type_converters.cc |
modified |
Files Changed
media/base/decrypt_config.ccmedia/mojo/common/media_type_converters.cc
Patch
From 1703fe61bee406496bc5af2b6c0af9fde8935b81 Mon Sep 17 00:00:00 2001
From: Stephen Nusko <nuskos@chromium.org>
Date: Sun, 29 Mar 2026 23:45:15 -0700
Subject: [PATCH] [MiracleFix] Replace DCHECK with CHECK in media/base and media/mojo.
These assertions are critical for maintaining invariants and should
cause a crash in all build configurations if violated.
This is a speculative hardening based on the linked bug.
Bug: 495259842
Change-Id: I44509ce9b18f994d748bc42255ffa85ca3496abc
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/7695435
Auto-Submit: Stephen Nusko <nuskos@chromium.org>
Reviewed-by: Colin Blundell <blundell@chromium.org>
Reviewed-by: Takashi Toyoshima <toyoshim@chromium.org>
Commit-Queue: Stephen Nusko <nuskos@chromium.org>
Cr-Commit-Position: refs/heads/main@{#1606907}
---
diff --git a/media/base/decrypt_config.cc b/media/base/decrypt_config.cc
index 2b889ba..d5b560b 100644
--- a/media/base/decrypt_config.cc
+++ b/media/base/decrypt_config.cc
@@ -47,12 +47,12 @@
subsamples_(subsamples),
encryption_pattern_(std::move(encryption_pattern)) {
// Unencrypted blocks should not have a DecryptConfig.
- DCHECK_NE(encryption_scheme_, EncryptionScheme::kUnencrypted);
+ CHECK_NE(encryption_scheme_, EncryptionScheme::kUnencrypted);
CHECK_GT(key_id_.size(), 0u);
CHECK_EQ(iv_.size(), static_cast<size_t>(DecryptConfig::kDecryptionKeySize));
// Pattern not allowed for non-'cbcs' schemes.
- DCHECK(encryption_scheme_ == EncryptionScheme::kCbcs || !encryption_pattern_);
+ CHECK(encryption_scheme_ == EncryptionScheme::kCbcs || !encryption_pattern_);
}
DecryptConfig::~DecryptConfig() = default;
diff --git a/media/mojo/common/media_type_converters.cc b/media/mojo/common/media_type_converters.cc
index ea13051..33cf431 100644
--- a/media/mojo/common/media_type_converters.cc
+++ b/media/mojo/common/media_type_converters.cc
@@ -43,6 +43,17 @@
TypeConverter<std::unique_ptr<media::DecryptConfig>,
media::mojom::DecryptConfigPtr>::
Convert(const media::mojom::DecryptConfigPtr& input) {
+ // Required invariants by the DecryptConfig constructor, to prevent a renderer
+ // from crashing the GPU we check them here as well and gracefully return
+ // nullptr instead.
+ if (input->encryption_scheme == media::EncryptionScheme::kUnencrypted) {
+ return nullptr;
+ }
+ // Pattern not allowed for non-'cbcs' schemes.
+ if (input->encryption_scheme != media::EncryptionScheme::kCbcs &&
+ input->encryption_pattern) {
+ return nullptr;
+ }
return std::make_unique<media::DecryptConfig>(
input->encryption_scheme, input->key_id, input->iv, input->subsamples,
input->encryption_pattern);
@@ -203,8 +214,10 @@
// `media::AudioBuffer`.
// `data_size()` refers to the amount of memory really used by the audio
// data. The rest is padding, which we don't need to copy.
- DCHECK_GT(input.data_size(), 0u);
- DCHECK_GE(input.data_size(), input.data_->span().size());
+ // Safe to CHECK here since this is into Mojo not From mojo (and thus not
+ // untrusted input).
+ CHECK_GT(input.data_size(), 0u);
+ CHECK_GE(input.data_size(), input.data_->span().size());
auto buffer_start = input.data_->span().begin();
auto buffer_end = buffer_start + input.data_size();
buffer->data.assign(buffer_start, buffer_end);
@@ -252,7 +265,8 @@
base::CheckMul(input->frame_count,
base::CheckMul(input->channel_count, bytes_per_channel))
.ValueOrDefault(0u);
- if (input->data.size() < min_data_size) {
+ if (input->data.size() < min_data_size ||
+ input->data.size() % input->channel_count != 0) {
DLOG(ERROR) << "Received invalid AudioBuffer, replace it with EOS.";
return media::AudioBuffer::CreateEOSBuffer();
}
@@ -261,7 +275,6 @@
// 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) {
channel_ptrs[i] = UNSAFE_TODO(input->data.data() + i * size_per_channel);
}
Original Bug Report
Potential OOB Access in Android Mediaserver via DecryptConfig Validation Bypass
Project Fortify, an experimental security project, has identified the following potential security issue.
Overview: A compromised renderer can bypass subsample size validation by supplying a media::mojom::DecoderBuffer with an unencrypted DecryptConfig over Mojo. Due to a missing release-build check, this malicious configuration reaches the Android Mediaserver with an oversized clear_bytes value. This can result in an Out-of-Bounds memory access in the privileged system media service, potentially leading to a sandbox escape.
Affected files:
media/base/android/media_codec_bridge_impl.ccmedia/base/decoder_buffer.ccmedia/gpu/android/codec_wrapper.cc
Estimated timestamp from git blame: 2026-01-13
Description
There is a potential validation bypass in Chrome’s Android media stack that allows a compromised renderer to trigger an Out-of-Bounds (OOB) memory access in the Android Mediaserver process. By manipulating DecryptConfig with an EncryptionScheme::kUnencrypted scheme and a massive clear_bytes value, an attacker can bypass subsample validation. This large value is subsequently passed unchecked via JNI to the system’s MediaCodec service, potentially causing an OOB read or write and facilitating a sandbox escape.
Root Cause Analysis
The vulnerability is the result of a chain of missing checks and logical flaws across the Mojo IPC boundary and the Android media pipeline:
-
Insecure Mojo Deserialization: When the GPU process receives a
media::mojom::DecoderBuffer, it is deserialized usingmojo::TypeConverter. The converter forDecryptConfig(media/mojo/common/media_type_converters.cc) invokes themedia::DecryptConfigconstructor directly. The constructor only preventsEncryptionScheme::kUnencryptedvia aDCHECK_NE, which is compiled out in Official Release builds. Thus, an attacker can successfully instantiate aDecryptConfiglabeled as unencrypted. -
Validation Bypass: In
media/base/decoder_buffer.cc,DecoderBuffer::DoSubsamplesMatch()is responsible for verifying that the sum ofclear_bytesandcypher_bytesmatches the actual buffer size. However, it returns early if the buffer is unencrypted:if (!buffer.is_encrypted()) { return true; }Because
is_encrypted()returns false when the scheme iskUnencrypted, the function returnstrue, completely bypassing theVerifySubsamplesMatchSizecheck. -
Incorrect Dispatch Logic: In
media/gpu/android/codec_wrapper.cc, the code routes the buffer to the secure decoding path based solely on the presence of aDecryptConfigpointer, rather than whether the buffer is actually encrypted:const DecryptConfig* decrypt_config = buffer.decrypt_config(); if (decrypt_config) { result = codec_->QueueSecureInputBuffer(...); } -
Missing Bounds Check in JNI: In
media/base/android/media_codec_bridge_impl.cc,QueueSecureInputBufferprepares the subsample arrays for the Java layer. While it verifies thatsubsamples.cypher_bytesdoes not exceedINT32_MAX, it performs no validation whatsoever onsubsamples.clear_bytes. A massive value like0x7FFFFFFFeasily fits into the signed 32-bit array and is passed to Java, and subsequently to the AndroidMediaCodecsystem API.
Potential Attack Steps
Note: These are suggested steps for how an attacker might exploit this issue, as we do not yet have a working Proof of Concept.
- Gain code execution in the isolated renderer process.
- Establish a Mojo connection to the GPU process to access a video decoding interface (e.g.,
media::mojom::VideoDecoder). - Construct a malicious
media::mojom::DecoderBuffermessage. - Set the
data_sizeof the buffer payload to a small, valid size (e.g., 10 bytes). - Add a
media::mojom::DecryptConfigto the buffer. - Set the
encryption_schemeof theDecryptConfigtomedia::mojom::EncryptionScheme::kUnencrypted. - Add a
SubsampleEntryto thesubsamplesarray, settingclear_bytesto0x7FFFFFFFandcypher_bytesto0. - Set
key_idto a dummy non-empty array andivto a dummy 16-byte array to pass secondary release-buildCHECKs in the constructor. - Send the message to the GPU process. The buffer will bypass size validations, navigate the C++ to Java JNI layer, and reach the system’s
MediaCodecsubsystem. - The Android Mediaserver will attempt to parse
0x7FFFFFFFclear bytes from the shared memory buffer map, triggering an OOB read (and potentially OOB write depending on the underlying codec plugin’s memory operations).
Impact
Successful exploitation of this vulnerability causes memory corruption within the Android Mediaserver (or a specific hardware codec plugin). Given the privileges of the Mediaserver process, this could lead to Remote Code Execution (RCE) outside the Chromium sandbox, resulting in a full sandbox escape.
Evaluated with Chrome root at commit: 9760e6c70cd33a320713361f17c6dcca85648c0f
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.