[
https://issues.apache.org/jira/browse/PDFBOX-6236?page=com.atlassian.jira.plugin.system.issuetabpanels:comment-tabpanel&focusedCommentId=18105955#comment-18105955
]
Michael Klink commented on PDFBOX-6236:
---------------------------------------
As far as I can see, the field object 43 added in the incremental update is
correctly resolvable by programs looking up objects as described in the PDF
spec. (I have not checked the exact cross reference offset, though.)
Also, re-using the xref stream object number of the previous revision is a not
uncommon "optimization"
Strictly speaking, though, there is an error in each of the revisions of your
PDF: There is no entry for the respective cross reference stream object number
in its revision cross references. But this is required by the PDF spec.
Unless you explicitly test for this error, it does not pose a problem.
Nonetheless, it is an error and it may cause libraries to try and "repair" the
cross references. And such "repairs" may favor the wrong object...
I have not checked the PDFBox code but this error may even be the reason PDFBox
re-uses that object number: There is no mapping for it in the cross references,
so apparently it is not taken yet and can be used for anything...
-----
By the way, PDFBox itself used to forget to include the cross reference stream
object number before version 3.0.8, too, see PDFBOX-6176.
If you have created the signed base file (with the error in the cross
references) by signing with DSS and a PDFBox version before 3.0.8, you may want
to try and update PDFBox to 3.0.8 in your DSS instance and sign again. If that
works, the result file should not suffer from the same issue in the outermost
cross references. Then you may want to re-try your example code here (with
PDFBox 3.0.8, though) and see whether the issue still occurs.
> 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]