Skip to main content

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

Expat 2.8.0 released, includes security fixes

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.0 was released two days ago. The key motivation for cutting a release and doing so now was:

  • Addressing security issue CVE-2026-41080 — insufficient entropy (CWE-331) —, and also
  • Getting support for entropy extractor getentropy(3) as well as bugfixes in the hands of users.

So, a summary "entropy and bugfixes" would be on point for the theme of this release.

What is entropy, and what does Expat need it for?

Entropy (in computing) is the amount of information that an attacker does not know. If your banking card pin has four decimal digits, from 0000 to 9999, that's 10,000 possible combinations; that's roughly 14 bits or less than two bytes of entropy — import math; math.ceil(math.log2(10_000) / 8) in Python — that the attacker is missing.

Expat needs high-quality entropy for a salt with its internal hash tables. Without an unknown-to-the-attacker hash salt, a hash table can be attacked using hash flooding, allowing denial of service attacks through crafted XML documents.

Now Expat 2.8.0 uses more entropy than past releases — 16 bytes rather than previously 4 to 8 bytes (depending on architecture) —, starts supporting entropy provider getentropy(3) in the many systems that offer it (including WASI, that lacks all other previously supported providers like getrandom or arc4random), and also offers a new API function XML_SetHashSalt16Bytes that overcomes the limitations of its predecessor XML_SetHashSalt.

For implementing the new cross-platform getentropy(3) support, I teamed up with Jérôme Duval. The bug fixes were contributed by Matthew Fernandez: the maintainer of Graphviz. 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.0. Thank you!

Sebastian Pipping