[
https://issues.apache.org/jira/browse/PDFBOX-6236?page=com.atlassian.jira.plugin.system.issuetabpanels:all-tabpanel
]
Andreas Lehmkühler resolved PDFBOX-6236.
----------------------------------------
Resolution: Fixed
[~tounakty2026] thanks for the report.
In the end the fix was quite easy. We have to ensure that the origin trailer
size is used to initialize the highest object number within COSWriter when
performing incremental updates
> PDDocument.saveIncremental() adds a new field object whose number collides
> with the increment's own XRef stream object, on a PDF that already contains a
> prior signature
> ------------------------------------------------------------------------------------------------------------------------------------------------------------------------
>
> Key: PDFBOX-6236
> URL: https://issues.apache.org/jira/browse/PDFBOX-6236
> Project: PDFBox
> Issue Type: Bug
> Components: Writing
> Affects Versions: 3.0.6 PDFBox
> Environment: - Java 25 (Eclipse Temurin 25+36)
> - OS: Linux
> Reporter: Riadh
> Assignee: Andreas Lehmkühler
> Priority: Major
> Fix For: 3.0.9 PDFBox, 4.0.0
>
> Attachments: PDFBOX-BUG-REPORT.md, ReproPdfBoxOnly.java,
> already-signed.pdf, with-empty-field.pdf
>
>
> ## Summary
> Adding a new AcroForm field (a `PDSignatureField`, but the field type does
> not appear to matter) to a PDF that already contains one prior signature,
> then saving it with `PDDocument#saveIncremental(OutputStream)`, produces a
> document in which the new field's indirect object number is ambiguous:
> PDFBox's own reader resolves it to the expected field dictionary, but any
> reader that builds a flat cross-reference map across the full `/Prev`
> revision chain (e.g. pypdf, PyPDF2 — tested with both, same result) resolves
> the very same object number to the incremental update's own `/XRef` stream
> dictionary instead. No signing is actually performed in this reproduction —
> this is a pure `saveIncremental()` issue, independent of any
> digital-signature or third-party library (e.g. it does **not** require the EU
> DSS library; the attached reproduction uses only `org.apache.pdfbox.*`
> classes).
> ## Steps to reproduce
> 1. Start from any PDF that already has one prior signature (any signature
> type/level). In the attached `already-signed.pdf`, the highest existing
> object number is 42 (`COSDocument#getHighestXRefObjectNumber() == 42`), the
> AcroForm has 2 fields: a `/Sig` field at object 9 and a text field at object
> 10. 2. Add a new signature field and save incrementally:
> {code:java}
> try (PDDocument doc = Loader.loadPDF(new File("already-signed.pdf"))) {
> PDAcroForm acroForm = doc.getDocumentCatalog().getAcroForm();
> PDPage page = doc.getPage(0);
> PDSignatureField signatureField = new PDSignatureField(acroForm);
> signatureField.setPartialName("signature_signed_pdfbox_only");
> PDAnnotationWidget widget = signatureField.getWidgets().get(0);
> widget.setRectangle(new PDRectangle(50, 50, 100, 50));
> widget.setPage(page);
> page.getAnnotations().add(widget);
> acroForm.getFields().add(signatureField);
> try (FileOutputStream fos = new FileOutputStream("with-empty-field.pdf"))
> {
> doc.saveIncremental(fos);
> }
> } {code}
> (Full source in the attached `ReproPdfBoxOnly.java`.)
> 3. Inspect the resulting `with-empty-field.pdf`.
> ## Expected result
> The AcroForm's `/Fields` array has 3 entries, and the new entry (object
> number 43 in this reproduction) resolves — consistently, from any conformant
> PDF reader — to the new field's `/Widget` dictionary.
> ## Actual result
> - Re-opening the file **with PDFBox itself** resolves object `43 0 R`
> correctly:
> {code:java}
> field[2] objNum=43 /Type=Annot /FT=Sig /T=signature_signed_pdfbox_only
> hasV=false {code}
> - Reading the **same file** with `pypdf` (6.16.1) or `PyPDF2` (both tested,
> identical result) resolves the very same reference (`43 0 R`) in the
> AcroForm's `/Fields` array to the increment's own `/XRef` stream dictionary
> instead:
> {code:java}
> >>> fields[2].get_object()
> {'/ID': [...], '/Info': ..., '/Root': ..., '/Prev': 55240, '/Type': '/XRef',
> '/Size': 44, '/Index': [0, 2, 33, 10], '/W': [1, 3, 0], '/Filter':
> '/FlateDecode'}
> >>> pdf.get_fields().keys()
> dict_keys(['signature', 'prénom']) # the new field is invisible {code}
> Object number `43` is therefore used ambiguously within the same incremental
> update: PDFBox's writer appears to reuse/collide the number allocated to the
> new field object with the number allocated to the revision's own `/XRef`
> stream object. Since a standards-compliant reader merging the `/Prev` xref
> chain keeps only the *last* xref entry seen per object number, and PDFBox
> itself resolves it one way while other libraries resolve it the other way,
> this indicates PDFBox's `COSWriter` writes conflicting/duplicate xref
> information for object `43` within this single incremental revision.
>
> ## Suggested area to investigate
> `org.apache.pdfbox.pdfwriter.COSWriter`:
> - The `number` counter used both to allocate new object keys while writing
> the body (`getObjectKey(COSBase)`) and to allocate the xref-stream's own key
> in `doWriteXRefInc()` (`COSObjectKey xrefStreamKey = new
> COSObjectKey(++number, 0)`).
> - This counter is seeded from `COSDocument#getHighestXRefObjectNumber()`,
> itself computed by `COSParser` by merging the `/Prev` xref chain at load time
> (`org/apache/pdfbox/pdfparser/COSParser.java`, around the code that does
> `document.setHighestXRefObjectNumber(...)` after building
> `document.getXrefTable()`).
> - When the input document already has objects from a prior
> signature/incremental revision, this next-object-number computation appears
> not to always fully account for the existing revision chain, so the number
> handed out for the new field object collides with the number subsequently
> used (or already used) for the increment's own xref-stream object.
> This looks adjacent to — but distinct from — PDFBOX-4997 (unmodified
> `COSName`-only objects being needlessly rewritten during incremental
> updates), which was found while investigating this issue but does not by
> itself explain the object-number collision described here.
> ## Impact
> Any workflow that adds a new form/signature field to a PDF that already
> carries a prior signature, then saves incrementally (a common pattern for
> sequential multi-signer PAdES workflows, e.g. via the EU DSS library's
> `PAdESService#addNewSignatureField`, which is how we first found this)
> produces a PDF where the newly added field is not reliably discoverable by
> other PDF libraries, even though PDFBox's own reader can still find it — a
> real interoperability/validation risk for any consumer not using this exact
> PDFBox version and reading strategy.
> ## Attachments
> - `already-signed.pdf` — input document with 1 existing signature.
> - `with-empty-field.pdf` — output of the reproduction, already exhibiting the
> corrupted `/Fields` entry.
> - `ReproPdfBoxOnly.java` — full minimal reproduction source (pure PDFBox, no
> external dependency beyond PDFBox itself).
--
This message was sent by Atlassian Jira
(v8.20.10#820010)
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]