Skip to content

fix: prevent /P permissions overflow from corrupting decryption key - #1630

Open
garcia-d wants to merge 2 commits into
LibrePDF:2.2-java21from
garcia-d:fix/2.2-java21/pdfnumber-int-overflow
Open

garcia-d wants to merge 2 commits into
LibrePDF:2.2-java21from
garcia-d:fix/2.2-java21/pdfnumber-int-overflow

Conversation

@garcia-d

@garcia-d garcia-d commented Sep 14, 2026

Copy link
Copy Markdown

Description of the new Feature/Bugfix

PdfNumber#intValue() narrowed the underlying double with a plain
(int) cast. Some PDF producers write the /Encrypt dictionary's /P
(permissions) entry using its unsigned 32-bit decimal representation
instead of the equivalent signed value (e.g. 4294965956 instead of
-1340) - both encode the identical 4-byte value, and this is spec-legal.

Java's narrowing double-to-int conversion saturates rather than wraps for
an out-of-range value, so such a /P value was misread as
Integer.MAX_VALUE instead of -1340. That corrupted the encryption key
the standard security handler derives, so its own recomputed /U check
failed for every password - including a correct, empty one - and
PdfReader reported BadPasswordException for a document that was never
actually password-protected.

Routed the cast through long first, which reproduces the expected
low-order-32-bit truncation instead of clamping.

Related Issue: none (found while investigating a user-reported PDF, not
tied to a filed issue)

Unit-Tests for the new Feature/Bugfix

  • Unit-Tests added to reproduce the bug
  • Unit-Tests added to the added feature

Compatibilities Issues

No method signatures changed. Any caller previously relying on the
(incorrect) saturated value of an out-of-range PdfNumber#intValue() would
now see the correctly truncated value instead - this is a correctness fix,
not expected to be a compatibility concern in practice.

Your real name

Diego Garcia

Testing details

Added PdfNumberTest (direct truncation coverage) and
DecryptUnsignedPermissionsTest (a PdfReader-level regression test using
a crafted PDF whose /P is written this way with an empty user password).
Full module test suite run locally per branch, no regressions (only 3
pre-existing, unrelated failures on some branches, from a missing OS/2
table in the local macOS Courier.ttc system font).

🤖 Generated with Claude Code

PdfNumber#intValue() narrowed the underlying double with a plain (int)
cast. Some PDF producers write the /Encrypt dictionary's /P (permissions)
entry using its unsigned 32-bit decimal representation instead of the
equivalent signed value (e.g. 4294965956 instead of -1340) - both encode
the identical 4-byte value, and this is spec-legal.

Java's narrowing double-to-int conversion saturates rather than wraps for
an out-of-range value, so such a /P value was misread as
Integer.MAX_VALUE instead of -1340. That corrupted the encryption key the
standard security handler derives, so its own recomputed /U check failed
for every password - including a correct, empty one - and PdfReader
reported BadPasswordException for a document that was never actually
password-protected.

Route the cast through long first, which reproduces the expected
low-order-32-bit truncation instead of clamping.

Add PdfNumberTest covering the truncation directly, and
DecryptUnsignedPermissionsTest, a PdfReader-level regression test using a
crafted PDF whose /P is written this way with an empty user password.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
@codacy-production

Copy link
Copy Markdown

Not up to standards ⛔

NEW Get contextual insights on your PRs based on Codacy's metrics, along with PR and Jira context, without leaving GitHub. Enable AI reviewer
TIP This summary will be updated as you push new changes.

DecryptUnsignedPermissionsTest reached PdfReader's private
ownerPasswordUsed field via reflection (setAccessible), which Codacy
flagged as a high-severity visibility-alteration issue, plus a medium
issue for wrapping the reflection exceptions in a raw RuntimeException.

PdfReader already exposes a public isOwnerPasswordUsed() getter that
returns the same field, so the reflection is unnecessary. Calling it
directly returns the identical value and removes both findings.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
@garcia-d
garcia-d force-pushed the fix/2.2-java21/pdfnumber-int-overflow branch from ac945df to 5d27a07 Compare September 14, 2026 22:17
@sonarqubecloud

Copy link
Copy Markdown

@garcia-d

Copy link
Copy Markdown
Author

Hi @asturio,

This PR backports the fix preventing /P permissions overflow from corrupting the decryption key to the 2.2-java21 branch. The same fix has been opened for every active branch: #1627 (1.3-java8), #1628 (1.4-java11), #1629 (2.0-java17), #1631 (master), #1632 (2.4).

Whenever you get a chance, I'd appreciate your review, and if it looks good, approval/merge into 2.2-java21.

I'd also kindly ask, once merged, if an official release including this fix could be cut — particularly for the Java 8-compatible line (#1627), since I'd rather rely on an official release than patch it in locally.

Thanks for maintaining OpenPDF!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant