ANT-2026-CRFDC4JM · libreoffice/core
heap-buffer-overflow 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-CRFDC4JM: WMF Unicode-escape text DX-array heap overflow
In the WMF W_META_ESCAPE PRIVATE_ESCAPE_UNICODE path (wmfreader.cxx:1271-1311), nStringLen and nDXCount are read independently from the file with no check that nDXCount >= nStringLen. The KernArray (std::vector) is resized to nDXCount and passed to MtfTools::DrawText, which loops i=0..rText.getLength()-1 and does unchecked (*pDXArry)[i] reads and writes. An attacker sets nStringLen large (e.g. 8000) and nDXCount=1, causing ~64 KB (up to ~256 KB) of doubles to be written past an 8-byte heap allocation. WMF is parsed whenever opened standalone or embedded in ODT/DOCX/RTF/PPTX, so a malicious document triggers heap corruption and likely RCE on open.
Target
Project: libreoffice/core
Location: emfio/source/reader/mtftools.cxx:1717
Discovery: static analysis — not yet dynamically reproduced
Technical Details
Root cause is a missing invariant check: the reader never enforces nDXCount >= nStringLen (the writer side at wmfwr.cxx:516 shows the intended invariant). DrawText then uses unchecked std::vector::operator[] in a loop bounded by the string length rather than the array size, yielding both OOB read (mtftools.cxx:1715) and OOB write (mtftools.cxx:1720). _GLIBCXX_ASSERTIONS is off in default TDF builds, so operator[] performs no bounds check.
Reproduction
- Craft a WMF with W_META_CREATEFONTINDIRECT for a ubiquitous font (e.g. Arial/Liberation Sans) followed by SELECTOBJECT to pass the IsFontAvailable gate.
- Add a W_META_ESCAPE record carrying the LibreOffice PRIVATE_ESCAPE_UNICODE signature (magic 0x2c2a4f4f / 0x0a) and a correct CRC32 over the payload.
- In the escape payload, set nStringLen large (e.g. 8000) with a matching string, and nDXCount=1 with a single DX entry.
- Embed the WMF in an ODT/DOCX/RTF/PPTX (or deliver standalone) and send to the victim.
- On open, DrawText loops over 8000 chars and writes ~7999 doubles past the 1-element KernArray heap allocation.
[No reproducer or sanitizer output attached — request from security-cvd@anthropic.com if needed.]
Suggested Fix
Before per-character indexing, ensure the DX-advance array has at least rText.getLength() elements (reject the record or resize/pad the array) so pDXArry is never indexed beyond its size.
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-CRFDC4JM.
Reference: ANT-2026-CRFDC4JM
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/emfio/source/reader/wmfreader.cxx b/emfio/source/reader/wmfreader.cxx
index a645411267b53..390f7271acf38 100644
--- a/emfio/source/reader/wmfreader.cxx
+++ b/emfio/source/reader/wmfreader.cxx
@@ -1349,7 +1349,7 @@ namespace emfio
GetFont().GetFamilyName()))
{
Point aPt;
- sal_uInt32 nStringLen, nDXCount;
+ sal_uInt32 nStringLen(0), nDXCount(0);
KernArray aDXAry;
SvMemoryStream aMemoryStream(nEscLen);
aMemoryStream.WriteBytes(pData.get(), nEscLen);
@@ -1368,6 +1368,8 @@ namespace emfio
OUString aString = read_uInt16s_ToOUString(
aMemoryStream, nStringLen);
aMemoryStream.ReadUInt32(nDXCount);
+ if (nDXCount < o3tl::make_unsigned(aString.getLength()))
+ nDXCount = 0;
if ((static_cast<sal_uInt64>(nDXCount)
* sizeof(sal_Int32))
>= (nEscLen - aMemoryStream.Tell()))
@@ -1376,7 +1378,7 @@ namespace emfio
aDXAry.resize(nDXCount);
for (sal_uInt32 i = 0; i < nDXCount; i++)
{
- sal_Int32 val;
+ sal_Int32 val(0);
aMemoryStream.ReadInt32(val);
aDXAry[i] = val;
}https://github.com/LibreOffice/core/commit/4406da79b0
Recorded dates, in order.
- 2026-04-02 Discovered or logged
- 2026-07-03 Sent to maintainer
- 2026-07-06 Maintainer acknowledged
- 2026-07-24 Patch released
- 2026-09-28 Publicly revealed
SHA-3-512 hash:
0cc2bd420b5659200db58805519d367e0f76e3baa0e39bdb85cfbe5ae2687a623a7c42edd42b0b875e954d9a14f8dd9a8fa8d63e23c38f0c84e44f843f7e823a
Committed 2026-07-22 07:32 UTC
Revealed 2026-09-28 21:05 UTC
Verify (download preimage.json)
Show preimage JSON
{
"ant_id": "ANT-2026-CRFDC4JM",
"bug_class": "Heap buffer overflow",
"claude_severity": "high",
"commit_sha": null,
"created_at": "2026-04-16T01:54:40+00:00",
"description": "In the WMF W_META_ESCAPE PRIVATE_ESCAPE_UNICODE path (wmfreader.cxx:1271-1311), nStringLen and nDXCount are read independently from the file with no check that nDXCount >= nStringLen. The KernArray (std::vector<double>) is resized to nDXCount and passed to MtfTools::DrawText, which loops i=0..rText.getLength()-1 and does unchecked (*pDXArry)[i] reads and writes. An attacker sets nStringLen large (e.g. 8000) and nDXCount=1, causing ~64 KB (up to ~256 KB) of doubles to be written past an 8-byte heap allocation. WMF is parsed whenever opened standalone or embedded in ODT/DOCX/RTF/PPTX, so a malicious document triggers heap corruption and likely RCE on open.",
"discovered_at": "2026-04-02T00:00:00+00:00",
"location": "emfio/source/reader/mtftools.cxx:1717",
"poc_sha256": null,
"preimage_version": 1,
"project": "LibreOffice/core",
"reproduction": [
"1. Craft a WMF with W_META_CREATEFONTINDIRECT for a ubiquitous font (e.g. Arial/Liberation Sans) followed by SELECTOBJECT to pass the IsFontAvailable gate.",
"2. Add a W_META_ESCAPE record carrying the LibreOffice PRIVATE_ESCAPE_UNICODE signature (magic 0x2c2a4f4f / 0x0a) and a correct CRC32 over the payload.",
"3. In the escape payload, set nStringLen large (e.g. 8000) with a matching string, and nDXCount=1 with a single DX entry.",
"4. Embed the WMF in an ODT/DOCX/RTF/PPTX (or deliver standalone) and send to the victim.",
"5. On open, DrawText loops over 8000 chars and writes ~7999 doubles past the 1-element KernArray heap allocation."
],
"technical_details": "Root cause is a missing invariant check: the reader never enforces nDXCount >= nStringLen (the writer side at wmfwr.cxx:516 shows the intended invariant). DrawText then uses unchecked std::vector<double>::operator[] in a loop bounded by the string length rather than the array size, yielding both OOB read (mtftools.cxx:1715) and OOB write (mtftools.cxx:1720). _GLIBCXX_ASSERTIONS is off in default TDF builds, so operator[] performs no bounds check.",
"title": "WMF Unicode-escape text DX-array heap overflow",
"vendor_severity": "high"
}