[ 
https://issues.apache.org/jira/browse/PDFBOX-6236?page=com.atlassian.jira.plugin.system.issuetabpanels:comment-tabpanel&focusedCommentId=18106114#comment-18106114
 ] 

Tilman Hausherr commented on PDFBOX-6236:
-----------------------------------------

yes there are gaps... this was previously discussed in PDFBOX-5382 but never 
resolved.

20.08.2026 05:41:47 WARN  signature.SigUtils.checkCrossReferenceTable:468 - 
Object 15 missing, signature verification may fail in Adobe Reader, see 
https://stackoverflow.com/questions/71267471/
20.08.2026 05:41:47 WARN  signature.SigUtils.checkCrossReferenceTable:468 - 
Object 21 missing, signature verification may fail in Adobe Reader, see 
https://stackoverflow.com/questions/71267471/
20.08.2026 05:41:47 WARN  signature.SigUtils.checkCrossReferenceTable:468 - 
Object 32 missing, signature verification may fail in Adobe Reader, see 
https://stackoverflow.com/questions/71267471/


> 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
>    Affects Versions: 3.0.6 PDFBox
>         Environment: - Java 25 (Eclipse Temurin 25+36)
> - OS: Linux
>            Reporter: Riadh
>            Priority: Major
>         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]

Reply via email to