pdfcpu v0.16.4
This is a security update for crafted PDFs and form or create data that could cause excessive CPU or memory use or terminate a process.
Additive Go API Changes: types.DecodeStreamFunc and types.NewLazyObjectStreamObjectWithDecoder.
Existing constructors and signatures remain unchanged.
Requires Go 1.26 or later.
Release binaries were built with Go 1.27.3, which includes security fixes for crypto/x509.
1) Unbounded object traversal — GHSA-2623-w998-vcr4:
Long chains of PDF objects could exhaust the Go stack and terminate the process during optimization while reading, annotation removal, or page migration. These paths now use iterative traversal. The fix preserves long valid chains, handles objects whose value is another reference, and bounds error-path output.
Fix: 0bb00190.
2) Repeated stream data — GHSA-pr6x-vqwr-4hj2:
Shared or nested stream aliases and oversized declared lengths could make a small PDF retain unbounded encoded or decoded data. Encoded streams now count toward the existing per-read budget, lengths past the end of the input no longer size buffers, and compressed xref entries that share one current object-stream index are rejected.
Filtered decodes through XRefTable.DecodeStream and lazy object streams each receive a separate cumulative allowance
A = max(1 GiB, 2 × MaxDecodeBytes); both default to 1 GiB. The allowances count cumulative decoding, not live memory.
Object streams remain lazy, image extraction remains subject to its per-stream and image limits, and a negative
MaxDecodeBytes disables these limits.
Fix: 82c88cec.
3) Poster tile grids — GHSA-g5q6-53hx-f68j:
A crafted page size could make poster create an unbounded number of tile definitions and output pages, consuming
excessive CPU and memory. Poster output is now limited to 1,000 tiles per source page, and oversized grids are rejected
before page content is transformed or tile objects are allocated. Zero, negative and non-finite page or tile dimensions
are rejected.
Tiles produced by poster, cut and ndown now share one resource dictionary per source page instead of carrying a
copy each. With a margin, each tile references the shared page content through a content array instead of embedding a
copy of it. Rendered output is unchanged.
Fix: a9725db6.
4) Create table dimensions — GHSA-cphg-88q6-whwh:
Table rows and cols in a create template were unbounded, so a few hundred bytes of JSON could make create
allocate and render without limit. All tables of a template now share a limit of 100,000 rows plus columns. A table
whose anchor is invalid, or given together with pos, is now rejected; previously its remaining validation was
skipped.
Fix: 5367e878.
5) Unbounded page numbers — GHSA-gx4m-7r76-vpww:
A large page number in a create template or form-fill data could make pdfcpu allocate excessive memory or stall.
create now limits additions to 1,000 pages beyond the existing document; form fill and form multifill accept image
page numbers only within the document. Both paths reject page numbers below 1.
Fix: ebac361a.
6) Bookmark split page numbers — GHSA-p2r6-9jcq-fp7v:
An outline destination with a very large page number could make split -m bookmark allocate memory proportional to
that number, or panic for library callers. Bookmark page spans are now checked against the page count before their
pages are listed; such files fail with the same "invalid page number" error as before.
Fix: 70f1a860.
7) Shared resource graphs — GHSA-vwmc-mhq7-fqhc:
Validation could repeatedly process the same shared object through different paths in resource, pattern, property-list and visibility-expression graphs. A small PDF could therefore keep validation busy for an extended time. The fix reuses successful validation results while preserving checks on deeper paths and existing cycle handling. Commands that validate PDFs during reading benefit from the fix.
Fix: 7653f158.
8) Shared object comparison graphs — GHSA-8pv5-h3hv-xhc9:
Object comparison could repeatedly process the same pair of indirect objects when font, image, form XObject or content-stream dictionaries contained shared reference graphs. The comparison is used by default for font, image and form XObject deduplication, validation's info-dictionary reconciliation and image reuse during create; content-stream deduplication is optional. A small PDF could therefore cause exponential comparison work. Object comparison now memoizes indirect-reference pairs for the duration of each comparison, limits distinct pairs to the document's object table size and enforces the existing recursion-depth limit. Reaching either limit conservatively treats the objects as different, so callers keep both objects instead of failing the command; the optimize log reports the cutoff.
Fix: d6abb27e.
Thanks
Special thanks to Black Water (@Manyo9) for reporting these vulnerabilities.
Changelog
- [0f5ac7] bump version
- [a9725d] fix GHSA-g5q6-53hx-f68j: bound poster tile grids
- [d6abb2] fix GHSA-8pv5-h3hv-xhc9: memoize indirect-reference pairs in object comparison
- [5367e8] fix GHSA-cphg-88q6-whwh: bound create table rows and columns
- [70f1a8] fix GHSA-p2r6-9jcq-fp7v: check bookmark page spans before splitting
- [82c88c] fix GHSA-pr6x-vqwr-4hj2: bound repeated stream data while reading
- [ebac36] fix GHSA-gx4m-7r76-vpww: bound page numbers in create and form fill
- [0bb001] fix GHSA-2623-w998-vcr4: walk object graphs without recursion
- [7653f1] fix GHSA-vwmc-mhq7-fqhc: validate shared resource graphs once per depth
- [358a9a] add GHSA regression tests and use ghsa_<id> test naming