High firefox Memory Corruption 🔧 Commit mapped

Overview

High
Severity
CVSS
No
Exploited ITW
Fixed
Fix Status
Impacthigh
DescriptionAn attacker was able to perform memory corruption in the GMP process which processes encrypted media. This process is also heavily sandboxed, but represents slightly different privileges from the content process.
ComponentDOM
Bug ClassMemory Corruption
Tracker1979527
Fix commitf926812a0a8a (firefox) +74/-32
CISA KEVNot listed
CreditedOskar
Disclosed2025-08-19

Changed Functions

FunctionChangeNotes
if
dom/media/gmp/ChromiumCDMChild.cpp
modified
if
dom/media/gmp/ChromiumCDMParent.cpp
modified
if
dom/media/gmp/DecryptJob.cpp
modified

Files Changed

  • dom/media/gmp/ChromiumCDMChild.cpp
  • dom/media/gmp/ChromiumCDMParent.cpp
  • dom/media/gmp/ChromiumCDMParent.h
  • dom/media/gmp/DecryptJob.cpp
  • dom/media/gmp/PChromiumCDM.ipdl
diff --git a/dom/media/gmp/ChromiumCDMChild.cpp b/dom/media/gmp/ChromiumCDMChild.cpp
index c7a6aad89dd..3181a66f514 100644
--- a/dom/media/gmp/ChromiumCDMChild.cpp
+++ b/dom/media/gmp/ChromiumCDMChild.cpp
@@ -645,25 +645,32 @@ mozilla::ipc::IPCResult ChromiumCDMChild::RecvDecrypt(
   cdm::Status status = mCDM->Decrypt(input, &output);
 
   // CDM should have allocated a cdm::Buffer for output.
-  CDMShmemBuffer* buffer =
-      output.DecryptedBuffer()
-          ? static_cast<CDMShmemBuffer*>(output.DecryptedBuffer())
-          : nullptr;
-  MOZ_ASSERT_IF(buffer, buffer->AsShmemBuffer());
-  if (status != cdm::kSuccess || !buffer) {
+  if (status != cdm::kSuccess || !output.DecryptedBuffer()) {
     Unused << SendDecryptFailed(aId, status);
     return IPC_OK();
   }
 
-  // Success! Return the decrypted sample to parent.
-  MOZ_ASSERT(!HasShmemOfSize(outputShmemSize));
-  ipc::Shmem shmem = buffer->ExtractShmem();
-  if (SendDecrypted(aId, cdm::kSuccess, std::move(shmem))) {
-    // No need to deallocate the output shmem; it should have been returned
-    // to the content process.
-    autoDeallocateOutputShmem.release();
+  auto* buffer = static_cast<CDMBuffer*>(output.DecryptedBuffer());
+  if (auto* shmemBuffer = buffer->AsShmemBuffer()) {
+    MOZ_ASSERT(!HasShmemOfSize(outputShmemSize));
+    ipc::Shmem shmem = shmemBuffer->ExtractShmem();
+    if (SendDecryptedShmem(aId, cdm::kSuccess, std::move(shmem))) {
+      // No need to deallocate the output shmem; it should have been returned
+      // to the content process.
+      autoDeallocateOutputShmem.release();
+    }
+    return IPC_OK();
+  }
+
+  if (auto* arrayBuffer = buffer->AsArrayBuffer()) {
+    Unused << SendDecryptedData(aId, cdm::kSuccess,
+                                arrayBuffer->ExtractBuffer());
+    return IPC_OK();
   }
 
+  MOZ_ASSERT_UNREACHABLE("Unexpected CDMBuffer type!");
+  GMP_LOG_DEBUG("ChromiumCDMChild::RecvDecrypt() unexpected CDMBuffer type");
+  Unused << SendDecryptFailed(aId, cdm::kDecryptError);
   return IPC_OK();
 }
 
@@ -805,13 +812,18 @@ void ChromiumCDMChild::ReturnOutput(WidevineVideoFrame& aFrame) {
   }
 
   CDMBuffer* base = reinterpret_cast<CDMBuffer*>(aFrame.FrameBuffer());
-  if (base->AsShmemBuffer()) {
-    ipc::Shmem shmem = base->AsShmemBuffer()->ExtractShmem();
+  if (auto* shmemBase = base->AsShmemBuffer()) {
+    ipc::Shmem shmem = shmemBase->ExtractShmem();
     Unused << SendDecodedShmem(output, std::move(shmem));
-  } else {
-    MOZ_ASSERT(base->AsArrayBuffer());
-    Unused << SendDecodedData(output, base->AsArrayBuffer()->ExtractBuffer());
+    return;
   }
+
+  if (auto* arrayBase = base->AsArrayBuffer()) {
+    Unused << SendDecodedData(output, arrayBase->ExtractBuffer());
+    return;
+  }
+
+  MOZ_ASSERT_UNREACHABLE("Unexpected CDMBuffer type!");
 }
 
 mozilla::ipc::IPCResult ChromiumCDMChild::RecvDrain() {
diff --git a/dom/media/gmp/ChromiumCDMParent.cpp b/dom/media/gmp/ChromiumCDMParent.cpp
index 90a73c89c5b..f1f6bf55bec 100644
--- a/dom/media/gmp/ChromiumCDMParent.cpp
+++ b/dom/media/gmp/ChromiumCDMParent.cpp
@@ -686,11 +686,11 @@ ipc::IPCResult ChromiumCDMParent::RecvDecryptFailed(const uint32_t& aId,
   return IPC_OK();
 }
 
-ipc::IPCResult ChromiumCDMParent::RecvDecrypted(const uint32_t& aId,
-                                                const uint32_t& aStatus,
-                                                ipc::Shmem&& aShmem) {
+ipc::IPCResult ChromiumCDMParent::RecvDecryptedShmem(const uint32_t& aId,
+                                                     const uint32_t& aStatus,
+                                                     ipc::Shmem&& aShmem) {
   MOZ_ASSERT(mGMPThread->IsOnCurrentThread());
-  GMP_LOG_DEBUG("ChromiumCDMParent::RecvDecrypted(this=%p, id=%" PRIu32
+  GMP_LOG_DEBUG("ChromiumCDMParent::RecvDecryptedShmem(this=%p, id=%" PRIu32
                 ", status=%" PRIu32 ")",
                 this, aId, aStatus);
 
@@ -704,9 +704,33 @@ ipc::IPCResult ChromiumCDMParent::RecvDecrypted(const uint32_t& aId,
   }
   for (size_t i = 0; i < mDecrypts.Length(); i++) {
     if (mDecrypts[i]->mId == aId) {
-      mDecrypts[i]->PostResult(
-          ToDecryptStatus(aStatus),
-          Span<const uint8_t>(aShmem.get<uint8_t>(), aShmem.Size<uint8_t>()));
+      mDecrypts[i]->PostResult(ToDecryptStatus(aStatus),
+                               aShmem.IsReadable()
+                                   ? Span<const uint8_t>(aShmem.get<uint8_t>(),
+                                                         aShmem.Size<uint8_t>())
+                                   : Span<const uint8_t>());
+      mDecrypts.RemoveElementAt(i);
+      break;
+    }
+  }
+  return IPC_OK();
+}
+
+ipc::IPCResult ChromiumCDMParent::RecvDecryptedData(const uint32_t& aId,
+                                                    const uint32_t& aStatus,
+                                                    nsTArray<uint8_t>&& aData) {
+  MOZ_ASSERT(mGMPThread->IsOnCurrentThread());
+  GMP_LOG_DEBUG("ChromiumCDMParent::RecvDecryptedData(this=%p, id=%" PRIu32
+                ", status=%" PRIu32 ")",
+                this, aId, aStatus);
+
+  if (mIsShutdown) {
+    MOZ_ASSERT(mDecrypts.IsEmpty());
+    return IPC_OK();
+  }
+  for (size_t i = 0; i < mDecrypts.Length(); i++) {
+    if (mDecrypts[i]->mId == aId) {
+      mDecrypts[i]->PostResult(ToDecryptStatus(aStatus), aData);
       mDecrypts.RemoveElementAt(i);
       break;
     }
diff --git a/dom/media/gmp/ChromiumCDMParent.h b/dom/media/gmp/ChromiumCDMParent.h
index e26512c8a94..88bf64ecb4d 100644
--- a/dom/media/gmp/ChromiumCDMParent.h
+++ b/dom/media/gmp/ChromiumCDMParent.h
@@ -126,8 +126,11 @@ class ChromiumCDMParent final : public PChromiumCDMParent,
                                         const double& aSecondsSinceEpoch);
   ipc::IPCResult RecvOnSessionClosed(const nsCString& aSessionId);
   ipc::IPCResult RecvOnQueryOutputProtectionStatus();
-  ipc::IPCResult RecvDecrypted(const uint32_t& aId, const uint32_t& aStatus,
-                               ipc::Shmem&& aData);
+  ipc::IPCResult RecvDecryptedShmem(const uint32_t& aId,
+                                    const uint32_t& aStatus,
+                                    ipc::Shmem&& aData);
+  ipc::IPCResult RecvDecryptedData(const uint32_t& aId, const uint32_t& aStatus,
+                                   nsTArray<uint8_t>&& aData);
   ipc::IPCResult RecvDecryptFailed(const uint32_t& aId,
                                    const uint32_t& aStatus);
   ipc::IPCResult RecvOnDecoderInitDone(const uint32_t& aStatus);
diff --git a/dom/media/gmp/DecryptJob.cpp b/dom/media/gmp/DecryptJob.cpp
index 03fbfdbf0c7..0794faece80 100644
--- a/dom/media/gmp/DecryptJob.cpp
+++ b/dom/media/gmp/DecryptJob.cpp
@@ -28,11 +28,13 @@ void DecryptJob::PostResult(DecryptStatus aResult,
   if (aDecryptedData.Length() != mSample->Size()) {
     NS_WARNING("CDM returned incorrect number of decrypted bytes");
   }
-  if (aResult == eme::Ok) {
+  if (aResult == DecryptStatus::Ok) {
     UniquePtr<MediaRawDataWriter> writer(mSample->CreateWriter());
-    PodCopy(writer->Data(), aDecryptedData.Elements(),
-            std::min<size_t>(aDecryptedData.Length(), mSample->Size()));
-  } else if (aResult == eme::NoKeyErr) {
+    if (NS_WARN_IF(!writer->Replace(aDecryptedData.Elements(),
+                                    aDecryptedData.Length()))) {
+      aResult = DecryptStatus::GenericErr;
+    }
+  } else if (aResult == DecryptStatus::NoKeyErr) {
     NS_WARNING("CDM returned NoKeyErr");
     // We still have the encrypted sample, so we can re-enqueue it to be
     // decrypted again once the key is usable again.
diff --git a/dom/media/gmp/PChromiumCDM.ipdl b/dom/media/gmp/PChromiumCDM.ipdl
index 2bd2103477e..e7aaf3bbab8 100644
--- a/dom/media/gmp/PChromiumCDM.ipdl
+++ b/dom/media/gmp/PChromiumCDM.ipdl
@@ -106,7 +106,8 @@ parent:
   async ResolveLoadSessionPromise(uint32_t aPromiseId, bool aSuccessful);
 
   // Return values of cdm::ContentDecryptionModule_10::Decrypt
-  async Decrypted(uint32_t aId, uint32_t aStatus, Shmem aDecryptedData);
+  async DecryptedShmem(uint32_t aId, uint32_t aStatus, Shmem aData);
+  async DecryptedData(uint32_t aId, uint32_t aStatus, uint8_t[] aData);
   async DecryptFailed(uint32_t aId, uint32_t aStatus);
 
   async OnDecoderInitDone(uint32_t aStatus);
Loading diff…