Prevent crashes in win32 GDI code due to null bitmap pointers

This CL adds CFX_DIBBase::RealizeIfNeeded() as the spiritial successor
to CFX_DIBExtractor, which https://pdfium-review.googlesource.com/108371
removed. This is necessary for interactions with some GDI APIs that do
not operate on scanlines and require a pointer to a contiguous bitmap
buffer.

Call RealizeIfNeeded() closer to where the GDI API calls happen, instead
of exactly where the prior CFX_DIBExtractor uses were. With the
RealizeIfNeeded() calls in place, the GDI APIs will no longer receive
null buffer pointers and crash.

Unlike CFX_DIBExtractor, RealizeIfNeeded() won't create a new
CFX_DIBitmap when an existing CFX_DIBBase already has a contiguous
buffer.

Bug: chromium:1466498
Change-Id: I33b46a79c2f0a2ad467e40171db328c4f74b8153
Reviewed-on: https://pdfium-review.googlesource.com/c/pdfium/+/111433
Commit-Queue: Nigi <nigi@chromium.org>
Reviewed-by: Nigi <nigi@chromium.org>
diff --git a/core/fxge/dib/cfx_dibbase.cpp b/core/fxge/dib/cfx_dibbase.cpp
index b64bc28..4aa586f 100644
--- a/core/fxge/dib/cfx_dibbase.cpp
+++ b/core/fxge/dib/cfx_dibbase.cpp
@@ -633,6 +633,15 @@
   return GetRequiredPaletteSize() * sizeof(uint32_t);
 }
 
+#if BUILDFLAG(IS_WIN)
+RetainPtr<const CFX_DIBBase> CFX_DIBBase::RealizeIfNeeded() const {
+  if (GetBuffer().empty()) {
+    return Realize();
+  }
+  return pdfium::WrapRetain(this);
+}
+#endif
+
 RetainPtr<CFX_DIBitmap> CFX_DIBBase::Realize() const {
   return ClipToInternal(nullptr);
 }
diff --git a/core/fxge/dib/cfx_dibbase.h b/core/fxge/dib/cfx_dibbase.h
index ebf1eac..6327888 100644
--- a/core/fxge/dib/cfx_dibbase.h
+++ b/core/fxge/dib/cfx_dibbase.h
@@ -9,6 +9,7 @@
 
 #include <stdint.h>
 
+#include "build/build_config.h"
 #include "core/fxcrt/data_vector.h"
 #include "core/fxcrt/retain_ptr.h"
 #include "core/fxge/dib/fx_dib.h"
@@ -64,6 +65,11 @@
   // Copies into internally-owned palette.
   void SetPalette(pdfium::span<const uint32_t> src_palette);
 
+#if BUILDFLAG(IS_WIN)
+  // Calls Realize() if needed. Otherwise, return `this`.
+  RetainPtr<const CFX_DIBBase> RealizeIfNeeded() const;
+#endif
+
   RetainPtr<CFX_DIBitmap> Realize() const;
   RetainPtr<CFX_DIBitmap> ClipTo(const FX_RECT& rect) const;
   RetainPtr<CFX_DIBitmap> ConvertTo(FXDIB_Format format) const;
diff --git a/core/fxge/win32/cgdi_device_driver.cpp b/core/fxge/win32/cgdi_device_driver.cpp
index 6ae582e..aa1d13f 100644
--- a/core/fxge/win32/cgdi_device_driver.cpp
+++ b/core/fxge/win32/cgdi_device_driver.cpp
@@ -153,7 +153,7 @@
   EndPath(hDC);
 }
 
-ByteString GetBitmapInfo(const RetainPtr<CFX_DIBBase>& source) {
+ByteString GetBitmapInfo(const RetainPtr<const CFX_DIBBase>& source) {
   int len = sizeof(BITMAPINFOHEADER);
   if (source->GetBPP() == 1 || source->GetBPP() == 8) {
     len += sizeof(DWORD) * (int)(1 << source->GetBPP());
@@ -399,24 +399,34 @@
       return false;
     }
 
-    ByteString info = GetBitmapInfo(flipped_source);
+    RetainPtr<const CFX_DIBBase> realized_source =
+        flipped_source->RealizeIfNeeded();
+    if (!realized_source) {
+      return false;
+    }
+    ByteString info = GetBitmapInfo(realized_source);
     ((BITMAPINFOHEADER*)info.c_str())->biHeight *= -1;
     FX_RECT dst_rect(0, 0, src_rect.Width(), src_rect.Height());
-    dst_rect.Intersect(0, 0, flipped_source->GetWidth(),
-                       flipped_source->GetHeight());
+    dst_rect.Intersect(0, 0, realized_source->GetWidth(),
+                       realized_source->GetHeight());
     int dst_width = dst_rect.Width();
     int dst_height = dst_rect.Height();
     ::StretchDIBits(m_hDC, left, top, dst_width, dst_height, 0, 0, dst_width,
-                    dst_height, flipped_source->GetBuffer().data(),
+                    dst_height, realized_source->GetBuffer().data(),
                     (BITMAPINFO*)info.c_str(), DIB_RGB_COLORS, SRCCOPY);
     return true;
   }
 
-  ByteString info = GetBitmapInfo(source);
-  ::SetDIBitsToDevice(m_hDC, left, top, src_rect.Width(), src_rect.Height(),
-                      src_rect.left, source->GetHeight() - src_rect.bottom, 0,
-                      source->GetHeight(), source->GetBuffer().data(),
-                      (BITMAPINFO*)info.c_str(), DIB_RGB_COLORS);
+  RetainPtr<const CFX_DIBBase> realized_source = source->RealizeIfNeeded();
+  if (!realized_source) {
+    return false;
+  }
+  ByteString info = GetBitmapInfo(realized_source);
+  ::SetDIBitsToDevice(
+      m_hDC, left, top, src_rect.Width(), src_rect.Height(), src_rect.left,
+      realized_source->GetHeight() - src_rect.bottom, 0,
+      realized_source->GetHeight(), realized_source->GetBuffer().data(),
+      (BITMAPINFO*)info.c_str(), DIB_RGB_COLORS);
   return true;
 }
 
@@ -430,7 +440,6 @@
     return false;
   }
 
-  ByteString info = GetBitmapInfo(source);
   if ((int64_t)abs(dest_width) * abs(dest_height) <
           (int64_t)source->GetWidth() * source->GetHeight() * 4 ||
       options.bInterpolateBilinear) {
@@ -438,21 +447,29 @@
   } else {
     SetStretchBltMode(m_hDC, COLORONCOLOR);
   }
-  RetainPtr<CFX_DIBBase> stretch_source = source;
+
+  RetainPtr<const CFX_DIBBase> realized_source;
   if (m_DeviceType == DeviceType::kPrinter &&
       ((int64_t)source->GetWidth() * source->GetHeight() >
        (int64_t)abs(dest_width) * abs(dest_height))) {
-    stretch_source = source->StretchTo(dest_width, dest_height,
-                                       FXDIB_ResampleOptions(), nullptr);
+    RetainPtr<CFX_DIBBase> stretch_source = source->StretchTo(
+        dest_width, dest_height, FXDIB_ResampleOptions(), nullptr);
     if (!stretch_source) {
       return false;
     }
-    info = GetBitmapInfo(stretch_source);
+    realized_source = stretch_source->RealizeIfNeeded();
+  } else {
+    realized_source = source->RealizeIfNeeded();
   }
+  if (!realized_source) {
+    return false;
+  }
+
+  ByteString info = GetBitmapInfo(realized_source);
   ::StretchDIBits(m_hDC, dest_left, dest_top, dest_width, dest_height, 0, 0,
-                  stretch_source->GetWidth(), stretch_source->GetHeight(),
-                  stretch_source->GetBuffer().data(), (BITMAPINFO*)info.c_str(),
-                  DIB_RGB_COLORS, SRCCOPY);
+                  realized_source->GetWidth(), realized_source->GetHeight(),
+                  realized_source->GetBuffer().data(),
+                  (BITMAPINFO*)info.c_str(), DIB_RGB_COLORS, SRCCOPY);
   return true;
 }
 
@@ -466,8 +483,13 @@
     return false;
   }
 
-  int width = source->GetWidth();
-  int height = source->GetHeight();
+  RetainPtr<const CFX_DIBBase> realized_source = source->RealizeIfNeeded();
+  if (!realized_source) {
+    return false;
+  }
+
+  int width = realized_source->GetWidth();
+  int height = realized_source->GetHeight();
   struct {
     BITMAPINFOHEADER bmiHeader;
     uint32_t bmiColors[2];
@@ -507,8 +529,8 @@
   // 0xB8074A
 
   ::StretchDIBits(m_hDC, dest_left, dest_top, dest_width, dest_height, 0, 0,
-                  width, height, source->GetBuffer().data(), (BITMAPINFO*)&bmi,
-                  DIB_RGB_COLORS, 0xB8074A);
+                  width, height, realized_source->GetBuffer().data(),
+                  (BITMAPINFO*)&bmi, DIB_RGB_COLORS, 0xB8074A);
 
   SelectObject(m_hDC, hOld);
   DeleteObject(hPattern);
diff --git a/core/fxge/win32/cgdi_plus_ext.cpp b/core/fxge/win32/cgdi_plus_ext.cpp
index 42cf505..f6a9181 100644
--- a/core/fxge/win32/cgdi_plus_ext.cpp
+++ b/core/fxge/win32/cgdi_plus_ext.cpp
@@ -222,16 +222,22 @@
                 dest_height);
     return;
   }
-  int src_pitch = source->GetPitch();
+
+  RetainPtr<const CFX_DIBBase> realized_source = source->RealizeIfNeeded();
+  if (!realized_source) {
+    return;
+  }
+
+  int src_pitch = realized_source->GetPitch();
 
   // `GdipCreateBitmapFromScan0()` requires a `BYTE*` scanline buffer, but in
   // this case, the bitmap only gets read by `GdipDrawImagePointsI()`, then
   // disposed of, so it's safe to cast away `const` here.
-  uint8_t* scan0 =
-      const_cast<uint8_t*>(source->GetBuffer()
-                               .subspan(src_rect.top * src_pitch +
-                                        source->GetBPP() * src_rect.left / 8)
-                               .data());
+  uint8_t* scan0 = const_cast<uint8_t*>(
+      realized_source->GetBuffer()
+          .subspan(src_rect.top * src_pitch +
+                   realized_source->GetBPP() * src_rect.left / 8)
+          .data());
   Gdiplus::GpBitmap* bitmap = nullptr;
   switch (source->GetFormat()) {
     case FXDIB_Format::kArgb:
@@ -254,7 +260,7 @@
       pal[0] = 0;
       pal[1] = 256;
       for (int i = 0; i < 256; i++)
-        pal[i + 2] = source->GetPaletteArgb(i);
+        pal[i + 2] = realized_source->GetPaletteArgb(i);
       CallFunc(GdipSetImagePalette)(bitmap, (Gdiplus::ColorPalette*)pal);
       break;
     }