CVE-2026-10917
Overview
Changed Functions
| Function | Change | Notes |
|---|---|---|
TEST_Fchromeos/components/cdm_factory_daemon/content_decryption_module_adapter_unittest.cc |
modified | |
ifchromeos/components/cdm_factory_daemon/mojom/decrypt_config_mojom_traits.cc |
modified | |
TESTchromeos/components/cdm_factory_daemon/mojom/decrypt_config_mojom_traits_unittest.cc |
modified |
Files Changed
chromecast/starboard/media/media/drm_util_test.ccchromecast/starboard/media/media/starboard_audio_decoder_test.ccchromecast/starboard/media/media/starboard_video_decoder_test.ccchromeos/components/cdm_factory_daemon/content_decryption_module_adapter_unittest.ccchromeos/components/cdm_factory_daemon/mojom/decrypt_config_mojom_traits.ccchromeos/components/cdm_factory_daemon/mojom/decrypt_config_mojom_traits_unittest.cc
Patch
From 006ccbd6235f102a372481b003a989aae9e4d021 Mon Sep 17 00:00:00 2001 From: Vikram Pasupathy <[email protected]> Date: Thu, 09 Apr 2026 23:47:24 -0700 Subject: [PATCH] media: Validate EncryptionPattern bounds during deserialization Previously, the Mojo IPC interface accepted arbitrary 32-bit integers for the `crypt_byte_block` and `skip_byte_block` fields of `EncryptionPattern`, despite the ISO specification limiting them to 4 bits (max 15). This CL makes the following changes: 1. Introduced a centralized `EncryptionPattern::Create()` factory. 2. All callers of EncryptionPattern(a,b) now use Create(). 3. The MP4 (MSE) stream parser now explicitly validates the pattern values during box parsing, preventing "silent renderer kills" by failing with a media error instead of letting the malformed data reach the Mojo boundary. Bug: 497929481 Change-Id: Iee4f6fca458456f0c4d988b212d7af3374d3fcbb Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/7728010 Reviewed-by: Dale Curtis <[email protected]> Reviewed-by: Yuchen Liu <[email protected]> Commit-Queue: Vikram Pasupathy <[email protected]> Reviewed-by: Pilar Molina Lopez <[email protected]> Reviewed-by: Fred Shih <[email protected]> Cr-Commit-Position: refs/heads/main@{#1612648} --- diff --git a/chromecast/starboard/media/media/drm_util_test.cc b/chromecast/starboard/media/media/drm_util_test.cc index 2c9747b02..5005823 100644 --- a/chromecast/starboard/media/media/drm_util_test.cc +++ b/chromecast/starboard/media/media/drm_util_test.cc @@ -109,7 +109,8 @@ constexpr std::string_view kIv = "abcdefghijklmnop"; CHECK_EQ(kIv.size(), static_cast<size_t>(::media::DecryptConfig::kDecryptionKeySize)); - const ::media::EncryptionPattern encryption_pattern(10, 20); + const auto encryption_pattern = ::media::EncryptionPattern::Create(10, 5); + CHECK(encryption_pattern.has_value()); const ::media::SubsampleEntry subsample(1, 6); StarboardDrmSubSampleMapping sb_subsample; sb_subsample.clear_byte_count = subsample.clear_bytes; diff --git a/chromecast/starboard/media/media/starboard_audio_decoder_test.cc b/chromecast/starboard/media/media/starboard_audio_decoder_test.cc index af77ace..07bd76f5 100644 --- a/chromecast/starboard/media/media/starboard_audio_decoder_test.cc +++ b/chromecast/starboard/media/media/starboard_audio_decoder_test.cc @@ -368,9 +368,9 @@ EXPECT_EQ(actual_drm_info.encryption_scheme, kStarboardDrmEncryptionSchemeAesCbc); EXPECT_EQ(actual_drm_info.encryption_pattern.crypt_byte_block, - encryption_pattern.crypt_byte_block()); + encryption_pattern->crypt_byte_block()); EXPECT_EQ(actual_drm_info.encryption_pattern.skip_byte_block, - encryption_pattern.skip_byte_block()); + encryption_pattern->skip_byte_block()); EXPECT_THAT(std::string(reinterpret_cast<const char*>( actual_drm_info.initialization_vector), actual_drm_info.initialization_vector_size), @@ -508,9 +508,9 @@ EXPECT_EQ(actual_drm_info.encryption_scheme, kStarboardDrmEncryptionSchemeAesCbc); EXPECT_EQ(actual_drm_info.encryption_pattern.crypt_byte_block, - encryption_pattern.crypt_byte_block()); + encryption_pattern->crypt_byte_block()); EXPECT_EQ(actual_drm_info.encryption_pattern.skip_byte_block, - encryption_pattern.skip_byte_block()); + encryption_pattern->skip_byte_block()); EXPECT_THAT(std::string(reinterpret_cast<const char*>( actual_drm_info.initialization_vector), actual_drm_info.initialization_vector_size), diff --git a/chromecast/starboard/media/media/starboard_video_decoder_test.cc b/chromecast/starboard/media/media/starboard_video_decoder_test.cc index 65e10971..e7dabc0 100644 --- a/chromecast/starboard/media/media/starboard_video_decoder_test.cc +++ b/chromecast/starboard/media/media/starboard_video_decoder_test.cc @@ -298,9 +298,9 @@ EXPECT_EQ(actual_drm_info.encryption_scheme, kStarboardDrmEncryptionSchemeAesCbc); EXPECT_EQ(actual_drm_info.encryption_pattern.crypt_byte_block, - encryption_pattern.crypt_byte_block()); + encryption_pattern->crypt_byte_block()); EXPECT_EQ(actual_drm_info.encryption_pattern.skip_byte_block, - encryption_pattern.skip_byte_block()); + encryption_pattern->skip_byte_block()); EXPECT_THAT(std::string(reinterpret_cast<const char*>( actual_drm_info.initialization_vector), actual_drm_info.initialization_vector_size), @@ -437,9 +437,9 @@ EXPECT_EQ(actual_drm_info.encryption_scheme, kStarboardDrmEncryptionSchemeAesCbc); EXPECT_EQ(actual_drm_info.encryption_pattern.crypt_byte_block, - encryption_pattern.crypt_byte_block()); + encryption_pattern->crypt_byte_block()); EXPECT_EQ(actual_drm_info.encryption_pattern.skip_byte_block, - encryption_pattern.skip_byte_block()); + encryption_pattern->skip_byte_block()); EXPECT_THAT(std::string(reinterpret_cast<const char*>( actual_drm_info.initialization_vector), actual_drm_info.initialization_vector_size), diff --git a/chromeos/components/cdm_factory_daemon/content_decryption_module_adapter_unittest.cc b/chromeos/components/cdm_factory_daemon/content_decryption_module_adapter_unittest.cc index f7a571a..cc48285 100644 --- a/chromeos/components/cdm_factory_daemon/content_decryption_module_adapter_unittest.cc +++ b/chromeos/components/cdm_factory_daemon/content_decryption_module_adapter_unittest.cc @@ -518,7 +518,7 @@ scoped_refptr<media::DecoderBuffer> encrypted_buffer = CreateDecoderBuffer(kFakeEncryptedData); encrypted_buffer->set_decrypt_config(media::DecryptConfig::CreateCbcsConfig( - kFakeKeyId, kFakeIv, {}, media::EncryptionPattern(6, 9))); + kFakeKeyId, kFakeIv, {}, media::EncryptionPattern::Create(6, 9))); cdm_adapter_->Decrypt(media::Decryptor::kVideo, encrypted_buffer, callback.Get()); base::RunLoop().RunUntilIdle(); @@ -537,7 +537,7 @@ scoped_refptr<media::DecoderBuffer> encrypted_buffer = CreateDecoderBuffer(kFakeEncryptedData); encrypted_buffer->set_decrypt_config(media::DecryptConfig::CreateCbcsConfig( - kFakeKeyId, kFakeIv, {}, media::EncryptionPattern(6, 9))); + kFakeKeyId, kFakeIv, {}, media::EncryptionPattern::Create(6, 9))); cdm_adapter_->Decrypt(media::Decryptor::kVideo, encrypted_buffer, callback.Get()); base::RunLoop().RunUntilIdle(); @@ -558,8 +558,8 @@ TEST_F(ContentDecryptionModuleAdapterTest, Decrypt_Success) { std::unique_ptr<media::DecryptConfig> expected_decrypt_config = - media::DecryptConfig::CreateCbcsConfig(kFakeKeyId, kFakeIv, {}, - media::EncryptionPattern(6, 9)); + media::DecryptConfig::CreateCbcsConfig( + kFakeKeyId, kFakeIv, {}, media::EncryptionPattern::Create(6, 9)); EXPECT_CALL( *mock_daemon_cdm_, Decrypt(kFakeEncryptedData, @@ -587,7 +587,7 @@ CreateDecoderBuffer(kFakeEncryptedData); encrypted_buffer->set_is_key_frame(true); encrypted_buffer->set_decrypt_config(media::DecryptConfig::CreateCbcsConfig( - kFakeKeyId, kFakeIv, {}, media::EncryptionPattern(6, 9))); + kFakeKeyId, kFakeIv, {}, media::EncryptionPattern::Create(6, 9))); cdm_adapter_->Decrypt(media::Decryptor::kVideo, encrypted_buffer, callback.Get()); base::RunLoop().RunUntilIdle(); @@ -619,8 +619,8 @@ TEST_F(ContentDecryptionModuleAdapterTest, Decrypt_SecureHandleEncrypted) { std::unique_ptr<media::DecryptConfig> expected_decrypt_config = - media::DecryptConfig::CreateCbcsConfig(kFakeKeyId, kFakeIv, {}, - media::EncryptionPattern(6, 9)); + media::DecryptConfig::CreateCbcsConfig( + kFakeKeyId, kFakeIv, {}, media::EncryptionPattern::Create(6, 9)); EXPECT_CALL(*mock_daemon_cdm_, Decrypt(kFakeEncryptedData, MatchesDecryptConfig(&expected_decrypt_config), true, @@ -637,7 +637,7 @@ CreateDecoderBuffer(kFakeEncryptedData); encrypted_buffer->set_is_key_frame(true); encrypted_buffer->set_decrypt_config(media::DecryptConfig::CreateCbcsConfig( - kFakeKeyId, kFakeIv, {}, media::EncryptionPattern(6, 9))); + kFakeKeyId, kFakeIv, {}, media::EncryptionPattern::Create(6, 9))); encrypted_buffer->WritableSideData().secure_handle = kFakeSecureHandle; base::MockCallback<media::Decryptor::DecryptCB> callback; EXPECT_CALL(callback, Run(media::Decryptor::kSuccess, diff --git a/chromeos/components/cdm_factory_daemon/mojom/decrypt_config_mojom_traits.cc b/chromeos/components/cdm_factory_daemon/mojom/decrypt_config_mojom_traits.cc index d406c35..d09a5788 100644 --- a/chromeos/components/cdm_factory_daemon/mojom/decrypt_config_mojom_traits.cc +++ b/chromeos/components/cdm_factory_daemon/mojom/decrypt_config_mojom_traits.cc @@ -78,8 +78,12 @@ media::EncryptionPattern>:: Read(chromeos::cdm::mojom::EncryptionPatternDataView input, media::EncryptionPattern* output) { - *output = media::EncryptionPattern(input.crypt_byte_block(), - input.skip_byte_block()); + auto pattern = media::EncryptionPattern::Create(input.crypt_byte_block(), + input.skip_byte_block()); + if (!pattern) { + return false; + } + *output = *pattern; return true; } diff --git a/chromeos/components/cdm_factory_daemon/mojom/decrypt_config_mojom_traits_unittest.cc b/chromeos/components/cdm_factory_daemon/mojom/decrypt_config_mojom_traits_unittest.cc index c85d384..ad88ef59 100644 --- a/chromeos/components/cdm_factory_daemon/mojom/decrypt_config_mojom_traits_unittest.cc +++ b/chromeos/components/cdm_factory_daemon/mojom/decrypt_config_mojom_traits_unittest.cc @@ -11,7 +11,9 @@ namespace chromeos { TEST(DecryptConfigStructTraitsTest, ConvertEncryptionPattern) { - auto input = media::EncryptionPattern(22, 42); + auto pattern_opt = media::EncryptionPattern::Create(1, 2); + ASSERT_TRUE(pattern_opt.has_value()); + auto input = *pattern_opt; std::vector<uint8_t> data = chromeos::cdm::mojom::EncryptionPattern::Serialize(&input); @@ -40,7 +42,7 @@ media::EncryptionScheme::kCbcs, "FAKEKEY",
Regression Test / PoC
diff --git a/chromecast/starboard/media/media/drm_util_test.cc b/chromecast/starboard/media/media/drm_util_test.cc
index 2c9747b02..5005823 100644
--- a/chromecast/starboard/media/media/drm_util_test.cc
+++ b/chromecast/starboard/media/media/drm_util_test.cc
@@ -109,7 +109,8 @@
constexpr std::string_view kIv = "abcdefghijklmnop";
CHECK_EQ(kIv.size(),
static_cast<size_t>(::media::DecryptConfig::kDecryptionKeySize));
- const ::media::EncryptionPattern encryption_pattern(10, 20);
+ const auto encryption_pattern = ::media::EncryptionPattern::Create(10, 5);
+ CHECK(encryption_pattern.has_value());
const ::media::SubsampleEntry subsample(1, 6);
StarboardDrmSubSampleMapping sb_subsample;
sb_subsample.clear_byte_count = subsample.clear_bytes;
diff --git a/chromecast/starboard/media/media/starboard_audio_decoder_test.cc b/chromecast/starboard/media/media/starboard_audio_decoder_test.cc
index af77ace..07bd76f5 100644
--- a/chromecast/starboard/media/media/starboard_audio_decoder_test.cc
+++ b/chromecast/starboard/media/media/starboard_audio_decoder_test.cc
@@ -368,9 +368,9 @@
EXPECT_EQ(actual_drm_info.encryption_scheme,
kStarboardDrmEncryptionSchemeAesCbc);
EXPECT_EQ(actual_drm_info.encryption_pattern.crypt_byte_block,
- encryption_pattern.crypt_byte_block());
+ encryption_pattern->crypt_byte_block());
EXPECT_EQ(actual_drm_info.encryption_pattern.skip_byte_block,
- encryption_pattern.skip_byte_block());
+ encryption_pattern->skip_byte_block());
EXPECT_THAT(std::string(reinterpret_cast<const char*>(
actual_drm_info.initialization_vector),
actual_drm_info.initialization_vector_size),
@@ -508,9 +508,9 @@
EXPECT_EQ(actual_drm_info.encryption_scheme,
kStarboardDrmEncryptionSchemeAesCbc);
EXPECT_EQ(actual_drm_info.encryption_pattern.crypt_byte_block,
- encryption_pattern.crypt_byte_block());
+ encryption_pattern->crypt_byte_block());
EXPECT_EQ(actual_drm_info.encryption_pattern.skip_byte_block,
- encryption_pattern.skip_byte_block());
+ encryption_pattern->skip_byte_block());
EXPECT_THAT(std::string(reinterpret_cast<const char*>(
actual_drm_info.initialization_vector),
actual_drm_info.initialization_vector_size),
diff --git a/chromecast/starboard/media/media/starboard_video_decoder_test.cc b/chromecast/starboard/media/media/starboard_video_decoder_test.cc
index 65e10971..e7dabc0 100644
--- a/chromecast/starboard/media/media/starboard_video_decoder_test.cc
+++ b/chromecast/starboard/media/media/starboard_video_decoder_test.cc
@@ -298,9 +298,9 @@
EXPECT_EQ(actual_drm_info.encryption_scheme,
kStarboardDrmEncryptionSchemeAesCbc);
EXPECT_EQ(actual_drm_info.encryption_pattern.crypt_byte_block,
- encryption_pattern.crypt_byte_block());
+ encryption_pattern->crypt_byte_block());
EXPECT_EQ(actual_drm_info.encryption_pattern.skip_byte_block,
- encryption_pattern.skip_byte_block());
+ encryption_pattern->skip_byte_block());
EXPECT_THAT(std::string(reinterpret_cast<const char*>(
actual_drm_info.initialization_vector),
actual_drm_info.initialization_vector_size),
@@ -437,9 +437,9 @@
EXPECT_EQ(actual_drm_info.encryption_scheme,
kStarboardDrmEncryptionSchemeAesCbc);
EXPECT_EQ(actual_drm_info.encryption_pattern.crypt_byte_block,
- encryption_pattern.crypt_byte_block());
+ encryption_pattern->crypt_byte_block());
EXPECT_EQ(actual_drm_info.encryption_pattern.skip_byte_block,
- encryption_pattern.skip_byte_block());
+ encryption_pattern->skip_byte_block());
EXPECT_THAT(std::string(reinterpret_cast<const char*>(
actual_drm_info.initialization_vector),
actual_drm_info.initialization_vector_size),
diff --git a/chromeos/components/cdm_factory_daemon/content_decryption_module_adapter_unittest.cc b/chromeos/components/cdm_factory_daemon/content_decryption_module_adapter_unittest.cc
index f7a571a..cc48285 100644
--- a/chromeos/components/cdm_factory_daemon/content_decryption_module_adapter_unittest.cc
+++ b/chromeos/components/cdm_factory_daemon/content_decryption_module_adapter_unittest.cc
@@ -518,7 +518,7 @@
scoped_refptr<media::DecoderBuffer> encrypted_buffer =
CreateDecoderBuffer(kFakeEncryptedData);
encrypted_buffer->set_decrypt_config(media::DecryptConfig::CreateCbcsConfig(
- kFakeKeyId, kFakeIv, {}, media::EncryptionPattern(6, 9)));
+ kFakeKeyId, kFakeIv, {}, media::EncryptionPattern::Create(6, 9)));
cdm_adapter_->Decrypt(media::Decryptor::kVideo, encrypted_buffer,
callback.Get());
base::RunLoop().RunUntilIdle();
@@ -537,7 +537,7 @@
scoped_refptr<media::DecoderBuffer> encrypted_buffer =
CreateDecoderBuffer(kFakeEncryptedData);
encrypted_buffer->set_decrypt_config(media::DecryptConfig::CreateCbcsConfig(
- kFakeKeyId, kFakeIv, {}, media::EncryptionPattern(6, 9)));
+ kFakeKeyId, kFakeIv, {}, media::EncryptionPattern::Create(6, 9)));
cdm_adapter_->Decrypt(media::Decryptor::kVideo, encrypted_buffer,
callback.Get());
base::RunLoop().RunUntilIdle();
@@ -558,8 +558,8 @@
TEST_F(ContentDecryptionModuleAdapterTest, Decrypt_Success) {
std::unique_ptr<media::DecryptConfig> expected_decrypt_config =
- media::DecryptConfig::CreateCbcsConfig(kFakeKeyId, kFakeIv, {},
- media::EncryptionPattern(6, 9));
+ media::DecryptConfig::CreateCbcsConfig(
+ kFakeKeyId, kFakeIv, {}, media::EncryptionPattern::Create(6, 9));
EXPECT_CALL(
*mock_daemon_cdm_,
Decrypt(kFakeEncryptedData,
@@ -587,7 +587,7 @@
CreateDecoderBuffer(kFakeEncryptedData);
encrypted_buffer->set_is_key_frame(true);
encrypted_buffer->set_decrypt_config(media::DecryptConfig::CreateCbcsConfig(
- kFakeKeyId, kFakeIv, {}, media::EncryptionPattern(6, 9)));
+ kFakeKeyId, kFakeIv, {}, media::EncryptionPattern::Create(6, 9)));
cdm_adapter_->Decrypt(media::Decryptor::kVideo, encrypted_buffer,
callback.Get());
base::RunLoop().RunUntilIdle();
@@ -619,8 +619,8 @@
TEST_F(ContentDecryptionModuleAdapterTest, Decrypt_SecureHandleEncrypted) {
std::unique_ptr<media::DecryptConfig> expected_decrypt_config =
- media::DecryptConfig::CreateCbcsConfig(kFakeKeyId, kFakeIv, {},
- media::EncryptionPattern(6, 9));
+ media::DecryptConfig::CreateCbcsConfig(
+ kFakeKeyId, kFakeIv, {}, media::EncryptionPattern::Create(6, 9));
EXPECT_CALL(*mock_daemon_cdm_,
Decrypt(kFakeEncryptedData,
MatchesDecryptConfig(&expected_decrypt_config), true,
@@ -637,7 +637,7 @@
CreateDecoderBuffer(kFakeEncryptedData);
encrypted_buffer->set_is_key_frame(true);
encrypted_buffer->set_decrypt_config(media::DecryptConfig::CreateCbcsConfig(
- kFakeKeyId, kFakeIv, {}, media::EncryptionPattern(6, 9)));
+ kFakeKeyId, kFakeIv, {}, media::EncryptionPattern::Create(6, 9)));
encrypted_buffer->WritableSideData().secure_handle = kFakeSecureHandle;
base::MockCallback<media::Decryptor::DecryptCB> callback;
EXPECT_CALL(callback, Run(media::Decryptor::kSuccess,
diff --git a/chromeos/components/cdm_factory_daemon/mojom/decrypt_config_mojom_traits_unittest.cc b/chromeos/components/cdm_factory_daemon/mojom/decrypt_config_mojom_traits_unittest.cc
index c85d384..ad88ef59 100644
--- a/chromeos/components/cdm_factory_daemon/mojom/decrypt_config_mojom_traits_unittest.cc
+++ b/chromeos/components/cdm_factory_daemon/mojom/decrypt_config_mojom_traits_unittest.cc
@@ -11,7 +11,9 @@
namespace chromeos {
TEST(DecryptConfigStructTraitsTest, ConvertEncryptionPattern) {
- auto input = media::EncryptionPattern(22, 42);
+ auto pattern_opt = media::EncryptionPattern::Create(1, 2);
+ ASSERT_TRUE(pattern_opt.has_value());
+ auto input = *pattern_opt;
std::vector<uint8_t> data =
chromeos::cdm::mojom::EncryptionPattern::Serialize(&input);
@@ -40,7 +42,7 @@
media::EncryptionScheme::kCbcs, "FAKEKEY",
std::string(media::DecryptConfig::kDecryptionKeySize, '1'),
std::vector<media::SubsampleEntry>({media::SubsampleEntry(1, 3)}),
- std::make_optional<media::EncryptionPattern>(22, 42));
+ media::EncryptionPattern::Create(1, 2));
std::vector<uint8_t> data =
chromeos::cdm::mojom::DecryptConfig::Serialize(&input);
diff --git a/media/base/decrypt_config_unittest.cc b/media/base/decrypt_config_unittest.cc
index 4df6ee1..658eb3f2 100644
--- a/media/base/decrypt_config_unittest.cc
+++ b/media/base/decrypt_config_unittest.cc
@@ -50,8 +50,8 @@
}
TEST(DecryptConfigTest, CbcsConstruction) {
- auto config = DecryptConfig::CreateCbcsConfig(kDefaultKeyId, kDefaultIV, {},
- EncryptionPattern(1, 2));
+ auto config = DecryptConfig::CreateCbcsConfig(
+ kDefaultKeyId, kDefaultIV, {}, EncryptionPattern::Create(1, 2));
EXPECT_EQ(config->key_id(), kDefaultKeyId);
EXPECT_EQ(config->iv(), kDefaultIV);
EXPECT_EQ(config->subsamples().size(), 0u);
@@ -63,7 +63,7 @@
// Now with multiple subsample entries.
config = DecryptConfig::CreateCbcsConfig(kDefaultKeyId, kAlternateIV,
{{1, 2}, {3, 4}, {5, 6}, {7, 8}},
- EncryptionPattern(1, 0));
+ EncryptionPattern::Create(1, 0));
EXPECT_EQ(config->key_id(), kDefaultKeyId);
EXPECT_EQ(config->iv(), kAlternateIV);
EXPECT_EQ(config->subsamples().size(), 4u);
@@ -92,8 +92,8 @@
auto config1 = DecryptConfig::CreateCencConfig(kDefaultKeyId, kDefaultIV, {});
EXPECT_TRUE(config1->Matches(*config1));
- auto config2 = DecryptConfig::CreateCbcsConfig(kDefaultKeyId, kDefaultIV, {},
- EncryptionPattern(1, 2));
+ auto config2 = DecryptConfig::CreateCbcsConfig(
+ kDefaultKeyId, kDefaultIV, {}, EncryptionPattern::Create(1, 2));
EXPECT_TRUE(config2->Matches(*config2));
EXPECT_FALSE(config1->Matches(*config2));
@@ -130,28 +130,28 @@
}
TEST(DecryptConfigTest, CbcsMatches) {
- auto config1 = DecryptConfig::CreateCbcsConfig(kDefaultKeyId, kDefaultIV, {},
- EncryptionPattern(1, 2));
+ auto config1 = DecryptConfig::CreateCbcsConfig(
+ kDefaultKeyId, kDefaultIV, {}, EncryptionPattern::Create(1, 2));
EXPECT_TRUE(config1->Matches(*config1));
// Different key_id.
- auto config2 = DecryptConfig::CreateCbcsConfig(kAlternateKeyId, kDefaultIV,
- {}, EncryptionPattern(1, 2));
+ auto config2 = DecryptConfig::CreateCbcsConfig(
+ kAlternateKeyId, kDefaultIV, {}, EncryptionPattern::Create(1, 2));
EXPECT_FALSE(config1->Matches(*config2));
EXPECT_FALSE(config2->Matches(*config1));
// Different IV.
- auto config3 = DecryptConfig::CreateCbcsConfig(kDefaultKeyId, kAlternateIV,
- {}, EncryptionPattern(1, 2));
+ auto config3 = DecryptConfig::CreateCbcsConfig(
+ kDefaultKeyId, kAlternateIV, {}, EncryptionPattern::Create(1, 2));
EXPECT_FALSE(config1->Matches(*config3));
EXPECT_FALSE(config2->Matches(*config3));
EXPECT_FALSE(config3->Matches(*config1));
EXPECT_FALSE(config3->Matches(*config2));
// Different subsamples.
- auto config4 = DecryptConfig::CreateCbcsConfig(kDefaultKeyId, kDefaultIV,
- {{1, 2}, {3, 4}, {5, 6}},
- EncryptionPattern(1, 2));
+ auto config4 = DecryptConfig::CreateCbcsConfig(
+ kDefaultKeyId, kDefaultIV, {{1, 2}, {3, 4}, {5, 6}},
+ EncryptionPattern::Create(1, 2));
EXPECT_FALSE(config1->Matches(*config4));
EXPECT_FALSE(config2->Matches(*config4));
EXPECT_FALSE(config3->Matches(*config4));
@@ -160,8 +160,8 @@
EXPECT_FALSE(config4->Matches(*config3));
// Different pattern.
- auto config5 = DecryptConfig::CreateCbcsConfig(kDefaultKeyId, kDefaultIV, {},
- EncryptionPattern(5, 6));
+ auto config5 = DecryptConfig::CreateCbcsConfig(
+ kDefaultKeyId, kDefaultIV, {}, EncryptionPattern::Create(5, 6));
EXPECT_FALSE(config1->Matches(*config5));
EXPECT_FALSE(config2->Matches(*config5));
EXPECT_FALSE(config3->Matches(*config5));
@@ -197,7 +197,7 @@
// 'cbcs' config with subsamples and pattern.
stream << *DecryptConfig::CreateCbcsConfig(kAlternateKeyId, kAlternateIV,
{{1, 2}, {3, 4}, {5, 6}, {7, 8}},
- EncryptionPattern(1, 2));
+ EncryptionPattern::Create(1, 2));
}
} // namespace media
diff --git a/media/base/encryption_pattern_unittest.cc b/media/base/encryption_pattern_unittest.cc
new file mode 100644
index 0000000..27346ce
--- /dev/null
+++ b/media/base/encryption_pattern_unittest.cc
@@ -0,0 +1,43 @@
+// Copyright 2026 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "media/base/encryption_pattern.h"
+
+#include "base/test/scoped_feature_list.h"
+#include "media/base/media_switches.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace media {
+
+TEST(EncryptionPatternTest, CreateWithValidationEnabled) {
+ base::test::ScopedFeatureList scoped_feature_list;
+ scoped_feature_list.InitAndEnableFeature(kValidateEncryptionPatternSize);
+
+ // Valid patterns (0-15)
+ EXPECT_TRUE(EncryptionPattern::Create(0, 0).has_value());
+ EXPECT_TRUE(EncryptionPattern::Create(1, 9).has_value());
+ EXPECT_TRUE(EncryptionPattern::Create(15, 15).has_value());
+
+ // Invalid patterns (> 15)
+ EXPECT_FALSE(EncryptionPattern::Create(16, 0).has_value());
+ EXPECT_FALSE(EncryptionPattern::Create(0, 16).has_value());
+ EXPECT_FALSE(EncryptionPattern::Create(100, 100).has_value());
+}
+
+TEST(EncryptionPatternTest, CreateWithValidationDisabled) {
+ base::test::ScopedFeatureList scoped_feature_list;
+ scoped_feature_list.InitAndDisableFeature(kValidateEncryptionPatternSize);
+
+ // Valid patterns (0-15)
+ EXPECT_TRUE(EncryptionPattern::Create(0, 0).has_value());
+ EXPECT_TRUE(EncryptionPattern::Create(1, 9).has_value());
+ EXPECT_TRUE(EncryptionPattern::Create(15, 15).has_value());
+
+ // Invalid patterns (> 15)
+ EXPECT_TRUE(EncryptionPattern::Create(16, 0).has_value());
+ EXPECT_TRUE(EncryptionPattern::Create(0, 16).has_value());
+ EXPECT_TRUE(EncryptionPattern::Create(100, 100).has_value());
+}
+
+} // namespace media
diff --git a/media/cdm/cbcs_decryptor_unittest.cc b/media/cdm/cbcs_decryptor_unittest.cc
index b77c06b..ffe288bb 100644
... (truncated)
Original Bug Report
Potential Sandbox Escape via Unvalidated EncryptionPattern in Mojo IPC
Project Fortify, an experimental security project, has identified the following potential security issue.
Overview: Chromium fails to validate the crypt_byte_block and skip_byte_block fields of media::EncryptionPattern when receiving Mojo IPC messages from a renderer. These fields are defined as 4-bit values by the ISO specification, but the Mojo interface accepts arbitrary 32-bit integers. A compromised renderer can send out-of-bounds values to the CDM or GPU processes, potentially triggering integer overflows and memory corruption in Widevine or hardware video drivers.
Affected files:
media/mojo/mojom/encryption_pattern_mojom_traits.ccmedia/cdm/cdm_type_conversion.ccmedia/mojo/services/mojo_decryptor_service.ccmedia/mojo/services/mojo_video_decoder_service.ccmedia/mojo/common/validation_utils.ccmedia/base/decrypt_config.cc
Estimated timestamp from git blame: 2025-03-17
Description
There is a potential sandbox escape vulnerability caused by a missing bounds check on EncryptionPattern parameters passed via Mojo IPC.
Per the ISO/IEC 23001-7:2016 specification, the pattern encryption blocks (crypt_byte_block and skip_byte_block) are 4-bit values, capping them at a maximum of 15. While Chromium’s MP4 parser correctly masks these values during file parsing, the Mojo IPC interface (media.mojom.EncryptionPattern) represents them as uint32 values.
When a compromised renderer process sends a mojom::DecoderBuffer to the GPU process or CDM Utility process, these values are deserialized in StructTraits<media::mojom::EncryptionPatternDataView, media::EncryptionPattern>::Read (media/mojo/mojom/encryption_pattern_mojom_traits.cc) without any bounds checking. Furthermore, ValidateAndConvertMojoDecryptConfig (media/mojo/common/validation_utils.cc) does not validate the range of the encryption_pattern fields.
These massive 32-bit values (e.g., 0xFFFFFFFF) are subsequently forwarded to highly sensitive, lower-level components:
- CDM Utility Process (Widevine):
ToCdmInputBuffer(media/cdm/cdm_type_conversion.cc) copies the unvalidated values directly into thecdm::InputBuffer_2.patternstruct. This struct is passed across the CDM interface to the closed-source Widevine library. - GPU Process (Hardware Decoders): Hardware delegates, such as the VAAPI delegate (
media/gpu/vaapi/vaapi_video_decoder_delegate.cc), copy the unvalidated values into structures likeVAEncryptionParameters(crypto_params->blocks_stripe_encrypted), which are then passed to graphics drivers.
If the receiving component (Widevine or the GPU driver) calculates buffer offsets, loop bounds, or total protected block sizes assuming these values are standard 4-bit numbers, providing UINT32_MAX will likely cause an integer overflow. This can lead to out-of-bounds reads/writes or heap corruption, potentially allowing the attacker to escape the renderer sandbox and execute code in the GPU or CDM process.
Potential Reproduction Steps
Note: Our tooling agent does not have the ability to run code, so these are theoretical steps an attacker would follow.
- Gain code execution within a sandboxed renderer process (e.g., via a V8 exploit).
- Obtain a Mojo handle to either
media::mojom::VideoDecoder(GPU process) ormedia::mojom::Decryptor(CDM utility process). - Construct a
media::mojom::DecryptConfigobject withencryption_schemeset tocbcs. - Embed a
media::mojom::EncryptionPatternwithcrypt_byte_blockandskip_byte_blockset to0xFFFFFFFF. - Wrap this
DecryptConfigin amedia::mojom::DecoderBufferand send it over the Mojo pipe usingDecodeorDecrypt. - The receiving process deserializes the buffer without range checks and passes the malicious block counts to the CDM or graphics driver, triggering an integer overflow and subsequent memory corruption.
Proposed Fix
Add explicit range validation during Mojo deserialization or validation to ensure these fields do not exceed their maximum 4-bit limit.
Modify ValidateAndConvertMojoDecryptConfig in media/mojo/common/validation_utils.cc (or StructTraits::Read in encryption_pattern_mojom_traits.cc) to reject the configuration if the values exceed 15:
if (decrypt_config->encryption_pattern.has_value()) {
if (decrypt_config->encryption_pattern->crypt_byte_block() > 15 ||
decrypt_config->encryption_pattern->skip_byte_block() > 15) {
return nullptr; // Or return false in StructTraits::Read
}
}
Evaluated with Chrome root at commit: a9cbf6e8b275fe4147435aa905f3b7f5a656f5f0
Results from 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.