Overview

High
Severity
CVSS
No
Exploited ITW
Fixed
Fix Status
ImpactHeap buffer overflow in PDFium
DescriptionHeap buffer overflow in PDFium
ComponentPDFium
Bug ClassOOB
Tracker500036290
Fix commitbce2e6728279 (pdfium) +10/-2
CISA KEVNot listed
CreditedGoogle
Disclosed2026-04-15

Changed Functions

FunctionChangeNotes
for
core/fxge/win32/cfx_psrenderer.cpp
modified

Files Changed

  • core/fxge/win32/cfx_psrenderer.cpp
From bce2e672827992dcd7289e2872ca10c5c20bf056 Mon Sep 17 00:00:00 2001
From: Tom Sepez <tsepez@google.com>
Date: Tue, 07 Apr 2026 15:50:30 -0700
Subject: [PATCH] Use safe arithmetic in CFX_PSRenderer::DrawDIBits()

Hardening suggestion from the AI bot.

Bug: 500036290
Change-Id: Ie521629d06ba944f610b941a8c9e9505fa29aea7
Reviewed-on: https://pdfium-review.googlesource.com/c/pdfium/+/145731
Reviewed-by: Lei Zhang <thestig@chromium.org>
Commit-Queue: Tom Sepez <tsepez@chromium.org>
---

diff --git a/core/fxge/win32/cfx_psrenderer.cpp b/core/fxge/win32/cfx_psrenderer.cpp
index b38f1a2..b8710e5 100644
--- a/core/fxge/win32/cfx_psrenderer.cpp
+++ b/core/fxge/win32/cfx_psrenderer.cpp
@@ -620,8 +620,16 @@
         encoder_iface_->pJpegEncodeFunc(bitmap, &output_buf, &output_size)) {
       filter = "/DCTDecode filter ";
     } else {
-      int src_pitch = width * bytes_per_pixel;
-      output_size = height * src_pitch;
+      FX_SAFE_UINT32 safe_pitch = bytes_per_pixel;
+      safe_pitch *= width;
+      FX_SAFE_UINT32 safe_output_size = safe_pitch;
+      safe_output_size *= height;
+      if (!safe_output_size.IsValid()) {
+        WriteString("\nQ\n");
+        return false;
+      }
+      uint32_t src_pitch = safe_pitch.ValueOrDie();
+      output_size = safe_output_size.ValueOrDie();
       output_buf = FX_Alloc(uint8_t, output_size);
       for (int row = 0; row < height; row++) {
         const uint8_t* src_scan = bitmap->GetScanline(row).data();
Loading diff…

Original Bug Report

reported by vm...@google.com

Potential Integer Overflow leading to OOB Write in CFX_PSRenderer::DrawDIBits

Project Fortify, an experimental security project, has identified the following potential security issue. If you’re a feature owner CC-ed on this bug, please do your best to review these reports without the security team.

Overview: A potential integer overflow exists in PDFium’s PostScript renderer when calculating the memory required for image conversion. When printing a specially crafted PDF with massive dimensions, this calculation overflows, leading to a severely undersized heap allocation. Subsequent rendering loops write attacker-controlled data extensively out-of-bounds, potentially allowing for remote code execution within the sandboxed utility process.

Affected files:

  • third_party/pdfium/core/fxge/win32/cfx_psrenderer.cpp
  • third_party/pdfium/core/fpdfapi/page/cpdf_pageimagecache.cpp
  • third_party/pdfium/core/fpdfapi/page/cpdf_dib.cpp

Estimated timestamp from git blame: 2024-08-09

Description

A potential integer overflow vulnerability exists in CFX_PSRenderer::DrawDIBits (third_party/pdfium/core/fxge/win32/cfx_psrenderer.cpp). When a PDF is printed to a PostScript printer, this function allocates a heap buffer to hold converted image data. The calculation for the buffer size relies on standard 32-bit signed integer math (int). If an image is sufficiently large, the mathematical product of height and src_pitch exceeds INT_MAX, wraps around, and truncates to a small positive value, resulting in an undersized buffer allocation.

Vulnerability Details

In CFX_PSRenderer::DrawDIBits, the buffer size is calculated without safe math wrappers:

int src_pitch = width * bytes_per_pixel;
output_size = height * src_pitch;
output_buf = FX_Alloc(uint8_t, output_size);

While upstream PDFium checks (like in CPDF_DIB::LoadInternal) validate image dimensions using FX_SAFE_UINT32, an attacker can bypass these limitations by utilizing an image configuration that expands later in the pipeline. For example, a 16-bit Grayscale image with dimensions 11,075 x 131,071 requires approximately 2.9 GB uncompressed, which fits perfectly within a valid 32-bit unsigned integer.

Because this image exceeds the 60 MB kHugeImageSize caching threshold (CPDF_PageImageCache::Entry::ContinueGetCachedBitmap), PDFium evaluates it lazily (realize_hint=false), avoiding an immediate allocation.

When CFX_PSRenderer::DrawDIBits processes the image, the 16-bpc format is promoted to a 24-bpp BGR format (bytes_per_pixel = 3). The renderer calculates src_pitch as 11,075 * 3 = 33,225. It then calculates output_size as 131,071 * 33,225, which equals 4,354,833,975. This value overflows a 32-bit signed int and truncates to exactly 59,866,679 bytes. FX_Alloc subsequently allocates this severely undersized 59.8 MB buffer.

In the subsequent copy loop:

for (int row = 0; row < height; row++) {
    uint8_t* dest_scan = UNSAFE_TODO(output_buf + row * src_pitch);
    // ... copies pixels to dest_scan
}
  • Forward OOB Write: Starting around row 1,802, the offset row * 33,225 exceeds the allocated 59.8 MB, causing massive forward out-of-bounds writes of attacker-controlled pixel data.
  • Backward OOB Write: At row 64,635, the signed 32-bit integer offset calculation overflows INT_MAX (64,635 * 33,225 = 2,147,497,875), wrapping to a negative value (-2,147,469,421). This negative offset is sign-extended and added to the output_buf pointer, resulting in a backward out-of-bounds write roughly 2.14 GB before the buffer.

Potential Attack Steps

Note: These are potential steps based on code analysis, as our tooling agent cannot execute code to verify a working proof of concept.

  1. An attacker crafts a malicious PDF containing an Image XObject.
  2. The image specifies Width = 11,075, Height = 131,071, BitsPerComponent = 16, and ColorSpace = DeviceGray, backed by a FlateDecode stream containing carefully structured malicious pixel payloads.
  3. The attacker convinces a victim to open the PDF in Chrome and print it to a PostScript-capable printer on a Windows machine.
  4. PDFium processes the image lazily due to its large uncompressed footprint (~2.9GB).
  5. During PostScript rendering, the image is promoted to 24-bpp, triggering the integer overflow in CFX_PSRenderer::DrawDIBits.
  6. The rendering loop copies the attacker’s decompressed pixel stream wildly out of bounds.
  7. While writing across gigabytes of memory is likely to hit an unmapped guard page and crash, an attacker might exploit race conditions by overwriting critical structures (like IPC objects or vtables) accessed by concurrent threads within the kPdfConversion utility process before the crash occurs, potentially achieving Remote Code Execution.

Suggested Fix

The memory calculation in CFX_PSRenderer::DrawDIBits should utilize FX_SAFE_UINT32 to prevent integer overflow and abort rendering if the required size exceeds representable limits.

FX_SAFE_UINT32 safe_src_pitch = width;
safe_src_pitch *= bytes_per_pixel;

FX_SAFE_UINT32 safe_output_size = safe_src_pitch;
safe_output_size *= height;

if (!safe_output_size.IsValid()) {
    return false; // or handle error accordingly
}

int src_pitch = safe_src_pitch.ValueOrDie();
size_t output_size = safe_output_size.ValueOrDie();

Evaluated with Chrome root at commit: f200f57a19490707ff8bc7aa5de3cbc443a3afad


Results 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.

View on issue tracker