Medium chrome Logic Error 📄 Reporter bug report 🔧 Commit mapped

Overview

Medium
Severity
CVSS
No
Exploited ITW
Fixed
Fix Status
ImpactInsufficient validation of untrusted input in Media
DescriptionInsufficient validation of untrusted input in Media
ComponentMedia
Bug ClassLogic Error
Tracker498700369
Fix commit8fa45a02c280 (chromium/src) +112/-15
CISA KEVNot listed
CreditedGoogle
Disclosed2026-06-02

Changed Functions

FunctionChangeNotes
TEST
media/mojo/common/media_type_converters_unittest.cc
modified
if
media/mojo/common/mojo_decoder_buffer_converter.cc
modified
TEST
media/mojo/common/mojo_decoder_buffer_converter_unittest.cc
modified
if
media/mojo/services/mojo_demuxer_stream_adapter.cc
modified

Files Changed

  • media/mojo/common/media_type_converters.cc
  • media/mojo/common/media_type_converters_unittest.cc
  • media/mojo/common/mojo_decoder_buffer_converter.cc
  • media/mojo/common/mojo_decoder_buffer_converter_unittest.cc
  • media/mojo/services/mojo_demuxer_stream_adapter.cc
From 8fa45a02c280f88fc8f82d400c67e3d924c2bc7e Mon Sep 17 00:00:00 2001
From: Sangbaek Park <sangbaekpark@google.com>
Date: Wed, 22 Apr 2026 09:55:17 -0700
Subject: [PATCH] media: Validate DecoderBuffer subsample metadata from untrusted sources

This change hardens the Media Foundation-based decryption path on
Windows by adding validation checks for `DecoderBuffer` subsample
metadata. A compromised renderer could previously send a malformed
`DecoderBuffer` over a Mojo pipe, where the sum of clear and encrypted
bytes in its `DecryptConfig` subsamples exceeded the buffer's actual
data size. This unvalidated metadata could lead to out-of-bounds memory
access in the higher-privilege Media Foundation CDM utility process,
potentially resulting in a sandbox escape.

To mitigate this, validation is now performed at multiple layers:

1.  Mojo Type Conversion (`media_type_converters.cc`): Added a check
using `media::DecoderBuffer::DoSubsamplesMatch()` during
`DecoderBuffer` deserialization to ensure the integrity of
`DecryptConfig` subsamples against the buffer's actual data size. If
validation fails, the deserialization returns `nullptr`.
2.  Mojo Decoder Buffer Handling
(`mojo_decoder_buffer_converter.cc`): Modified
`MojoDecoderBufferReader::ReadDecoderBuffer` to gracefully handle
`nullptr` returns from the `TypeConverter` (due to validation failures)
by immediately invoking the callback with `nullptr` instead of a
`DCHECK`.
3.  Demuxer Stream Adapter (`mojo_demuxer_stream_adapter.cc`): Added an
explicit check using `media::DecoderBuffer::DoSubsamplesMatch()` in
`MojoDemuxerStreamAdapter::OnBufferRead` to verify subsample integrity
immediately upon receiving a decrypted buffer from the renderer. If the
check fails, the read operation is stopped with a `kError`.

Tests: { MediaTypeConvertersTest.RejectOOBSubsample,
MojoDemuxerStreamAdapterTest.ReadAbortedOnOOBSubsample,
MojoDecoderBufferConverterTest.ReadMalformedDecoderBuffer }

Bug: 498700369
Change-Id: Idc643560381bd24844d0590e5778bbe91d03bc74
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/7782949
Reviewed-by: Dale Curtis <dalecurtis@chromium.org>
Commit-Queue: Sangbaek Park <sangbaekpark@chromium.org>
Reviewed-by: Nasko Oskov <nasko@chromium.org>
Cr-Commit-Position: refs/heads/main@{#1618940}
---

diff --git a/media/mojo/common/media_type_converters.cc b/media/mojo/common/media_type_converters.cc
index 22631c2a..0630476 100644
--- a/media/mojo/common/media_type_converters.cc
+++ b/media/mojo/common/media_type_converters.cc
@@ -190,6 +190,11 @@
       return nullptr;
     }
     buffer->set_decrypt_config(std::move(decrypt_config));
+
+    if (!media::DecoderBuffer::DoSubsamplesMatch(*buffer)) {
+      DVLOG(1) << __func__ << ": Subsamples do not match buffer size";
+      return nullptr;
+    }
   }
 
   // TODO(dalecurtis): We intentionally do not deserialize the data section of
diff --git a/media/mojo/common/media_type_converters_unittest.cc b/media/mojo/common/media_type_converters_unittest.cc
index 298fe2db..31d3568 100644
--- a/media/mojo/common/media_type_converters_unittest.cc
+++ b/media/mojo/common/media_type_converters_unittest.cc
@@ -172,10 +172,7 @@
   const char kKeyId[] = "00112233445566778899aabbccddeeff";
   const char kIv[] = "0123456789abcdef";
 
-  std::vector<SubsampleEntry> subsamples;
-  subsamples.push_back(SubsampleEntry(10, 20));
-  subsamples.push_back(SubsampleEntry(30, 40));
-  subsamples.push_back(SubsampleEntry(50, 60));
+  std::vector<SubsampleEntry> subsamples = {SubsampleEntry(5, 8)};
 
   // Original.
   scoped_refptr<DecoderBuffer> buffer(DecoderBuffer::CopyFrom(kData));
@@ -207,10 +204,7 @@
   const char kKeyId[] = "00112233445566778899aabbccddeeff";
   const char kIv[] = "0123456789abcdef";
 
-  std::vector<SubsampleEntry> subsamples;
-  subsamples.push_back(SubsampleEntry(10, 20));
-  subsamples.push_back(SubsampleEntry(30, 40));
-  subsamples.push_back(SubsampleEntry(50, 60));
+  std::vector<SubsampleEntry> subsamples = {SubsampleEntry(5, 8)};
 
   auto pattern = EncryptionPattern::Create(1, 2);
 
@@ -301,4 +295,28 @@
   CompareAudioBuffers(kSampleFormatPlanarF32, *buffer, *result);
 }
 
+// This test ensures that a `mojom::DecoderBuffer` with maliciously oversized
+// subsamples is correctly rejected during conversion to a C++ `DecoderBuffer`,
+// resulting in a `nullptr` return.
+TEST(MediaTypeConvertersTest, RejectOOBSubsample) {
+  const size_t kDataSize = 100;
+  auto buffer = base::MakeRefCounted<DecoderBuffer>(kDataSize);
+
+  std::vector<SubsampleEntry> subsamples;
+  // Set a malicious DecryptConfig with subsamples larger than data size
+  subsamples.push_back(SubsampleEntry(50, 500));
+
+  buffer->set_decrypt_config(DecryptConfig::CreateCencConfig(
+      "key_id", "0123456789abcdef", subsamples));
+
+  // Convert to Mojom
+  mojom::DecoderBufferPtr mojo_buffer = mojom::DecoderBuffer::From(*buffer);
+
+  // Convert back to DecoderBuffer
+  scoped_refptr<DecoderBuffer> converted_buffer =
+      mojo_buffer.To<scoped_refptr<DecoderBuffer>>();
+
+  EXPECT_FALSE(converted_buffer);
+}
+
 }  // namespace media
diff --git a/media/mojo/common/mojo_decoder_buffer_converter.cc b/media/mojo/common/mojo_decoder_buffer_converter.cc
index a38389f..915d7a1 100644
--- a/media/mojo/common/mojo_decoder_buffer_converter.cc
+++ b/media/mojo/common/mojo_decoder_buffer_converter.cc
@@ -132,7 +132,11 @@
 
   scoped_refptr<DecoderBuffer> media_buffer(
       mojo_buffer.To<scoped_refptr<DecoderBuffer>>());
-  DCHECK(media_buffer);
+  if (!media_buffer) {
+    std::move(read_cb).Run(nullptr);
+    OnPipeError(MOJO_RESULT_INVALID_ARGUMENT);
+    return;
+  }
 
   if (MediaTraceIsEnabled() && !media_buffer->end_of_stream()) {
     TRACE_EVENT_BEGIN(
diff --git a/media/mojo/common/mojo_decoder_buffer_converter_unittest.cc b/media/mojo/common/mojo_decoder_buffer_converter_unittest.cc
index d0312d5..6690ace 100644
--- a/media/mojo/common/mojo_decoder_buffer_converter_unittest.cc
+++ b/media/mojo/common/mojo_decoder_buffer_converter_unittest.cc
@@ -14,6 +14,8 @@
 #include "base/test/task_environment.h"
 #include "media/base/decoder_buffer.h"
 #include "media/base/decrypt_config.h"
+#include "media/base/subsample_entry.h"
+#include "media/mojo/common/media_type_converters.h"
 #include "testing/gmock/include/gmock/gmock.h"
 #include "testing/gtest/include/gtest/gtest.h"
 
@@ -114,10 +116,7 @@
   const char kKeyId[] = "00112233445566778899aabbccddeeff";
   const char kIv[] = "0123456789abcdef";
 
-  std::vector<SubsampleEntry> subsamples;
-  subsamples.push_back(SubsampleEntry(10, 20));
-  subsamples.push_back(SubsampleEntry(30, 40));
-  subsamples.push_back(SubsampleEntry(50, 60));
+  std::vector<SubsampleEntry> subsamples = {SubsampleEntry(5, 8)};
 
   scoped_refptr<DecoderBuffer> buffer(DecoderBuffer::CopyFrom(kData));
   buffer->set_decrypt_config(
@@ -418,4 +417,31 @@
   run_loop.Run();
 }
 
+// This test confirms that `MojoDecoderBufferReader` correctly handles a
+// `nullptr` `DecoderBuffer` returned by the `TypeConverter` (due to subsample
+// validation failure) by invoking its read callback with `nullptr`, preventing
+// a `DCHECK` crash.
+TEST(MojoDecoderBufferConverterTest, ReadMalformedDecoderBuffer) {
+  base::test::SingleThreadTaskEnvironment task_environment;
+  base::RunLoop run_loop;
+  MojoDecoderBufferConverter converter;
+
+  // Create a buffer and attach a malformed DecryptConfig (subsamples larger
+  // than data size).
+  const uint8_t kData[] = "Hello, world";
+  auto media_buffer = DecoderBuffer::CopyFrom(kData);
+  std::vector<SubsampleEntry> subsamples;
+  subsamples.emplace_back(SubsampleEntry(50, 500));
+  media_buffer->set_decrypt_config(DecryptConfig::CreateCencConfig(
+      "key_id", "0123456789abcdef", subsamples));
+
+  auto mojo_buffer = mojom::DecoderBuffer::From(*media_buffer);
+  base::MockCallback<MojoDecoderBufferReader::ReadCB> mock_cb;
+  EXPECT_CALL(mock_cb, Run(testing::IsNull()))
+      .WillOnce(testing::InvokeWithoutArgs(&run_loop, &base::RunLoop::Quit));
+
+  converter.reader->ReadDecoderBuffer(std::move(mojo_buffer), mock_cb.Get());
+  run_loop.Run();
+}
+
 }  // namespace media
diff --git a/media/mojo/services/mojo_demuxer_stream_adapter.cc b/media/mojo/services/mojo_demuxer_stream_adapter.cc
index 4c3364b3..bb9ef291 100644
--- a/media/mojo/services/mojo_demuxer_stream_adapter.cc
+++ b/media/mojo/services/mojo_demuxer_stream_adapter.cc
@@ -129,9 +129,17 @@
   if (!buffer) {
     DVLOG(1) << __func__ << ": null buffer";
Loading diff…

Regression Test / PoC

shipped with the fix
diff --git a/media/mojo/common/media_type_converters_unittest.cc b/media/mojo/common/media_type_converters_unittest.cc
index 298fe2db..31d3568 100644
--- a/media/mojo/common/media_type_converters_unittest.cc
+++ b/media/mojo/common/media_type_converters_unittest.cc
@@ -172,10 +172,7 @@
   const char kKeyId[] = "00112233445566778899aabbccddeeff";
   const char kIv[] = "0123456789abcdef";
 
-  std::vector<SubsampleEntry> subsamples;
-  subsamples.push_back(SubsampleEntry(10, 20));
-  subsamples.push_back(SubsampleEntry(30, 40));
-  subsamples.push_back(SubsampleEntry(50, 60));
+  std::vector<SubsampleEntry> subsamples = {SubsampleEntry(5, 8)};
 
   // Original.
   scoped_refptr<DecoderBuffer> buffer(DecoderBuffer::CopyFrom(kData));
@@ -207,10 +204,7 @@
   const char kKeyId[] = "00112233445566778899aabbccddeeff";
   const char kIv[] = "0123456789abcdef";
 
-  std::vector<SubsampleEntry> subsamples;
-  subsamples.push_back(SubsampleEntry(10, 20));
-  subsamples.push_back(SubsampleEntry(30, 40));
-  subsamples.push_back(SubsampleEntry(50, 60));
+  std::vector<SubsampleEntry> subsamples = {SubsampleEntry(5, 8)};
 
   auto pattern = EncryptionPattern::Create(1, 2);
 
@@ -301,4 +295,28 @@
   CompareAudioBuffers(kSampleFormatPlanarF32, *buffer, *result);
 }
 
+// This test ensures that a `mojom::DecoderBuffer` with maliciously oversized
+// subsamples is correctly rejected during conversion to a C++ `DecoderBuffer`,
+// resulting in a `nullptr` return.
+TEST(MediaTypeConvertersTest, RejectOOBSubsample) {
+  const size_t kDataSize = 100;
+  auto buffer = base::MakeRefCounted<DecoderBuffer>(kDataSize);
+
+  std::vector<SubsampleEntry> subsamples;
+  // Set a malicious DecryptConfig with subsamples larger than data size
+  subsamples.push_back(SubsampleEntry(50, 500));
+
+  buffer->set_decrypt_config(DecryptConfig::CreateCencConfig(
+      "key_id", "0123456789abcdef", subsamples));
+
+  // Convert to Mojom
+  mojom::DecoderBufferPtr mojo_buffer = mojom::DecoderBuffer::From(*buffer);
+
+  // Convert back to DecoderBuffer
+  scoped_refptr<DecoderBuffer> converted_buffer =
+      mojo_buffer.To<scoped_refptr<DecoderBuffer>>();
+
+  EXPECT_FALSE(converted_buffer);
+}
+
 }  // namespace media
diff --git a/media/mojo/common/mojo_decoder_buffer_converter_unittest.cc b/media/mojo/common/mojo_decoder_buffer_converter_unittest.cc
index d0312d5..6690ace 100644
--- a/media/mojo/common/mojo_decoder_buffer_converter_unittest.cc
+++ b/media/mojo/common/mojo_decoder_buffer_converter_unittest.cc
@@ -14,6 +14,8 @@
 #include "base/test/task_environment.h"
 #include "media/base/decoder_buffer.h"
 #include "media/base/decrypt_config.h"
+#include "media/base/subsample_entry.h"
+#include "media/mojo/common/media_type_converters.h"
 #include "testing/gmock/include/gmock/gmock.h"
 #include "testing/gtest/include/gtest/gtest.h"
 
@@ -114,10 +116,7 @@
   const char kKeyId[] = "00112233445566778899aabbccddeeff";
   const char kIv[] = "0123456789abcdef";
 
-  std::vector<SubsampleEntry> subsamples;
-  subsamples.push_back(SubsampleEntry(10, 20));
-  subsamples.push_back(SubsampleEntry(30, 40));
-  subsamples.push_back(SubsampleEntry(50, 60));
+  std::vector<SubsampleEntry> subsamples = {SubsampleEntry(5, 8)};
 
   scoped_refptr<DecoderBuffer> buffer(DecoderBuffer::CopyFrom(kData));
   buffer->set_decrypt_config(
@@ -418,4 +417,31 @@
   run_loop.Run();
 }
 
+// This test confirms that `MojoDecoderBufferReader` correctly handles a
+// `nullptr` `DecoderBuffer` returned by the `TypeConverter` (due to subsample
+// validation failure) by invoking its read callback with `nullptr`, preventing
+// a `DCHECK` crash.
+TEST(MojoDecoderBufferConverterTest, ReadMalformedDecoderBuffer) {
+  base::test::SingleThreadTaskEnvironment task_environment;
+  base::RunLoop run_loop;
+  MojoDecoderBufferConverter converter;
+
+  // Create a buffer and attach a malformed DecryptConfig (subsamples larger
+  // than data size).
+  const uint8_t kData[] = "Hello, world";
+  auto media_buffer = DecoderBuffer::CopyFrom(kData);
+  std::vector<SubsampleEntry> subsamples;
+  subsamples.emplace_back(SubsampleEntry(50, 500));
+  media_buffer->set_decrypt_config(DecryptConfig::CreateCencConfig(
+      "key_id", "0123456789abcdef", subsamples));
+
+  auto mojo_buffer = mojom::DecoderBuffer::From(*media_buffer);
+  base::MockCallback<MojoDecoderBufferReader::ReadCB> mock_cb;
+  EXPECT_CALL(mock_cb, Run(testing::IsNull()))
+      .WillOnce(testing::InvokeWithoutArgs(&run_loop, &base::RunLoop::Quit));
+
+  converter.reader->ReadDecoderBuffer(std::move(mojo_buffer), mock_cb.Get());
+  run_loop.Run();
+}
+
 }  // namespace media
diff --git a/media/mojo/services/mojo_demuxer_stream_adapter_unittest.cc b/media/mojo/services/mojo_demuxer_stream_adapter_unittest.cc
index 1ef1faf4..c7a94d1 100644
--- a/media/mojo/services/mojo_demuxer_stream_adapter_unittest.cc
+++ b/media/mojo/services/mojo_demuxer_stream_adapter_unittest.cc
@@ -2,6 +2,8 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
+#include "media/mojo/services/mojo_demuxer_stream_adapter.h"
+
 #include <memory>
 
 #include "base/run_loop.h"
@@ -10,11 +12,12 @@
 #include "base/test/gmock_callback_support.h"
 #include "base/test/task_environment.h"
 #include "media/base/decoder_buffer.h"
+#include "media/base/decrypt_config.h"
 #include "media/base/demuxer_stream.h"
 #include "media/base/mock_filters.h"
+#include "media/base/subsample_entry.h"
 #include "media/base/test_helpers.h"
 #include "media/mojo/clients/mojo_demuxer_stream_impl.h"
-#include "media/mojo/services/mojo_demuxer_stream_adapter.h"
 #include "testing/gtest/include/gtest/gtest.h"
 
 using ::base::test::RunOnceCallback;
@@ -207,4 +210,37 @@
   loop.RunUntilIdle();
 }
 
+// This test verifies that the `MojoDemuxerStreamAdapter` properly identifies
+// and stops a read operation when presented with a `DecoderBuffer` containing
+// malformed subsample data.
+TEST_F(MojoDemuxerStreamAdapterTest, ReadAbortedOnOOBSubsample) {
+  Initialize(DemuxerStream::Type::AUDIO);
+
+  base::RunLoop abort_read_loop;
+  DemuxerStream::DecoderBufferVector buffers;
+
+  auto buffer = base::MakeRefCounted<DecoderBuffer>(100);
+  std::vector<SubsampleEntry> subsamples;
+  // Set a malicious DecryptConfig with subsamples describing more bytes than
+  // data size
+  subsamples.emplace_back(SubsampleEntry(50, 500));
+  buffer->set_decrypt_config(DecryptConfig::CreateCencConfig(
+      "key_id", "0123456789abcdef", subsamples));
+
+  buffers.push_back(buffer);
+
+  EXPECT_CALL(*stream_, OnRead(_))
+      .WillOnce(RunOnceCallback<0>(DemuxerStream::Status::kOk, buffers));
+
+  auto done_cb = base::BindLambdaForTesting(
+      [&](DemuxerStream::Status status,
+          DemuxerStream::DecoderBufferVector read_buffers) {
+        EXPECT_EQ(status, DemuxerStream::Status::kError);
+        EXPECT_TRUE(read_buffers.empty());
+        abort_read_loop.QuitWhenIdle();
+      });
+  ReadBuffer(1, done_cb);
+  abort_read_loop.Run();
+}
+
 }  // namespace media
Loading diff…

Original Bug Report

reported by er...@chromium.org

Missing validation of subsample metadata in MediaFoundation decryption path allows sandbox escape

Project Fortify, an experimental security project, has identified the following potential security issue.

Overview: A compromised renderer can provide malicious subsample metadata in a DecoderBuffer that exceeds the buffer’s data size. This unvalidated metadata is passed to the Media Foundation (MF) utility process and converted into an IMFSample, potentially triggering an out-of-bounds memory access in an MF decryptor. This can lead to a sandbox escape from the renderer into the higher-privilege MF CDM utility process on Windows.

Affected files:

  • media/base/win/mf_helpers.cc
  • media/mojo/services/mojo_demuxer_stream_adapter.cc
  • media/renderers/win/media_foundation_stream_wrapper.cc
  • media/mojo/common/media_type_converters.cc

Estimated timestamp from git blame: 2024-01-30

Vulnerability Summary

A potential security vulnerability exists in the MediaFoundation-based decryption path on Windows where subsample geometry provided by a renderer process is not validated before being passed to the Media Foundation (MF) utility process. A compromised renderer can supply a DecoderBuffer over the mojom::DemuxerStream Mojo pipe with a DecryptConfig whose subsamples describe more bytes than are actually present in the buffer’s data. This unvalidated metadata is subsequently used to create an IMFSample attribute in the MF CDM utility process, leading to a potential out-of-bounds (OOB) read or write.

Technical Details

In the MediaFoundationRenderer implementation, media data flows from the MojoDemuxerStreamImpl in the renderer process to the MojoDemuxerStreamAdapter in the MF utility process. The vulnerability arises from a lack of validation during this data flow:

  1. Missing Validation on Reception: When a DecoderBuffer is received via Mojo in the MF utility process, MojoDemuxerStreamAdapter::OnBufferRead (media/mojo/services/mojo_demuxer_stream_adapter.cc:127) processes the buffer but fails to call media::DecoderBuffer::DoSubsamplesMatch(). This helper function is used in other decryption paths (like DecryptingVideoDecoder) to ensure that the sum of clear and encrypted bytes in the subsamples matches the buffer’s data size.
  2. Unvalidated Type Conversion: The media::TypeConverter for DecoderBuffer in media/mojo/common/media_type_converters.cc deserializes the Mojo struct and populates the DecryptConfig and its subsamples without any integrity checks against the buffer’s overall size.
  3. Insecure IMFSample Creation: The unvalidated buffer is passed to GenerateSampleFromDecoderBuffer in media/base/win/mf_helpers.cc:673 (called by media/renderers/win/media_foundation_stream_wrapper.cc). This function translates the DecoderBuffer into a Windows Media Foundation IMFSample. It allocates an OS-level IMFMediaBuffer of size N (the actual data size) and copies the data into it.
  4. Malicious Metadata Attachment: Because the buffer is encrypted, AddEncryptAttributes (media/base/win/mf_helpers.cc:150) is called. This helper iterates through the unvalidated decrypt_config.subsamples() and attaches them as an MFSampleExtension_Encryption_SubSample_Mapping blob to the IMFSample without any bounds checking against the N byte data length.
  5. OOB Memory Access: The resulting IMFSample contains a memory buffer of size N, but the subsample mapping attribute describes a size M (where M > N). When this sample is processed by a decrypting Media Foundation Transform (MFT), such as the PlayReady MFT in the MF CDM utility process, the MFT may rely on the provided subsample mapping. If the MFT iterates through this mapping without re-validating it against the buffer’s actual length, it results in an OOB read. For in-place decryption (such as AES-CTR or AES-CBC), this also results in an OOB write.

Impact

An attacker who has compromised the renderer process can potentially achieve memory corruption (OOB read/write) in the Media Foundation CDM utility process. As defined in sandbox/policy/win/sandbox_win.cc:581, this process runs in a less restrictive sandbox with JobLevel::kInteractive to allow it to launch the protected media pipeline process (mfpmp.exe). Furthermore, it runs with the LPAC USER_UNPROTECTED token, granting more privileges than the renderer. Exploiting this OOB write allows the attacker to execute arbitrary code within this less-restricted context, achieving a sandbox escape and privilege escalation on Windows systems using hardware-secure EME playback.

Suggested Attack Steps

(Note: These are potential steps; our tooling agent does not have the ability to run code to provide a working PoC.)

  1. Compromise a renderer process and initiate encrypted media playback (e.g., via Encrypted Media Extensions) to establish a connection to the Media Foundation CDM utility process.
  2. Construct a malicious media::mojom::DecoderBuffer message to send to the utility process over the mojom::DemuxerStream interface.
  3. Set the data_size of the Mojo DecoderBuffer to a valid value N (e.g., 100 bytes).
  4. Populate the decrypt_config field of the Mojo DecoderBuffer with an array of subsamples where the sum of clear_bytes and cipher_bytes equals M, where M is significantly larger than N (e.g., 1000 bytes).
  5. Send the metadata over the Mojo message pipe, and the N bytes of actual media data over the associated Mojo DataPipe.
  6. The MF CDM utility process will deserialize the buffer, create an IMFSample with an N-byte buffer and the M-byte subsample mapping, and pass it to a decrypting MFT, potentially triggering the OOB memory access.

The media::DecoderBuffer::DoSubsamplesMatch() check must be enforced when receiving DecoderBuffer objects from a Mojo stream in the Media Foundation renderer path. This validation should occur as soon as the data is received and reassembled from the untrusted renderer process.

Specifically, a check like if (buffer->is_encrypted() && !media::DecoderBuffer::DoSubsamplesMatch(*buffer)) should be added to MojoDemuxerStreamAdapter::OnBufferRead in media/mojo/services/mojo_demuxer_stream_adapter.cc:127. If the subsamples do not match the buffer size, the stream adapter should abort the read or fail gracefully.

Evaluated with Chrome root at commit: 4859e669de60239572397b559c17dfab074511e9


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. And please feel free to reach out to me directly if you have concerns or feedback on the project.

View on issue tracker