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
Tracker498728857
Fix commit8624aad6fb1c (chromium/src) +35/-0
CISA KEVNot listed
CreditedGoogle
Disclosed2026-06-02

Changed Functions

FunctionChangeNotes
if
media/mojo/clients/BUILD.gn
modified
TEST_F
media/mojo/clients/mojo_decryptor_unittest.cc
modified

Files Changed

  • media/mojo/clients/BUILD.gn
  • media/mojo/clients/mojo_decryptor_unittest.cc
  • media/mojo/services/mojo_decryptor_service.cc
From 8624aad6fb1c4bd7222efc4560ec71d06169056c Mon Sep 17 00:00:00 2001
From: Feras Aldahlawi <[email protected]>
Date: Fri, 24 Apr 2026 11:12:00 -0700
Subject: [PATCH] media: Fix missing validation in MojoDecryptorService

Add explicit validation to MojoDecryptorService::InitializeVideoDecoder
to reject invalid configurations and terminate the Mojo connection if
detected.

Also add a unit test to verify that invalid configs are rejected and bad
messages are reported.

Bug: b:498728857
Change-Id: Iff7f4aa675a455d2b01279e9e3e372376a6a6964
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/7792238
Reviewed-by: Ted (Chromium) Meyer <[email protected]>
Auto-Submit: Feras Aldahlawi <[email protected]>
Commit-Queue: Ted (Chromium) Meyer <[email protected]>
Cr-Commit-Position: refs/heads/main@{#1620343}
---

diff --git a/media/mojo/clients/BUILD.gn b/media/mojo/clients/BUILD.gn
index 7c5c3a80..66e1242 100644
--- a/media/mojo/clients/BUILD.gn
+++ b/media/mojo/clients/BUILD.gn
@@ -161,6 +161,8 @@
     "//testing/gtest",
   ]
 
+  public_deps = [ "//mojo/public/cpp/test_support:test_utils" ]
+
   if (is_android) {
     sources += [ "mojo_android_overlay_unittest.cc" ]
 
diff --git a/media/mojo/clients/mojo_decryptor_unittest.cc b/media/mojo/clients/mojo_decryptor_unittest.cc
index e118140..36b0fdb7 100644
--- a/media/mojo/clients/mojo_decryptor_unittest.cc
+++ b/media/mojo/clients/mojo_decryptor_unittest.cc
@@ -11,6 +11,7 @@
 #include <utility>
 
 #include "base/functional/bind.h"
+#include "base/functional/callback_helpers.h"
 #include "base/run_loop.h"
 #include "base/test/test_message_loop.h"
 #include "media/base/decryptor.h"
@@ -21,6 +22,7 @@
 #include "media/mojo/mojom/decryptor.mojom.h"
 #include "media/mojo/services/mojo_decryptor_service.h"
 #include "mojo/public/cpp/bindings/receiver.h"
+#include "mojo/public/cpp/test_support/test_utils.h"
 #include "testing/gmock/include/gmock/gmock.h"
 #include "testing/gtest/include/gtest/gtest.h"
 
@@ -387,4 +389,28 @@
   base::RunLoop().RunUntilIdle();
 }
 
+TEST_F(MojoDecryptorTest, InitializeVideoDecoder_InvalidConfig) {
+  Initialize();
+
+  mojo::test::BadMessageObserver bad_message_observer;
+
+
+  EXPECT_CALL(*decryptor_, InitializeVideoDecoder(_, _)).Times(0);
+
+  gfx::Size coded_size(65536, 65536);
+  gfx::Rect visible_rect(0, 0, 65536, 65536);
+  gfx::Size natural_size(65536, 65536);
+  VideoDecoderConfig config(VideoCodec::kVP9, VP9PROFILE_PROFILE3,
+                            VideoDecoderConfig::AlphaMode::kIsOpaque,
+                            VideoColorSpace(), kNoTransformation, coded_size,
+                            visible_rect, natural_size, std::vector<uint8_t>(),
+                            EncryptionScheme());
+
+  mojo_decryptor_->InitializeVideoDecoder(config, base::DoNothing());
+
+  std::string bad_message = bad_message_observer.WaitForBadMessage();
+  EXPECT_EQ(bad_message, "Invalid VideoDecoderConfig");
+  base::RunLoop().RunUntilIdle();
+}
+
 }  // namespace media
diff --git a/media/mojo/services/mojo_decryptor_service.cc b/media/mojo/services/mojo_decryptor_service.cc
index 0476042..27d1cd4 100644
--- a/media/mojo/services/mojo_decryptor_service.cc
+++ b/media/mojo/services/mojo_decryptor_service.cc
@@ -125,6 +125,13 @@
     const VideoDecoderConfig& config,
     InitializeVideoDecoderCallback callback) {
   DVLOG(2) << __func__;
+
+  if (!config.IsValidConfig()) {
+    std::move(callback).Run(false);
+    mojo::ReportBadMessage("Invalid VideoDecoderConfig");
+    return;
+  }
+
   decryptor_->InitializeVideoDecoder(
       config, base::BindOnce(&MojoDecryptorService::OnVideoDecoderInitialized,
                              weak_this_, std::move(callback)));
Loading diff…

Regression Test / PoC

shipped with the fix
diff --git a/media/mojo/clients/mojo_decryptor_unittest.cc b/media/mojo/clients/mojo_decryptor_unittest.cc
index e118140..36b0fdb7 100644
--- a/media/mojo/clients/mojo_decryptor_unittest.cc
+++ b/media/mojo/clients/mojo_decryptor_unittest.cc
@@ -11,6 +11,7 @@
 #include <utility>
 
 #include "base/functional/bind.h"
+#include "base/functional/callback_helpers.h"
 #include "base/run_loop.h"
 #include "base/test/test_message_loop.h"
 #include "media/base/decryptor.h"
@@ -21,6 +22,7 @@
 #include "media/mojo/mojom/decryptor.mojom.h"
 #include "media/mojo/services/mojo_decryptor_service.h"
 #include "mojo/public/cpp/bindings/receiver.h"
+#include "mojo/public/cpp/test_support/test_utils.h"
 #include "testing/gmock/include/gmock/gmock.h"
 #include "testing/gtest/include/gtest/gtest.h"
 
@@ -387,4 +389,28 @@
   base::RunLoop().RunUntilIdle();
 }
 
+TEST_F(MojoDecryptorTest, InitializeVideoDecoder_InvalidConfig) {
+  Initialize();
+
+  mojo::test::BadMessageObserver bad_message_observer;
+
+
+  EXPECT_CALL(*decryptor_, InitializeVideoDecoder(_, _)).Times(0);
+
+  gfx::Size coded_size(65536, 65536);
+  gfx::Rect visible_rect(0, 0, 65536, 65536);
+  gfx::Size natural_size(65536, 65536);
+  VideoDecoderConfig config(VideoCodec::kVP9, VP9PROFILE_PROFILE3,
+                            VideoDecoderConfig::AlphaMode::kIsOpaque,
+                            VideoColorSpace(), kNoTransformation, coded_size,
+                            visible_rect, natural_size, std::vector<uint8_t>(),
+                            EncryptionScheme());
+
+  mojo_decryptor_->InitializeVideoDecoder(config, base::DoNothing());
+
+  std::string bad_message = bad_message_observer.WaitForBadMessage();
+  EXPECT_EQ(bad_message, "Invalid VideoDecoderConfig");
+  base::RunLoop().RunUntilIdle();
+}
+
 }  // namespace media
Loading diff…

Original Bug Report

reported by [email protected]

Missing VideoDecoderConfig validation in MojoDecryptorService

Project Fortify, an experimental security project, has identified the following potential security issue. If you’re a feature owner CC-ed on this bug, please do your best to review these reports without the security team.

Overview: A compromised renderer can send a VideoDecoderConfig with maliciously large video dimensions directly to the CDM process via MojoDecryptorService. Because size validation is missing in the receiving service, these unbounded dimensions are passed directly into the closed-source CDM library. This likely leads to integer overflows during frame buffer allocation, potentially resulting in a heap buffer overflow and code execution within the sandboxed CDM utility process.

Affected files:

  • media/mojo/services/mojo_decryptor_service.cc
  • media/mojo/mojom/video_decoder_config_mojom_traits.cc
  • media/cdm/cdm_adapter.cc

Estimated timestamp from git blame: 2025-12-09

Summary

There is a potential missing validation check in MojoDecryptorService::InitializeVideoDecoder that could allow a compromised renderer to trigger memory corruption within the sandboxed Content Decryption Module (CDM) utility process.

The Mojo deserialization traits for media::mojom::VideoDecoderConfig deliberately do not call IsValidConfig() to enforce dimension limits, delegating this responsibility to the receiving services. While MojoVideoDecoderService correctly enforces this check, MojoDecryptorService does not, allowing arbitrarily large video dimensions to be passed directly into third-party, closed-source CDM libraries (like Widevine).

Technical Details

  1. Deserialization bypasses limits: When a VideoDecoderConfig is sent over IPC, StructTraits<...>::Read in media/mojo/mojom/video_decoder_config_mojom_traits.cc reconstructs the object. The traits for gfx::Size only verify that width and height are non-negative. Crucially, the traits do not call config.IsValidConfig(), allowing the creation of a config with dimensions that far exceed media::limits::kMaxDimension (32767).
  2. Missing Service Validation: In media/mojo/services/mojo_decryptor_service.cc, InitializeVideoDecoder receives this config and forwards it immediately to the underlying decryptor_ (typically a CdmAdapter). It fails to validate the config or invoke mojo::ReportBadMessage.
  3. ABI Translation: In CdmAdapter::InitializeVideoDecoder (media/cdm/cdm_adapter.cc), the unvalidated VideoDecoderConfig is translated into a cdm::VideoDecoderConfig_3 struct. The malicious coded_size values are blindly copied into the ABI struct.
  4. Library Execution: The CdmAdapter passes the struct into the closed-source CDM library. Video decoders typically calculate frame buffer allocations by multiplying width and height (e.g., width * height * 1.5 for YUV). Passing maximum 32-bit integer values guarantees an integer overflow, leading to an undersized heap allocation and a subsequent heap buffer overflow when the library attempts to write video data.

Potential Attack Steps

Note: These are potential steps based on code analysis; our tooling agent does not yet have the ability to run code to provide a verified proof-of-concept.

  1. An attacker compromises a sandboxed Renderer process (e.g., via a V8 bug).
  2. The attacker uses media::mojom::InterfaceFactory::CreateCdm to request a CDM, which launches the CDM utility process and returns a CdmContext containing a direct pending_remote<media::mojom::Decryptor> pipe.
  3. The attacker crafts a malicious VideoDecoderConfig containing a valid codec but extremely large coded_size dimensions (e.g., width = 65536, height = 65536 or INT_MAX).
  4. The attacker calls InitializeVideoDecoder on the Decryptor remote, sending the malicious config.
  5. The CDM utility process receives the unvalidated dimensions, triggering an integer overflow and heap buffer overflow inside the third-party CDM library, leading to arbitrary code execution within the CDM process.

Suggested Fix

Add explicit validation to MojoDecryptorService::InitializeVideoDecoder to reject invalid configurations and terminate the Mojo connection if one is detected.

void MojoDecryptorService::InitializeVideoDecoder(
    const VideoDecoderConfig& config,
    InitializeVideoDecoderCallback callback) {
  DVLOG(2) << __func__;
  
  if (!config.IsValidConfig()) {
    mojo::ReportBadMessage("Invalid VideoDecoderConfig");
    return;
  }

  decryptor_->InitializeVideoDecoder(
      config, base::BindOnce(&MojoDecryptorService::OnVideoDecoderInitialized,
                             weak_this_, std::move(callback)));
}

Evaluated with Chrome root at commit: ff3d2b74fa39431785bd60e51463b08fcc71ee33


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