ANT-2026-J00S1S9Y · libreoffice/core
oob-write medium
Severity Claude high · Security research firm high · Maintainer medium
Discovered by Claude Mythos Preview
Anthropic's analysis, sealed at approval. Disclosure to the maintainer was performed by Ada Logics.
ANT-2026-J00S1S9Y: PDF encryption key-length field drives out-of-bounds key buffer write
In LibreOffice's PDF import component, PDFFile::setupDecryptionData stores m_nKeyLength = /Length / 8 from the attacker-authored /Encrypt dictionary without clamping to the spec maximum of 16 bytes. PDFFile::decrypt then writes five object-number/generation bytes starting at m_aDecryptionKey[m_nKeyLength], but m_aDecryptionKey is a fixed 21-byte array and the last member of the new-allocated PDFFileImplData struct. The gating check usesSupportedEncryptionFormat() only validates /V and /R, and password_to_key() clamps its return value but never m_nKeyLength, so an attacker who sets /Length to e.g. 65536 and precomputes /U for a chosen password gets a 5-byte write at an attacker-controlled offset (8 KB) past the struct into adjacent heap memory. The path fires when a victim opens an encrypted hybrid PDF and enters any password at the prompt, yielding heap corruption and potential code execution via heap grooming.
Target
Project: libreoffice/core
Location: sdext/source/pdfimport/pdfparse/pdfentries.cxx:1058
Discovery: static analysis — not yet dynamically reproduced
Technical Details
setupDecryptionData (pdfentries.cxx:1336) derives m_nKeyLength directly from the attacker-controlled /Encrypt /Length field with no upper bound, and decrypt (pdfentries.cxx:1058-1063) uses that value as the starting index for five writes into the fixed-size sal_uInt8[ENCRYPTION_KEY_LEN+5] = 21-byte m_aDecryptionKey array. Because m_aDecryptionKey is the final member of the heap-allocated PDFFileImplData (new'd at :1269), any m_nKeyLength > 16 writes beyond the allocation. No guard on the path — usesSupportedEncryptionFormat() (:1197-1203) checks only /V and /R, and password_to_key() (:1129-1131) clamps its return to 16 but does not write back to m_nKeyLength.
Reproduction
- Craft a hybrid PDF with /Encrypt dict setting /V 2, /R 3, and /Length to a large value (e.g. 65536), and precompute /U so a chosen/empty password authenticates
- Include an AdditionalStreams reference whose object number/generation yield the desired 5 payload bytes, and groom the heap via other PDF objects
- Deliver the PDF to the victim (e.g. phishing) and have them open it in LibreOffice
- Victim enters the password at the prompt; setupDecryptionData records m_nKeyLength = /Length / 8
- getAdditionalStream → writeStream → getDeflatedStream → decrypt writes 5 bytes at m_aDecryptionKey[m_nKeyLength..+4], corrupting the adjacent heap allocation
[No reproducer or sanitizer output attached — request from security-cvd@anthropic.com if needed.]
Suggested Fix
Reject or clamp /Encrypt /Length to the PDF specification maximum (128 bits / 16 bytes) before storing m_nKeyLength, so the per-object key expansion can never index past the fixed key buffer.
Acknowledgement
This vulnerability was discovered by Claude, Anthropic's AI assistant, and triaged by the Anthropic security team in collaboration with Anthropic Research. Please direct questions to security-cvd@anthropic.com and reference ANT-2026-J00S1S9Y.
Reference: ANT-2026-J00S1S9Y
Anthropic CVD Policy: https://www.anthropic.com/coordinated-vulnerability-disclosure
Triage and disclosure were performed by Ada Logics.
- Verdict
- true positive
- Severity
- high
The change that resolved this finding.
diff --git a/sdext/source/pdfimport/pdfparse/pdfentries.cxx b/sdext/source/pdfimport/pdfparse/pdfentries.cxx
index 3ec950e22b755..67ad3af2cb961 100644
--- a/sdext/source/pdfimport/pdfparse/pdfentries.cxx
+++ b/sdext/source/pdfimport/pdfparse/pdfentries.cxx
@@ -1339,7 +1339,15 @@ PDFFileImplData* PDFFile::impl_getData() const
{
PDFNumber* pNum = dynamic_cast<PDFNumber*>(len->second);
if( pNum )
- m_pData->m_nKeyLength = static_cast<sal_uInt32>(pNum->m_fValue) / 8;
+ {
+ sal_uInt32 nKeyLength = static_cast<sal_uInt32>(pNum->m_fValue) / 8;
+ if (nKeyLength > ENCRYPTION_KEY_LEN)
+ {
+ SAL_WARN("sdext.pdfimport.pdfparse", "entry has length " << nKeyLength << " which is greater than " << ENCRYPTION_KEY_LEN);
+ nKeyLength = ENCRYPTION_KEY_LEN;
+ }
+ m_pData->m_nKeyLength = nKeyLength;
+ }
}
PDFName* pFilter = dynamic_cast<PDFName*>(filter->second);
if( pFilter && pFilter->getFilteredName() == "Standard" )https://github.com/LibreOffice/core/commit/21124741e0134f0f1222a42726c223bf6da41dca
Recorded dates, in order.
- 2026-04-02 Discovered or logged
- 2026-07-04 Sent to maintainer
- 2026-07-24 Patch released
- 2026-08-12 Maintainer acknowledged
- 2026-09-28 Publicly revealed
SHA-3-512 hash:
e112f4bb26e69f3eda06bb1f6a6d24f86423aa63f50706bdce8d3273f832b6b2b7c2a3a5b46de26ced221d6ed7aad60d28cab303c316f82dfd88e23b9410f33a
Committed 2026-07-22 07:32 UTC
Revealed 2026-09-28 20:51 UTC
Verify (download preimage.json)
Show preimage JSON
{
"ant_id": "ANT-2026-J00S1S9Y",
"bug_class": "Heap Out-of-Bounds Write",
"claude_severity": "high",
"commit_sha": null,
"created_at": "2026-04-16T01:54:44+00:00",
"description": "In LibreOffice's PDF import component, PDFFile::setupDecryptionData stores m_nKeyLength = /Length / 8 from the attacker-authored /Encrypt dictionary without clamping to the spec maximum of 16 bytes. PDFFile::decrypt then writes five object-number/generation bytes starting at m_aDecryptionKey[m_nKeyLength], but m_aDecryptionKey is a fixed 21-byte array and the last member of the new-allocated PDFFileImplData struct. The gating check usesSupportedEncryptionFormat() only validates /V and /R, and password_to_key() clamps its return value but never m_nKeyLength, so an attacker who sets /Length to e.g. 65536 and precomputes /U for a chosen password gets a 5-byte write at an attacker-controlled offset (8 KB) past the struct into adjacent heap memory. The path fires when a victim opens an encrypted hybrid PDF and enters any password at the prompt, yielding heap corruption and potential code execution via heap grooming.",
"discovered_at": "2026-04-02T00:00:00+00:00",
"location": "sdext/source/pdfimport/pdfparse/pdfentries.cxx:1058",
"poc_sha256": null,
"preimage_version": 1,
"project": "LibreOffice/core",
"reproduction": [
"1. Craft a hybrid PDF with /Encrypt dict setting /V 2, /R 3, and /Length to a large value (e.g. 65536), and precompute /U so a chosen/empty password authenticates",
"2. Include an AdditionalStreams reference whose object number/generation yield the desired 5 payload bytes, and groom the heap via other PDF objects",
"3. Deliver the PDF to the victim (e.g. phishing) and have them open it in LibreOffice",
"4. Victim enters the password at the prompt; setupDecryptionData records m_nKeyLength = /Length / 8",
"5. getAdditionalStream → writeStream → getDeflatedStream → decrypt writes 5 bytes at m_aDecryptionKey[m_nKeyLength..+4], corrupting the adjacent heap allocation"
],
"technical_details": "setupDecryptionData (pdfentries.cxx:1336) derives m_nKeyLength directly from the attacker-controlled /Encrypt /Length field with no upper bound, and decrypt (pdfentries.cxx:1058-1063) uses that value as the starting index for five writes into the fixed-size sal_uInt8[ENCRYPTION_KEY_LEN+5] = 21-byte m_aDecryptionKey array. Because m_aDecryptionKey is the final member of the heap-allocated PDFFileImplData (new'd at :1269), any m_nKeyLength > 16 writes beyond the allocation. No guard on the path — usesSupportedEncryptionFormat() (:1197-1203) checks only /V and /R, and password_to_key() (:1129-1131) clamps its return to 16 but does not write back to m_nKeyLength.",
"title": "PDF encryption key-length field drives out-of-bounds key buffer write",
"vendor_severity": "high"
}