Moderate firefox Logic Error 🔧 Commit mapped

Overview

Moderate
Severity
—
CVSS
No
Exploited ITW
Fixed
Fix Status
Impactmoderate
DescriptionSandbox escape due to incorrect boundary conditions in the Widget: Win32 component
ComponentWidget
Bug ClassLogic Error
Tracker2061235
Fix commit4a60e484f6ec (firefox) +55/-20
CISA KEVNot listed
CreditedMozilla
Disclosed2026-09-15

Changed Functions

FunctionChangeNotes
mExtent
widget/windows/WinOLELock.h
modified
if
widget/windows/nsDataObj.cpp
modified
MakeScopeExit
widget/windows/nsDataObj.cpp
modified
MakeScopeExit
widget/windows/nsDataObjCollection.cpp
modified
if
widget/windows/nsDataObjCollection.cpp
modified

Files Changed

  • widget/windows/WinOLELock.h
  • widget/windows/nsClipboard.cpp
  • widget/windows/nsDataObj.cpp
  • widget/windows/nsDataObjCollection.cpp
  • widget/windows/nsDragService.cpp
diff --git a/widget/windows/WinOLELock.h b/widget/windows/WinOLELock.h
index 903a7891dba..6b6ae8c7001 100644
--- a/widget/windows/WinOLELock.h
+++ b/widget/windows/WinOLELock.h
@@ -11,6 +11,7 @@
 #include <type_traits>
 
 #include "mozilla/Assertions.h"
+#include "mozilla/CheckedInt.h"
 
 namespace details {
 // implementation of `is_complete_type_v` borrowed from Raymond Chen:
@@ -171,9 +172,9 @@ class ScopedOLEMemory<U[]> {
   static_assert(std::is_pod_v<U>, "type must be POD");
 
  public:
-  explicit ScopedOLEMemory(size_t n)
-      : mHandle(::GlobalAlloc(GMEM_MOVEABLE | GMEM_ZEROINIT, sizeof(U) * n)),
-        mExtent(n) {}
+  // Fail the allocation rather than under-allocating if the size computation
+  // would overflow; `lock()` hands out all `n` elements either way.
+  explicit ScopedOLEMemory(size_t n) : mHandle(AllocFor(n)), mExtent(n) {}
   ~ScopedOLEMemory() { ::GlobalFree(mHandle); }
 
   ScopedOLELock<U[]> lock() const { return ScopedOLELock<U[]>(mHandle); }
@@ -187,6 +188,14 @@ class ScopedOLEMemory<U[]> {
   }
 
  private:
+  static HGLOBAL AllocFor(size_t n) {
+    mozilla::CheckedInt<size_t> const bytes =
+        mozilla::CheckedInt<size_t>(n) * sizeof(U);
+    return bytes.isValid()
+               ? ::GlobalAlloc(GMEM_MOVEABLE | GMEM_ZEROINIT, bytes.value())
+               : nullptr;
+  }
+
   HGLOBAL mHandle;
   size_t mExtent;
 };
diff --git a/widget/windows/nsClipboard.cpp b/widget/windows/nsClipboard.cpp
index 4ae1234bc2f..15359f6c902 100644
--- a/widget/windows/nsClipboard.cpp
+++ b/widget/windows/nsClipboard.cpp
@@ -914,7 +914,7 @@ nsresult nsClipboard::GetNativeDataOffClipboard(IDataObject* aDataObject,
   UINT const format = aFormat;
 
   FORMATETC fe;
-  STGMEDIUM stm;
+  STGMEDIUM stm{};
   HRESULT hres = FillSTGMedium(aDataObject, format, &fe, &stm, TYMED_HGLOBAL);
 
   // If the format is CF_HDROP and we haven't found any files we can try looking
diff --git a/widget/windows/nsDataObj.cpp b/widget/windows/nsDataObj.cpp
index 1ff8ceba971..c68793d6062 100644
--- a/widget/windows/nsDataObj.cpp
+++ b/widget/windows/nsDataObj.cpp
@@ -14,6 +14,7 @@
 #include "WinUtils.h"
 #include "imgIEncoder.h"
 #include "imgITools.h"
+#include "mozilla/CheckedInt.h"
 #include "mozilla/Components.h"
 #include "mozilla/LazyIdleThread.h"
 #include "mozilla/Preferences.h"
@@ -743,8 +744,7 @@ STDMETHODIMP nsDataObj::GetData(LPFORMATETC aFormat, LPSTGMEDIUM pSTM) {
          dfInx < mDataFlavors.Length()) {
     nsCString const& df = mDataFlavors.ElementAt(dfInx);
     if (FormatsMatch(fe, *aFormat)) {
-      pSTM->pUnkForRelease =
-          nullptr;  // caller is responsible for deleting this data
+      *pSTM = STGMEDIUM{};
       CLIPFORMAT const format = aFormat->cfFormat;
 
       // compile-time-constant format indicators:
@@ -1648,22 +1648,44 @@ HRESULT nsDataObj::GetText(const nsACString& aDataFlavor, FORMATETC& aFE,
   if (aFE.cfFormat == CF_TEXT) {
     // Someone is asking for text/plain; convert the unicode (assuming it's
     // present) to text with the correct platform encoding.
-    size_t bufferSize = sizeof(char) * (len + 2);
-    char* plainTextData = static_cast<char*>(moz_xmalloc(bufferSize));
+    //
+    // One UTF-16 code unit can encode to more than two bytes: U+0800..U+FFFF
+    // takes three when the ANSI code page is UTF-8. Size the buffer from the
+    // code page's own maximum rather than assuming two.
+    // The code page is fixed until a Windows reboot.
+    static UINT sMaxCharSize = []() {
+      CPINFO cpInfo;
+      if (!::GetCPInfo(CP_ACP, &cpInfo)) {
+        MOZ_ASSERT_UNREACHABLE("Couldn't get code page info?");
+        // The max ANSI code page character size at the moment is four bytes.
+        return 4u;
+      }
+      return cpInfo.MaxCharSize;
+    }();
+
+    // |len| is a byte count; the conversion counts UTF-16 code units, and
+    // includes the terminating null.
+    CheckedInt<int> const unitCount = CheckedInt<int>(len) / 2 + 1;
+    CheckedInt<int> const bufferSize = unitCount * sMaxCharSize;
+    if (!bufferSize.isValid()) {
+      return E_FAIL;
+    }
+
+    char* plainTextData = static_cast<char*>(moz_xmalloc(bufferSize.value()));
     auto const _release =
         mozilla::MakeScopeExit([plainTextData]() { ::free(plainTextData); });
 
     char16_t* castedUnicode = reinterpret_cast<char16_t*>(data);
-    int32_t plainTextLen =
-        WideCharToMultiByte(CP_ACP, 0, (LPCWSTR)castedUnicode, len / 2 + 1,
-                            plainTextData, bufferSize, NULL, NULL);
+    int32_t plainTextLen = WideCharToMultiByte(
+        CP_ACP, 0, (LPCWSTR)castedUnicode, unitCount.value(), plainTextData,
+        bufferSize.value(), NULL, NULL);
 
     if (plainTextLen) {
       return assignDataToStg(plainTextData, plainTextLen);
     }
 
     NS_WARNING("Oh no, couldn't convert unicode to plain text");
-    return S_OK;
+    return E_FAIL;
   }
 
   if (aFE.cfFormat == nsClipboard::GetHtmlClipboardFormat()) {
@@ -1682,7 +1704,7 @@ HRESULT nsDataObj::GetText(const nsACString& aDataFlavor, FORMATETC& aFE,
     }
 
     NS_WARNING("Oh no, couldn't convert to HTML");
-    return S_OK;
+    return E_FAIL;
   }
 
   // We assume that any data-format that isn't caught above can be satisfied by
diff --git a/widget/windows/nsDataObjCollection.cpp b/widget/windows/nsDataObjCollection.cpp
index e42d087ff61..36c7900af0d 100644
--- a/widget/windows/nsDataObjCollection.cpp
+++ b/widget/windows/nsDataObjCollection.cpp
@@ -62,6 +62,8 @@ STDMETHODIMP_(ULONG) nsDataObjCollection::Release() {
 
 // IDataObject methods
 STDMETHODIMP nsDataObjCollection::GetData(LPFORMATETC pFE, LPSTGMEDIUM pSTM) {
+  *pSTM = STGMEDIUM{};
+
   static CLIPFORMAT fileDescriptorFlavorA =
       ::RegisterClipboardFormat(CFSTR_FILEDESCRIPTORA);
   static CLIPFORMAT fileDescriptorFlavorW =
@@ -149,7 +151,6 @@ void nsDataObjCollection::AddDataObject(IDataObject* aDataObj) {
 
 // Methods for getting data
 HRESULT nsDataObjCollection::GetFile(LPFORMATETC pFE, LPSTGMEDIUM pSTM) {
-  STGMEDIUM workingmedium;
   FORMATETC fe = *pFE;
   HGLOBAL hGlobalMemory;
   HRESULT hr;
@@ -163,6 +164,7 @@ HRESULT nsDataObjCollection::GetFile(LPFORMATETC pFE, LPSTGMEDIUM pSTM) {
       mozilla::MakeScopeExit([&]() { GlobalFree(hGlobalMemory); });
 
   for (uint32_t i = 0; i < mDataObjects.Length(); ++i) {
+    STGMEDIUM workingmedium{};
     nsDataObj* dataObj = mDataObjects.ElementAt(i);
     hr = dataObj->GetData(&fe, &workingmedium);
     if (hr != S_OK) {
@@ -173,6 +175,8 @@ HRESULT nsDataObjCollection::GetFile(LPFORMATETC pFE, LPSTGMEDIUM pSTM) {
           return hr;
       }
     }
+    auto releaseStgMedium =
+        mozilla::MakeScopeExit([&]() { ReleaseStgMedium(&workingmedium); });
     // Now we need to pull out the filename
     char16_t* buffer = (char16_t*)GlobalLock(workingmedium.hGlobal);
     if (buffer == nullptr) {
@@ -181,7 +185,6 @@ HRESULT nsDataObjCollection::GetFile(LPFORMATETC pFE, LPSTGMEDIUM pSTM) {
     buffer += sizeof(DROPFILES) / sizeof(char16_t);
     filename = buffer;
     GlobalUnlock(workingmedium.hGlobal);
-    ReleaseStgMedium(&workingmedium);
     // Now put the filename into our buffer
     mozilla::CheckedInt<size_t> alloclen =
         mozilla::CheckedInt<size_t>(filename.Length() + 1) * sizeof(char16_t);
@@ -231,7 +234,6 @@ HRESULT nsDataObjCollection::GetFile(LPFORMATETC pFE, LPSTGMEDIUM pSTM) {
 
 template <typename CharT, typename StringT>
 HRESULT nsDataObjCollection::GetText(LPFORMATETC pFE, LPSTGMEDIUM pSTM) {
-  STGMEDIUM workingmedium;
   FORMATETC fe = *pFE;
   HGLOBAL hGlobalMemory;
   HRESULT hr;
@@ -243,6 +245,7 @@ HRESULT nsDataObjCollection::GetText(LPFORMATETC pFE, LPSTGMEDIUM pSTM) {
 
   StringT text;
   for (uint32_t i = 0; i < mDataObjects.Length(); ++i) {
+    STGMEDIUM workingmedium{};
     nsDataObj* dataObj = mDataObjects.ElementAt(i);
     hr = dataObj->GetData(&fe, &workingmedium);
     if (hr != S_OK) {
@@ -253,6 +256,8 @@ HRESULT nsDataObjCollection::GetText(LPFORMATETC pFE, LPSTGMEDIUM pSTM) {
           return hr;
       }
     }
+    auto releaseStgMedium =
+        mozilla::MakeScopeExit([&]() { ReleaseStgMedium(&workingmedium); });
     // Now we need to pull out the text
     CharT* buffer = static_cast<CharT*>(GlobalLock(workingmedium.hGlobal));
     if (buffer == nullptr) {
@@ -260,7 +265,6 @@ HRESULT nsDataObjCollection::GetText(LPFORMATETC pFE, LPSTGMEDIUM pSTM) {
     }
     text = buffer;
     GlobalUnlock(workingmedium.hGlobal);
-    ReleaseStgMedium(&workingmedium);
     // Now put the text into our buffer
     mozilla::CheckedInt<size_t> alloclen =
         mozilla::CheckedInt<size_t>(text.Length()) * sizeof(CharT);
@@ -296,7 +300,6 @@ HRESULT nsDataObjCollection::GetText(LPFORMATETC pFE, LPSTGMEDIUM pSTM) {
 HRESULT nsDataObjCollection::GetFileDescriptors(LPFORMATETC pFE,
                                                 LPSTGMEDIUM pSTM,
                                                 bool aIsWideChar) {
-  STGMEDIUM workingmedium;
   FORMATETC fe = *pFE;
   HGLOBAL hGlobalMemory;
   HRESULT hr;
@@ -309,6 +312,7 @@ HRESULT nsDataObjCollection::GetFileDescriptors(LPFORMATETC pFE,
       mozilla::MakeScopeExit([&]() { GlobalFree(hGlobalMemory); });
 
   for (uint32_t i = 0; i < mDataObjects.Length(); ++i) {
+    STGMEDIUM workingmedium{};
     nsDataObj* dataObj = mDataObjects.ElementAt(i);
     hr = dataObj->GetData(&fe, &workingmedium);
     if (hr != S_OK) {
diff --git a/widget/windows/nsDragService.cpp b/widget/windows/nsDragService.cpp
index 2f016eaa785..a28995314c5 100644
--- a/widget/windows/nsDragService.cpp
+++ b/widget/windows/nsDragService.cpp
@@ -405,7 +405,7 @@ nsDragSession::GetNumDropItems(uint32_t* aNumItems) {
   FORMATETC fe2;
   SET_FORMATETC(fe2, CF_HDROP, 0, DVASPECT_CONTENT, -1, TYMED_HGLOBAL);
   if (SUCCEEDED(mDataObject->QueryGetData(&fe2))) {
-    STGMEDIUM stm;
+    STGMEDIUM stm{};
     if (FAILED(mDataObject->GetData(&fe2, &stm))) {
       *aNumItems = 1;
       return NS_OK;
@@ -428,7 +428,7 @@ nsDragSession::GetNumDropItems(uint32_t* aNumItems) {
   // Next check if we have a virtual file drop.
   SET_FORMATETC(fe2, nsClipboard::GetClipboardFileDescriptorFormatW(), 0,
                 DVASPECT_CONTENT, -1, TYMED_HGLOBAL);
-  STGMEDIUM stm;
+  STGMEDIUM stm{};
 
   if (SUCCEEDED(mDataObject->GetData(&fe2, &stm))) {
     *aNumItems = 0;
Loading diff…