Skip to main content

Expat 2.10.0 released, fixes four vulnerabilities

For readers new to Expat:

libexpat is a fast streaming XML parser. Alongside libxml2, Expat is one of the most widely used software libre XML parsers written in C, specifically C99. It is cross-platform and licensed under the MIT license.

Hi!

Expat 2.10.0 was released a few hours ago. The key motivation for cutting a release now was to get the security fixes into the hands of Expat's users.

The four fixed security issues are:

Two of these seem worthy of explanation.

Let's start with CVE-2026-115506. Back in November 2025, about a year ago, my friend Hanno Böck had a closer look at billion laughs attacks again. The Wikipedia version of the attack is this (slightly modified) XML document:

<!DOCTYPE lolz [
 <!ENTITY lol0 "lol">
 <!ENTITY lol1 "&lol0;&lol0;&lol0;&lol0;&lol0;&lol0;&lol0;">
 <!ENTITY lol2 "&lol1;&lol1;&lol1;&lol1;&lol1;&lol1;&lol1;">
 <!ENTITY lol3 "&lol2;&lol2;&lol2;&lol2;&lol2;&lol2;&lol2;">
 <!ENTITY lol4 "&lol3;&lol3;&lol3;&lol3;&lol3;&lol3;&lol3;">
 <!ENTITY lol5 "&lol4;&lol4;&lol4;&lol4;&lol4;&lol4;&lol4;">
 <!ENTITY lol6 "&lol5;&lol5;&lol5;&lol5;&lol5;&lol5;&lol5;">
 <!ENTITY lol7 "&lol6;&lol6;&lol6;&lol6;&lol6;&lol6;&lol6;">
 <!ENTITY lol8 "&lol7;&lol7;&lol7;&lol7;&lol7;&lol7;&lol7;">
 <!ENTITY lol9 "&lol8;&lol8;&lol8;&lol8;&lol8;&lol8;&lol8;">
]>
<lolz>&lol9;</lolz>

For this document, someone made at least six decisions:

  • How long should the content of the root entity lol0 be? Answer: 3.
  • How many references should each non-root entity contain? Answer: 7.
  • How many levels deep should entities be nested? Answer: 9.
  • How many references should point into the tree? Answer: 1.
  • Should the attack use general entities (&name;) or parameter entities (%name;)? Answer: general entities.
  • Should the declaration order be forward or backward? Answer: forward.

Hanno's question was: Is there a chance that some other combination of parameters could still take a lot of time to parse, could evade the detection mechanism? And so he created a simple XML fuzzer "lololol" to approach that question with code, and did indeed find combinations that took Expat too long to parse. The symptom essentially was that Expat already did detect the attack but that it exercised too much of the document before coming to the conclusion: this is malicious content.

Then in January 2026 Shani Yosef of echo.ai reached out offering help with a fix, signed an informal NDA with me, and got to work. Since then, one bottleneck was waiting for my next round of review: until August, Expat was unpaid spare time for me with little time, and starting August I now had more and paid time for Expat but also had multiple other high-priority Expat things on my plate at once. It was four of us teaming up in a private GitHub repository — Shani of Echo in the driver's seat and bringing the key idea for a fix, Matthew Fernandez, Berkay Eren Ürün of Siemens, and me in the review seats —, and then @brspoon also reported a remaining issue at the last minute. It was not an easy task but exciting! As a result, not only does Expat now detect and reject these remaining forms of the billion and parameter laughs attacks but also detect faster, in general. It does so by pre-computing how much an entity would expand, right before starting to actually go ahead and expand. A big "thank you!" to Shani, Matthew, Berkay, @brspoon, Echo and Siemens — thank you!

The other issue worth explaining — CVE-2026-113351 — was with the allocation tracker introduced with Expat 2.7.2, where every new allocation increases a per-parser total, and every freeing of memory decreases the total, to be able to detect maliciously excessive use of memory relative to document file size: bigger documents are allowed to allocate more memory. Now the issue was that XML_ParserReset, which resets a parser to a point where it is ready to parse another document while keeping some memory for reuse, would correctly account for the memory that the parser still kept but could then reject the next document in alleged violation of memory use amplification limits that in fact the previous document had built up. The fix now offsets that value while keeping the memory, so that the next document has the allocation tracker set back to zero effectively. Previously, an attacker could force follow-up parses to fail by feeding a large document to the parser and cause denial of service. Thanks to Pu Cheng for the report!

It is worth noting that back in August the count of known unfixed security issues in Expat was at 5, and now, after this release, it is at 13. So for now, in the same time, more new findings are coming in (20) than could be fixed (12).

As usual, there are multiple non-security, non-feature improvements included with this release. For more details about this release, please check out the change log.

It should be noted that it was the Open Source Sabbatical of the City of Munich that allowed me to focus on libexpat for this release through its funding — thank you! We're in the fourth month of hopefully six months total; there continues to be plenty to do. Wish me luck!

If your business relies on Expat beyond January 2027, please consider funding the maintenance of Expat to ensure its health and security for you and others. Thank you!

Thanks to everyone who contributed to this release of Expat!

If you maintain Expat packaging, a bundled copy of Expat, or a pinned version of Expat, please update to version 2.10.0. Thank you!

Sebastian Pipping

Expat 2.9.0 released, fixes two vulnerabilities

For readers new to Expat:

libexpat is a fast streaming XML parser. Alongside libxml2, Expat is one of the most widely used software libre XML parsers written in C, specifically C99. It is cross-platform and licensed under the MIT license.

Expat 2.9.0 was released a few hours ago. The key motivation for cutting a release now was to get the security fixes and also new features into the hands of Expat's users.

The two security issues are:

  • CVE-2026-77214 — a potential out-of-bounds read with function XML_ParseBuffer due to insufficient validation of the length parameter,
  • CVE-2026-102633 — an integer overflow in memory management code that affects 32-bit platforms.

If the link for CVE-2026-77214 still results in 404 NOT FOUND by the time of reading, that's because publishing is still in progress.

For CVE-2026-102633 I would like to thank Filippo Tedeschi and Matthew Fernandez. For CVE-2026-77214 I would like to thank Filippo Tedeschi once more, as well as Fabian Wahle (Hap Security).

One of the two new features is the so-called new "Properties API". You can think of properties as the control knobs of a parser. There are five properties at the moment in Expat 2.9.0…

  • Two for the parameters of the allocation tracker/limiter, which prevents maliciously excessive use of memory:
    • XML_PROP_ALLOC_TRACKER_ACTIVATION_THRESHOLD of type uint64_t
    • XML_PROP_ALLOC_TRACKER_MAXIMUM_AMPLIFICATION of type double
  • Another two for the parameters of the protection against billion laughs attacks:
    • XML_PROP_BILLION_LAUGHS_ACTIVATION_THRESHOLD of type uint64_t
    • XML_PROP_BILLION_LAUGHS_MAXIMUM_AMPLIFICATION of type double
  • One for the state of reparse deferral:
    • XML_PROP_REPARSE_DEFERRAL_ENABLED of type XML_Bool

…and there are getters and setters to adjust or retrieve their values:

The new API was introduced to:

  • allow Expat to add more properties — both security-related and non-security ones — in the future (without need to also add new related getter and setter functions for each of them as was the case in the past),
  • allow users to read (not just to set) the current values, and
  • allow users to distinuish the different sources of error beyond a mere "did not work".

The other feature being introduced with release 2.9.0 is the 64-bit location API. The old location API, that is made up of the five functions…

…has the core problem that it uses datatypes int, XML_Index and XML_Size. These data types are not big enough to work with XML content of more than 4 GiB in size: at some point they would wrap around. Macro XML_LARGE_SIZE that increased the size of these datatypes was introduced with Expat 2.0.0 in 2006 to address that problem, but the macro breaks ABI compatibility with anyone that is expecting the smaller datatypes, splitting the world in two, and is not commonly activated — the sitation was not great.

To improve on that situation, Expat 2.9.0 now deprecates macro XML_LARGE_SIZE and offers five new 64-bit versions in return, note the "64" at the end:

This approach now allows everyone that previously relied on the XML_LARGE_SIZE macro to return to mainstream Expat while having access to 64-bit location information that supports XML content beyond 4 GiB in size without wrapping around.

As usual, there are non-security non-feature improvements included with this release. For more details about this release, please check out the change log.

It should be noted that it was the Open Source Sabbatical of the City of Munich that allowed me to focus on libexpat for this release through its funding — thank you! We're in the third month of hopefully six months total; there continues to be plenty to do. Wish me luck!

If your business relies on Expat beyond January 2027, please consider funding the maintenance of Expat to ensure its health and security for you and others. Thank you!

Thanks to everyone who contributed to this release of Expat!

If you maintain Expat packaging, a bundled copy of Expat, or a pinned version of Expat, please update to version 2.9.0. Thank you!

Sebastian Pipping

Expat 2.8.5 released, fixes vulnerability CVE-2026-93990

For readers new to Expat:

libexpat is a fast streaming XML parser. Alongside libxml2, Expat is one of the most widely used software libre XML parsers written in C, specifically C99. It is cross-platform and licensed under the MIT license.

Expat 2.8.5 was released a few hours ago. The key motivation for cutting a release and doing so now was to get the fix for vulnerability CVE-2026-93990 into the hands of the users.

The vulnerability was reported and fixed by Kartik Kenchi — thank you! Expat prior to release 2.8.5 did not stop attackers from smuggling malformed UTF-16 into the application using Expat, where it could cause arbitrary damage, depending on how malformed UTF-16 was handled inside the application. The issue was that surrogate pairs were not validated for the second half being a "low surrogate" in the range of 0xDC00 to 0xDFFF.

As usual, there is also non-security work that went into this release: bugfixes and improvements to the xmlwf command line utility, improvements to the two build systems, the documentation, the infrastructure, and also internals. For more details about this release, please check out the change log.

It should be noted that it was the Open Source Sabbatical of the City of Munich that allowed me to focus on libexpat for this release through its funding — thank you! We're in the second month of hopefully six months total; there continues to be plenty to do. Wish me luck!

Thanks to everyone who contributed to this release of Expat!

If you maintain Expat packaging, a bundled copy of Expat, or a pinned version of Expat, please update to version 2.8.5. Thank you!

Sebastian Pipping

Expat 2.8.4 released, fixes 4 vulnerabilities

For readers new to Expat:

libexpat is a fast streaming XML parser. Alongside libxml2, Expat is one of the most widely used software libre XML parsers written in C, specifically C99. It is cross-platform and licensed under the MIT license.

Expat 2.8.4 was released a few hours ago. The key motivation for cutting a release and doing so now was getting the fixes to four vulnerabilities…

…into the hands of the community.

The vulnerabilities were reported by Darren Carreras, Fabian Wahle (Hap Security), Sorrashut Kaewtaworn, Zeyou Liu, and the fixing was done by Darren Carreras, Sorrashut Kaewtaworn, Zeyou Liu, and me — thank you!

Issue CVE-2026-66046 is worth illustrating, and the fixing pull request contains an attack payload generator in its description. The issue was that if an attacker dials up a document like…

<!DOCTYPE e [
  <!ATTLIST e a0 NMTOKEN "x">
  <!ATTLIST e a1 NMTOKEN "x">
  <!ATTLIST e a2 NMTOKEN "x">
]>
<e a0=" v " a1=" v " a2=" v " />

…from 3 attributes to thousands, they could leverage the now-past quadratic runtime nature of Expat's related machinery to cause denial of service even with moderatly sized payload. The fix was migrating from an O(n) loop to an amortized O(1) hash table lookup.

As usual, there is also non-security work that went into this release. For instance, on the infrastructure side of things, the CI now covers Fil-C, RISC-V, Clang-based MinGW, and (the big-endian architecture) s390x for the first time.

It should be noted that it was the Open Source Sabbatical of the City of Munich that allowed me to focus on libexpat for the community through its funding — thank you! This has been the first month, and there are hopefully five more to come; there continues to be plenty to do. Wish me luck!

Thanks to everyone who contributed to this release of Expat!

For more details about this release, please check out the change log.

If you maintain Expat packaging, a bundled copy of Expat, or a pinned version of Expat, please update to version 2.8.4. Thank you!

Sebastian Pipping

Expat 2.8.3 released, fixes vulnerability CVE-2026-72522

For readers new to Expat:

libexpat is a fast streaming XML parser. Alongside libxml2, Expat is one of the most widely used software libre XML parsers written in C, specifically C99. It is cross-platform and licensed under the MIT license.

Expat 2.8.3 was released yesterday. The key motivation for cutting a release and doing so now was getting…

  • the fix to vulnerability CVE-2026-72522 as well as
  • the fix to a regression in Expat 2.8.2

…out to users.

The vulnerability was reported by the Mozilla Security Team, and it relates to how Expat handles decoding of UTF-16. The vulnerability is technically an out-of-bounds read, and the symptom in practice is (easy and reliable) denial of service by means of an infinite loop. It should be noted that the CVSS vector by Mitre for CVE-2026-72522 in the National Vulnerability Database is (once again) misclassifying the attack vector as Local (L) when it should be Network (N): there are no requirements for local access with CVE-2026-72522.

The regression was that on 32bit platforms and on 64bit Windows, processing XML content of 2+ GiB size was rejected as "out of memory" by mistake. The issue was reported by Evgeny Kotkov a week ago in the context of Subversion's use of libexpat.

Thanks to everyone who contributed to this release of Expat!

For more details about this release, please check out the change log.

If you maintain Expat packaging, a bundled copy of Expat, or a pinned version of Expat, please update to version 2.8.3. Thank you!

Sebastian Pipping

libexpat now funded by the City of Munich for up to 6 months

For readers new to Expat:

libexpat is a fast streaming XML parser. Alongside libxml2, Expat is one of the most widely used software libre XML parsers written in C, specifically C99. It is cross-platform and licensed under the MIT license.

Starting 2026-08-01, the "security vacation" of the project has ended and(!) I will be be paid to work on maintaining libexpat for up to 6 months thanks to the City of Munich under the umbrella of their Open Source Sabbatical program.
What does that mean?

For much of the past 10 years, working on libexpat has been competing with my regular occupation as a software engineer, chores, social life and re-creation. For the first time, I am now being employed to work on maintaining libexpat as my "regular job" for a limited period of time. My top priorities will be:

Yesterday and today most of my time went into fixing a vulnerability uncovered by Mozilla.

Technically, I am being employed by digitial@M now for of up 6 months with a regular working contract, including cancellation by either party, remotely from home. There is plenty to do.

Unvalidated AI slop submissions will still not be apprecated, but for everything else: if you want to throw intelligence at finding further vulnerabilities in libexpat and send them my way, the coming months will be the best chance at getting things fixed in reasonable time. Queueing theory and laws of physics still apply.

Wish me luck!

PS: If anyone managed to combine Clang-based MinGW with AddressSanitizer and Wine without crashing at launch, please show me how and drop me an e-mail. Thank you!

Best, Sebastian

Expat 2.8.2 released, fixes 13 vulnerabilities

For readers new to Expat:

libexpat is a fast streaming XML parser. Alongside libxml2, Expat is one of the most widely used software libre XML parsers written in C, specifically C99. It is cross-platform and licensed under the MIT license.

Expat 2.8.2 was released today. The key motivation for cutting a release and doing so now was getting security and non-security bugfixes out to users. On the security side, 13 vulnerabilities have been fixed:

The missing control flow integrity checks were brought to light by Steve Stagg in CPython, by Yousef Shanableh, Asher Darden, Haris Hussain, Sajin S of Astra Security and fixed by Kartik Kenchi, Haris Hussain and me.

The out-of-bounds write was reported and fixed by Alessandro Gario of Trail of Bits, Anthropic and Matthew Fernandez.

The integer overflows were reported and fixed by Kartik Kenchi and me.

Thanks to everyone who contributed to this release of Expat!

It it worth reminding that:

For more details about this release, please check out the change log.

If you maintain Expat packaging, a bundled copy of Expat, or a pinned version of Expat, please update to version 2.8.2. Thank you!

Sebastian Pipping

Expat 2.8.1 released, CVE-2026-45186 and CVSS unreliability

For readers new to Expat:

libexpat is a fast streaming XML parser. Alongside libxml2, Expat is one of the most widely used software libre XML parsers written in C, specifically C99. It is cross-platform and licensed under the MIT license.

Expat 2.8.1 was released yesterday. The key motivation for cutting a release and doing so now was:

The vulnerability was reported to me responsibly about eight months ago by Nick Wellnhofer, the long-time and past maintainer of libxml2.

The attack relies on Expat <2.8.1 using an O(n²) runtime algorithm — a for loop — to check for collisions among attribute names. It takes nothing more than dialing up XML document…

<!DOCTYPE d [
  <!ATTLIST e a0 CDATA "" a1 CDATA "" a2 CDATA "">
]>
<d/>

…from 3 attributes to a number big enough for the specific target of the attack.

It should be noted that a layer of compression around XML can significantly reduce the minimum attack payload size.

There is an attack payload generator available for download: please use it responsibly!

Berkay Eren Ürün and I teamed up for a fix. It uses a hash table instead of a linear loop to detect collisions, which turns overall runtime from O(n*n) into O(n).

For some numbers (from older ThinkPad X220 hardware):

Count Runtime unfixed Runtime fixed Payload size
  (seconds) (seconds) (uncompressed, bytes)
10,000 0.17 0.03 135,615
100,000 13.22 0.24 1,395,615
200,000 59.71 0.49 2,795,615
400,000 253.18 1.04 5,708,119

And a quick graph:

It is worth noting that after I filed a CVE request with Mitre, someone turned my classification as remote (i.e. parsing from the wire) to mistaken local (i.e. local account access needed) and also to "Attack complexity: High" when it is a simple as shown above and with an attack payload generator being public. That results is an unrealistically low current CVSS score "2.9 of 10" on GitHub…

…and also in NVD. A more realastic score than 2.9 would be 5.3 to 7.5.

I have requested a fix from Mitre in the meantime, but that's not fixing the core issue. This could serve as both a concrete example and a reminder that:

  • CVSS scores are unreliable: they are often over- or (worse) underestimating risk.

  • CVSS scores (and CVE reports) are edited by individuals that may or may not know better than the reporting individuals and/or the maintainers upstream.

  • CVSS score is not a metric to base decisions about vulnerabilities on.

Thanks to everyone who contributed to this release of Expat!

For more details about this release, please check out the change log.

If you maintain Expat packaging, a bundled copy of Expat, or a pinned version of Expat, please update to version 2.8.1. Thank you!

Sebastian Pipping

Steven, please fix the 1% loss/gain graph!

Back in December 2023, I got curious about Steven Bartlett's then-new book The Diary of a CEO: The 33 laws of business and life and ordered a copy for myself. I dived right in. On page 184 this graph hit me:

It is meant to be about how 100 USD develop over time with either constant 1% loss or 1% gain per day.

It puzzles and amazes me to this day how this graph — with all the things wrong about it, even for a schematic graph — made its way into a published book: It feels unreal. In particular:

  • The loss curve is "bending the wrong way": It is presented as concave when it should be convex.

  • The placement of 0 (zero) on the Y-axis is wild.

  • The graph has a log scale but seems to want to still live in the linear world.

Here is what that graph could have been with matplotlib (source code Gist in Python), either with a linear scale or with a truly logarithmic scale:

For comparision, here is how James Clear, the author of the book Atomic Habits, turns this into a working schematic graph for an article of his:

Steven, if you read this, please fix the 1% loss/gain graph for the next edition of the book — thank you!

-- Sebastian Pipping