CVE-2025-1918
Overview
Background
- PDFium
- Chrome’s bundled PDF rendering engine that parses and renders PDF documents inside the renderer/PDF process.
- `FPDFPage_GetRotation()`
- a PDFium public C API that returns a page’s rotation as an integer, using
-1to signal an error. - `Rotation`
- a scoped C++
enum classinpdfium_page.ccwith only four valid values (kRotate0=0,kRotate90=1,kRotate180=2,kRotate270=3). - `static_cast<Rotation>`
- a raw cast that reinterprets an arbitrary integer as an enumerator without validating that the value is one of the defined enumerators.
Root Cause Analysis
In PDFiumPage::GetBoundingBox(), the pre-patch code executed Rotation rotation = static_cast<Rotation>(FPDFPage_GetRotation(page));, blindly casting the API’s return value into the four-valued Rotation enum. When FPDFPage_GetRotation() returns -1 to indicate failure, this produces a Rotation object holding a value (-1) that has no corresponding enumerator, violating the invariant that a Rotation is always one of the four defined cases. That out-of-range enum value was then passed to GetRotatedRectF() and GetEffectiveCropBox(), which switch/index on the rotation and can read past the intended set of cases, producing the out-of-bounds read.
The fix introduces GetRotationFromRawValue(), which maps only 0-3 to valid enumerators and returns std::nullopt for anything else; GetBoundingBox() now checks rotation.has_value() and returns an empty gfx::RectF() before any consumer runs, restoring the invariant.
-1) directly into a four-valued enum class without validation, so the fix replaces the raw static_cast<Rotation> with a validating GetRotationFromRawValue() lookup that rejects out-of-range values before they reach rotation-dependent geometry code.Attack Path
- Craft a malformed page
An attacker delivers a PDF whose page causes
FPDFPage_GetRotation()to return the error sentinel-1rather than a valid 0-3 rotation. - Reach `GetBoundingBox()`
Rendering or measuring the page invokes
PDFiumPage::GetBoundingBox(), which (pre-patch) casts the-1into aRotationenum with no defined enumerator. - Propagate the invalid enum
The bad
Rotationvalue flows intoGetRotatedRectF()andGetEffectiveCropBox(), whose rotation-keyed logic reads outside the intended case set (inferred sink; the diff shows the invalid value being passed but not the exact indexed read). - Trigger the OOB read
The out-of-range enum causes an out-of-bounds read in PDFium as reported by the advisory, potentially leaking adjacent memory contents into computed geometry
(inferred).
Impact Assessment
(inferred) and not demonstrated by the patch. The only precondition is processing an attacker-controlled PDF page whose rotation query fails.Changed Functions
| Function | Change | Notes |
|---|---|---|
switchpdf/pdfium/pdfium_page.cc |
modified |
Files Changed
pdf/pdfium/pdfium_page.cc
Audit Directions
- Error sentinel cast into enumSearch for
static_cast<SomeEnum>(api_call())where the underlying API can return-1or other error sentinels, especially PDFiumFPDF*getters, and verify the value is validated before use. - Unchecked enum-keyed lookupsAudit
switchstatements and array indexing keyed onenum classvalues that originate from external/parsed data, ensuring adefault/out-of-range branch exists and cannot read past defined cases. - PDFium public-API return valuesReview other callers of PDFium C APIs in
pdf/pdfium/for missing checks on documented error returns before the value is consumed by geometry or memory operations.
Patch
From ba6a7c96e3ce949154a188aecb117fb04f2bc2ac Mon Sep 17 00:00:00 2001
From: Lei Zhang <thestig@chromium.org>
Date: Fri, 10 Jan 2025 17:06:26 -0800
Subject: [PATCH] PDF: Handle FPDFPage_GetRotation() error in PDFiumPage::GetBoundingBox()
If FPDFPage_GetRotation() returns -1, do not cast that to enum class
Rotation, which does not have a corresponding enum value.
Bug: 388557904
Change-Id: Id6e3c788a67b6ce4c36e09b38bdb913ad548dd13
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/6168649
Commit-Queue: Lei Zhang <thestig@chromium.org>
Code-Coverage: findit-for-me@appspot.gserviceaccount.com <findit-for-me@appspot.gserviceaccount.com>
Reviewed-by: Andy Phan <andyphan@chromium.org>
Cr-Commit-Position: refs/heads/main@{#1405103}
---
diff --git a/pdf/pdfium/pdfium_page.cc b/pdf/pdfium/pdfium_page.cc
index 3daf6097..72150ac 100644
--- a/pdf/pdfium/pdfium_page.cc
+++ b/pdf/pdfium/pdfium_page.cc
@@ -79,6 +79,21 @@
kRotate270 = 3,
};
+std::optional<Rotation> GetRotationFromRawValue(int rotation) {
+ switch (rotation) {
+ case 0:
+ return Rotation::kRotate0;
+ case 1:
+ return Rotation::kRotate90;
+ case 2:
+ return Rotation::kRotate180;
+ case 3:
+ return Rotation::kRotate270;
+ default:
+ return std::nullopt;
+ }
+}
+
gfx::RectF FloatPageRectToPixelRect(FPDF_PAGE page, const gfx::RectF& input) {
int output_width = FPDF_GetPageWidthF(page);
int output_height = FPDF_GetPageHeightF(page);
@@ -746,9 +761,14 @@
return gfx::RectF();
}
+ std::optional<Rotation> rotation =
+ GetRotationFromRawValue(FPDFPage_GetRotation(page));
+ if (!rotation.has_value()) {
+ return gfx::RectF();
+ }
+
// Page width and height are already swapped based on page rotation.
gfx::SizeF page_size(FPDF_GetPageWidthF(page), FPDF_GetPageHeightF(page));
- Rotation rotation = static_cast<Rotation>(FPDFPage_GetRotation(page));
// Start with bounds with the left and bottom values at the max possible
// bounds and the right and top values at the min possible bounds. Bounds are
@@ -779,10 +799,10 @@
}
gfx::RectF bounding_box =
- GetRotatedRectF(rotation, page_size, largest_bounds);
+ GetRotatedRectF(rotation.value(), page_size, largest_bounds);
gfx::RectF effective_crop_box =
- GetEffectiveCropBox(page, rotation, page_size);
+ GetEffectiveCropBox(page, rotation.value(), page_size);
// If the bounding box is empty, default to the effective crop box.
if (bounding_box.IsEmpty()) {
Original Bug Report
access-violation on unknown address 0x7ffde90a6f3c in chrome_pdf::`anonymous namespace'::GetRotatedRectF
VULNERABILITY DETAILS access-violation on unknown address 0x7ffde90a6f3c in chrome_pdf::`anonymous namespace’::GetRotatedRectF
VERSION Chromium 133.0.6941.0 (Developer Build) (64-bit) OS Windows 11 Version 24H2 (Build 26100.2605)
REPRODUCTION CASE
- Download the latest asan build.
- run the command: ./chrome.exe –no-sandbox –user-data-dir=C:/tmp/zzz http://localhost:8000/poc.html
FOR CRASHES, PLEASE INCLUDE THE FOLLOWING ADDITIONAL INFORMATION Type of crash: [tab]
==25228==ERROR: AddressSanitizer: access-violation on unknown address 0x7ffde90a6f3c (pc 0x7ff9cbff8c9e bp 0x00db1d5fb9a0 sp 0x00db1d5fb840 T0) ==25228==The signal is caused by a READ memory access. ==25228==*** WARNING: Failed to initialize DbgHelp! *** ==25228==*** Most likely this means that the app is already *** ==25228==*** using DbgHelp, possibly with incompatible flags. *** ==25228==*** Due to technical reasons, symbolization might crash *** ==25228==*** or produce wrong results. *** [31272:31316:0109/110645.574:ERROR:fm_registration_token_uploader.cc(186)] Client is missing for kUser scope [31272:31316:0109/110645.574:ERROR:fm_registration_token_uploader.cc(186)] Client is missing for kUser scope [31272:25008:0109/110646.321:ERROR:registration_request.cc(291)] Registration response error message: DEPRECATED_ENDPOINT #0 0x7ff9cbff8c9d in chrome_pdf::`anonymous namespace’::GetRotatedRectF C:\b\s\w\ir\cache\builder\src\pdf\pdfium\pdfium_page.cc:354 #1 0x7ff9cbff8c9d in chrome_pdf::PDFiumPage::GetBoundingBox(void) C:\b\s\w\ir\cache\builder\src\pdf\pdfium\pdfium_page.cc:782:7 #2 0x7ff9dbd8ad0c in chrome_pdf::PdfViewWebPlugin::HandleGetPageBoundingBoxMessage(class base::Value::Dict const &) C:\b\s\w\ir\cache\builder\src\pdf\pdf_view_web_plugin.cc:1654:35 #3 0x7ff9dbd89fb0 in chrome_pdf::PdfViewWebPlugin::OnMessage(class base::Value::Dict const &) C:\b\s\w\ir\cache\builder\src\pdf\pdf_view_web_plugin.cc:1608:3 #4 0x7ff9df92c078 in base::internal::DecayedFunctorTraits<void (chrome_pdf::PostMessageReceiver::Client::)(const base::Value::Dict &),base::WeakPtr<chrome_pdf::PostMessageReceiver::Client> &&,base::Value::Dict &&>::Invoke C:\b\s\w\ir\cache\builder\src\base\functional\bind_internal.h:729 #5 0x7ff9df92c078 in base::internal::InvokeHelper<1,base::internal::FunctorTraits<void (chrome_pdf::PostMessageReceiver::Client::&&)(const base::Value::Dict &),base::WeakPtr<chrome_pdf::PostMessageReceiver::Client> &&,base::Value::Dict &&>,void,0,1>::MakeItSo C:\b\s\w\ir\cache\builder\src\base\functional\bind_internal.h:945 #6 0x7ff9df92c078 in base::internal::Invoker<base::internal::FunctorTraits<void (chrome_pdf::PostMessageReceiver::Client::&&)(const base::Value::Dict &),base::WeakPtr<chrome_pdf::PostMessageReceiver::Client> &&,base::Value::Dict &&>,base::internal::BindState<1,1,0,void (chrome_pdf::PostMessageReceiver::Client::)(const base::Value::Dict &),base::WeakPtr<chrome_pdf::PostMessageReceiver::Client>,base::Value::Dict>,void ()>::RunImpl C:\b\s\w\ir\cache\builder\src\base\functional\bind_internal.h:1058 #7 0x7ff9df92c078 in base::internal::Invoker<struct base::internal::FunctorTraits<void (__cdecl chrome_pdf::PostMessageReceiver::Client::&&)(class base::Value::Dict const &), class base::WeakPtr<class chrome_pdf::PostMessageReceiver::Client> &&, class base::Value::Dict &&>, struct base::internal::BindState<1, 1, 0, void (__cdecl chrome_pdf::PostMessageReceiver::Client::)(class base::Value::Dict const &), class base::WeakPtr<class chrome_pdf::PostMessageReceiver::Client>, class base::Value::Dict>, (void)>::RunOnce(class base::internal::BindStateBase *) C:\b\s\w\ir\cache\builder\src\base\functional\bind_internal.h:971:12 #8 0x7ff9cd966acc in base::OnceCallback<void ()>::Run C:\b\s\w\ir\cache\builder\src\base\functional\callback.h:156 #9 0x7ff9cd966acc in base::TaskAnnotator::RunTaskImpl(struct base::PendingTask &) C:\b\s\w\ir\cache\builder\src\base\task\common\task_annotator.cc:210:34 #10 0x7ff9d29a5a94 in base::TaskAnnotator::RunTask C:\b\s\w\ir\cache\builder\src\base\task\common\task_annotator.h:106 #11 0x7ff9d29a5a94 in base::sequence_manager::internal::ThreadControllerWithMessagePumpImpl::DoWorkImpl(class base::LazyNow *) C:\b\s\w\ir\cache\builder\src\base\task\sequence_manager\thread_controller_with_message_pump_impl.cc:472:23 #12 0x7ff9d29a47e9 in base::sequence_manager::internal::ThreadControllerWithMessagePumpImpl::DoWork(void) C:\b\s\w\ir\cache\builder\src\base\task\sequence_manager\thread_controller_with_message_pump_impl.cc:332:40 #13 0x7ff9d29e761e in base::MessagePumpDefault::Run(class base::MessagePump::Delegate ) C:\b\s\w\ir\cache\builder\src\base\message_loop\message_pump_default.cc:40:55 #14 0x7ff9d29a7782 in base::sequence_manager::internal::ThreadControllerWithMessagePumpImpl::Run(bool, class base::TimeDelta) C:\b\s\w\ir\cache\builder\src\base\task\sequence_manager\thread_controller_with_message_pump_impl.cc:645:12 #15 0x7ff9cd9c4e7e in base::RunLoop::Run(class base::Location const &) C:\b\s\w\ir\cache\builder\src\base\run_loop.cc:134:14 #16 0x7ff9d19f5535 in content::RendererMain(struct content::MainFunctionParams) C:\b\s\w\ir\cache\builder\src\content\renderer\renderer_main.cc:337:16 #17 0x7ff9cb731b81 in content::RunOtherNamedProcessTypeMain(class std::__Cr::basic_string<char, struct std::__Cr::char_traits<char>, class std::Cr::allocator<char>> const &, struct content::MainFunctionParams, class content::ContentMainDelegate *) C:\b\s\w\ir\cache\builder\src\content\app\content_main_runner_impl.cc:777:14 #18 0x7ff9cb733dc0 in content::ContentMainRunnerImpl::Run(void) C:\b\s\w\ir\cache\builder\src\content\app\content_main_runner_impl.cc:1146:10 #19 0x7ff9cb7280a5 in content::RunContentProcess(struct content::ContentMainParams, class content::ContentMainRunner *) C:\b\s\w\ir\cache\builder\src\content\app\content_main.cc:348:36 #20 0x7ff9cb728c4d in content::ContentMain(struct content::ContentMainParams) C:\b\s\w\ir\cache\builder\src\content\app\content_main.cc:361:10 #21 0x7ff9bc7a1681 in ChromeMain C:\b\s\w\ir\cache\builder\src\chrome\app\chrome_main.cc:222:12 #22 0x7ff62c2b435d in MainDllLoader::Launch(struct HINSTANCE, class base::TimeTicks) C:\b\s\w\ir\cache\builder\src\chrome\app\main_dll_loader_win.cc:201:12 #23 0x7ff62c2b2006 in main C:\b\s\w\ir\cache\builder\src\chrome\app\chrome_exe_main_win.cc:352:20 #24 0x7ff62c82f12b in invoke_main D:\a_work\1\s\src\vctools\crt\vcstartup\src\startup\exe_common.inl:78 #25 0x7ff62c82f12b in __scrt_common_main_seh D:\a_work\1\s\src\vctools\crt\vcstartup\src\startup\exe_common.inl:288 #26 0x7ffaa368e8d6 (C:\WINDOWS\System32\KERNEL32.DLL+0x18002e8d6) #27 0x7ffaa4e3fbcb (C:\WINDOWS\SYSTEM32\ntdll.dll+0x1800dfbcb)
==25228==Register values: rax = 7ff9e90a6f40 rbx = db1d5fb920 rcx = 785cea1acbec rdx = ffffffff rdi = 12106f50f7c0 rsi = db1d5fbb40 rbp = db1d5fb9a0 rsp = db1d5fb840 r8 = 11f26fa71200 r9 = 20 r10 = 12346f806000 r11 = fdfdfdfdfdfdfdfd r12 = db1d5fb840 r13 = 1ec6f480000 r14 = 12126f5c32c0 r15 = 0 AddressSanitizer can not provide additional info. SUMMARY: AddressSanitizer: access-violation C:\b\s\w\ir\cache\builder\src\pdf\pdfium\pdfium_page.cc:354 in chrome_pdf::`anonymous namespace’::GetRotatedRectF
==25228==ADDITIONAL INFO
==25228==Note: Please include this section with the ASan report. Task trace: #0 0x7ff9df92b9eb in chrome_pdf::PostMessageReceiver::PostMessageW(class v8::Local<class v8::Value>) C:\b\s\w\ir\cache\builder\src\pdf\post_message_receiver.cc:131:7 #1 0x7ff9cdc451c5 in mojo::SimpleWatcher::Context::Notify(unsigned int, struct MojoHandleSignalsState, unsigned int) C:\b\s\w\ir\cache\builder\src\mojo\public\cpp\system\simple_watcher.cc:102:13
Command line: "./chrome.exe" --type=renderer --string-annotations --user-data-dir=C:/tmp/zzz --no-pre-read-main-dll --pdf-renderer --no-sandbox --file-url-path-alias="/gen=asan\gen" --video-capture-use-gpu-memory-buffer --lang=en-US --js-flags=--jitless --device-scale-factor=1.5 --num-raster-threads=4 --enable-main-frame-before-activation --renderer-client-id=9 --time-ticks-at-unix-epoch=-1736218503358671 --launch-time-ticks=173501150759 --metrics-shmem-handle=4356,i,11093616363079205023,10641701609597647046,2097152 --field-trial-handle=4540,i,11787306298688871873,6099382784474387663,262144 --variations-seed-version --mojo-platform-channel-handle=4536 /prefetch:1
==25228==END OF ADDITIONAL INFO ==25228==ABORTING