Medium firefox Logic Error 🔧 Commit mapped

Overview

Medium
Severity
—
CVSS
No
Exploited ITW
Fixed
Fix Status
Impactmoderate
DescriptionIncorrect boundary conditions in the Audio/Video: GMP component
ComponentDOM
Bug ClassLogic Error
Tracker2045424
Fix commit1bd02c4504c0 (firefox) +16/-1
CISA KEVNot listed
CreditedJacolon Walker
Disclosed2026-07-21

Background

CENC (Common Encryption)
the MP4 scheme in which media samples are encrypted with a per-sample initialization vector and key ID, decrypted here by the Clear Key CDM.
`tenc` box
the CENC track-encryption metadata atom inside an MP4 sinf, carrying the default per-sample IV size and default key ID.
`SinfParser::ParseTenc`
the Gecko demuxer routine that reads the tenc box fields, including mDefaultIVSize, from attacker-supplied MP4 bytes.
`ClearKeyDecryptor::Decrypt`
the Clear Key GMP/CDM function that pads a sample’s IV to the AES block length before decrypting.

Root Cause Analysis

SinfParser::ParseTenc read the tenc default per-sample IV size directly from the MP4 byte stream into mSinf.mDefaultIVSize (an uint8_t) with no validation, so a crafted file could specify any value from 0 to 255. That size flowed into ClearKeyDecryptor::Decrypt, which pads the IV to the AES block size via iv.insert(iv.end(), CENC_KEY_LEN - aMetadata.mIV.size(), 0). The invariant that the IV never exceeds CENC_KEY_LEN (16) was assumed but never enforced outside a debug assert, so when aMetadata.mIV.size() exceeded CENC_KEY_LEN the unsigned subtraction CENC_KEY_LEN - aMetadata.mIV.size() wrapped around to an enormous count, driving a pathological std::vector insertion.

The fix enforces the invariant at both ends: ParseTenc now rejects any defaultIVSize other than 0, 8, or 16 with NS_ERROR_FAILURE, and Decrypt returns Status::kDecryptError when mIV.size() > CENC_KEY_LEN. Because release builds do not evaluate the assert, the explicit runtime checks are what actually close the boundary hole.

Key insight
The single mistake was trusting an attacker-controlled IV-size field from the MP4 tenc box and then computing an unsigned padding count CENC_KEY_LEN - size without first bounding size; the fix adds explicit whitelist validation at parse time and a size guard at decrypt time so the subtraction can never underflow.

Attack Path

  1. Craft malicious MP4 An attacker authors an encrypted MP4 whose tenc box declares a default per-sample IV size larger than 16 (or otherwise not 0/8/16).
  2. Deliver to victim The file is served to a browser using the Clear Key EME/GMP path, e.g. via a web page or media element.
  3. Bypass parse validation SinfParser::ParseTenc stores the oversized mDefaultIVSize unchecked, propagating it to per-sample decryption metadata.
  4. Trigger underflow ClearKeyDecryptor::Decrypt computes CENC_KEY_LEN - aMetadata.mIV.size(), which underflows to a huge unsigned count passed to iv.insert.
  5. Corrupt decrypt state The pathological insertion produces a malformed or oversized IV buffer, yielding a decryption boundary error and abnormal behavior in the GMP process.

Impact Assessment

An attacker who gets a victim to load a crafted encrypted MP4 can violate the IV-length boundary condition in the Clear Key decryption path, running inside the sandboxed GMP/CDM (media plugin) process. The rated impact is moderate: the outcome is memory/allocation misbehavior and decrypt errors rather than a demonstrated code-execution primitive in the diff. The only precondition is that the victim processes the malicious media through the Clear Key EME pipeline.

Changed Functions

FunctionChangeNotes
if
dom/media/mp4/SinfParser.cpp
modified

Files Changed

  • dom/media/eme/clearkey/ClearKeyDecryptionManager.cpp
  • dom/media/mp4/SinfParser.cpp

Audit Directions

  • Unsigned subtraction from parsed lengths
    Audit every CONST - size or size - other where size originates from untrusted media metadata for wraparound before it feeds allocation or insert/resize counts.
  • Assert-only invariants
    Flag places where a size or bounds invariant is guaranteed only by assert, since release builds skip it; require an equivalent runtime check on any attacker-reachable path.
  • Container-format size fields
    Trace other MP4/box parsers (IV sizes, key-ID lengths, sample counts) to confirm each length is whitelisted or range-checked at parse time rather than trusted downstream in the GMP/CDM.
diff --git a/dom/media/eme/clearkey/ClearKeyDecryptionManager.cpp b/dom/media/eme/clearkey/ClearKeyDecryptionManager.cpp
index 328360a0311..eaea6bf09b6 100644
--- a/dom/media/eme/clearkey/ClearKeyDecryptionManager.cpp
+++ b/dom/media/eme/clearkey/ClearKeyDecryptionManager.cpp
@@ -265,6 +265,12 @@ Status ClearKeyDecryptor::Decrypt(uint8_t* aBuffer, uint32_t aBufferSize,
   assert(aMetadata.mIV.size() == 8 || aMetadata.mIV.size() == 16 ||
          (aMetadata.mIV.empty() && AllZero(aMetadata.mCipherBytes)));
 
+  if (aMetadata.mIV.size() > CENC_KEY_LEN) {
+    CK_LOGD("ClearKeyDecryptor::Decrypt unexpected IV size %zu",
+            aMetadata.mIV.size());
+    return Status::kDecryptError;
+  }
+
   std::vector<uint8_t> iv(aMetadata.mIV);
   iv.insert(iv.end(), CENC_KEY_LEN - aMetadata.mIV.size(), 0);
 
diff --git a/dom/media/mp4/SinfParser.cpp b/dom/media/mp4/SinfParser.cpp
index 4ffb4ff2f09..1455e89c0c5 100644
--- a/dom/media/mp4/SinfParser.cpp
+++ b/dom/media/mp4/SinfParser.cpp
@@ -7,6 +7,8 @@
 #include "AtomType.h"
 #include "Box.h"
 #include "ByteStream.h"
+#include "MediaDataDemuxer.h"
+#include "mozilla/Logging.h"
 #include "mozilla/Try.h"
 
 namespace mozilla {
@@ -73,7 +75,14 @@ Result<Ok, nsresult> SinfParser::ParseTenc(const Box& aBox) {
   }
 
   uint8_t isEncrypted = MOZ_TRY(reader->ReadU8());
-  mSinf.mDefaultIVSize = MOZ_TRY(reader->ReadU8());
+  uint8_t defaultIVSize = MOZ_TRY(reader->ReadU8());
+  if (defaultIVSize != 0 && defaultIVSize != 8 && defaultIVSize != 16) {
+    MOZ_LOG(gMediaDemuxerLog, LogLevel::Warning,
+            ("SinfParser: unexpected default per-sample IV size %u",
+             static_cast<unsigned>(defaultIVSize)));
+    return Err(NS_ERROR_FAILURE);
+  }
+  mSinf.mDefaultIVSize = defaultIVSize;
   memcpy(mSinf.mDefaultKeyID, reader->Read(16), 16);
 
   if (isEncrypted && mSinf.mDefaultIVSize == 0) {
Loading diff…