From 8343c6e7026f54e16edad95fe7271c29b2457f5a Mon Sep 17 00:00:00 2001 From: Mike Kaganski Date: Mon, 21 Sep 2026 14:39:14 +0500 Subject: [PATCH] vcl: size an appended cross-reference stream's offsets by the file WriteXRef wrote /W[1 3 1] and three offset bytes whatever the document. Signing a PDF larger than 16 MB recorded every offset in the appended update modulo 16777216, so the updated catalog and the signature itself pointed into the middle of the original body. The field is as wide as the largest offset needs now. /DecodeParms/Columns counts the bytes of a row apart from its predictor byte, so it follows the field rather than staying at 5, and each byte is shifted out of the offset rather than masked with an int too narrow to hold the mask. Reachable only from a file whose cross-reference is already a stream, which is why our own exports never met it. It has no test: the only path that appends a cross-reference section is signing, and that needs a certificate. Signed-off-by: Mike Kaganski Change-Id: I08279fcb43d0c538f66dc029890a58a76f6dab7e Reviewed-on: https://gerrit.collaboraoffice.com/c/online/+/13206 Tested-by: Jenkins CPCI Reviewed-by: Miklos Vajna (cherry picked from commit 7e52e9eabbda9de67fedab5bf96a71902e53bc28) Reviewed-on: https://gerrit.libreoffice.org/c/core/+/211445 Reviewed-by: Xisco Fauli Tested-by: Jenkins --- vcl/source/filter/ipdf/pdfdocument.cxx | 21 +++++++++++++++------ 1 file changed, 15 insertions(+), 6 deletions(-) diff --git a/vcl/source/filter/ipdf/pdfdocument.cxx b/vcl/source/filter/ipdf/pdfdocument.cxx index aec06e1740e3..d0d3b236bf7b 100644 --- a/vcl/source/filter/ipdf/pdfdocument.cxx +++ b/vcl/source/filter/ipdf/pdfdocument.cxx @@ -637,7 +637,14 @@ void PDFDocument::WriteXRef(sal_uInt64 nXRefOffset, PDFReferenceElement const* p // Write stream data. SvMemoryStream aXRefStream; - const size_t nOffsetLen = 3; + sal_uInt64 nMaxOffset = 0; + for (const auto& rXRef : m_aXRef) + if (rXRef.second.GetDirty()) + nMaxOffset = std::max(nMaxOffset, rXRef.second.GetOffset()); + // the second field has to hold the largest offset the update writes + size_t nOffsetLen = 1; + for (sal_uInt64 nRest = nMaxOffset; nRest > 0xff; nRest >>= 8) + ++nOffsetLen; // 3 additional bytes: predictor, the first and the third field. const size_t nLineLength = nOffsetLen + 3; // This is the line as it appears before tweaking according to the predictor. @@ -680,8 +687,7 @@ void PDFDocument::WriteXRef(sal_uInt64 nXRefOffset, PDFReferenceElement const* p size_t nByte = nOffsetLen - i - 1; // Fields requiring more than one byte are stored with the // high-order byte first. - unsigned char nCh = (rEntry.GetOffset() & (0xff << (nByte * 8))) >> (nByte * 8); - aOrigLine[nPos++] = nCh; + aOrigLine[nPos++] = static_cast(rEntry.GetOffset() >> (nByte * 8)); } // Third field. @@ -701,8 +707,10 @@ void PDFDocument::WriteXRef(sal_uInt64 nXRefOffset, PDFReferenceElement const* p } m_aEditBuffer.WriteNumberAsString(nXRefStreamId); - m_aEditBuffer.WriteOString( - " 0 obj\n<>/Filter/FlateDecode"); + m_aEditBuffer.WriteOString(" 0 obj\n<>/Filter/FlateDecode"); // ID. auto pID = dynamic_cast(m_pXRefStream->Lookup("ID"_ostr)); @@ -780,7 +788,8 @@ void PDFDocument::WriteXRef(sal_uInt64 nXRefOffset, PDFReferenceElement const* p m_aEditBuffer.WriteOString("/Size "); m_aEditBuffer.WriteNumberAsString(m_aXRef.size()); - m_aEditBuffer.WriteOString("/Type/XRef/W[1 3 1]>>\nstream\n"); + m_aEditBuffer.WriteOString(rtl::Concat2View( + "/Type/XRef/W[1 " + OString::number(sal_Int32(nOffsetLen)) + " 1]>>\nstream\n")); aXRefStream.Seek(0); m_aEditBuffer.WriteStream(aXRefStream); m_aEditBuffer.WriteOString("\nendstream\nendobj\n\n");