Safeguard
Security

CVE-2023-29469: A libxml2 Hashing Flaw That Crashes XML Parsers

CVE-2023-29469 lets a crafted XML document trigger a double free in libxml2 through non-deterministic hashing of empty strings. Affected versions and fixes explained.

Karan Patel
Platform Engineer
5 min read

CVE-2023-29469 is a denial-of-service vulnerability in libxml2 before version 2.10.4, where the xmlDictComputeFastKey function in dict.c produces non-deterministic hash values when hashing empty strings from a crafted XML document, corrupting the dictionary's internal state and leading to memory errors such as a double free. The practical result is that an attacker who can get your application to parse a malicious document can crash it. NVD rates it CVSS 5.5 (medium).

libxml2 is one of the most widely embedded C libraries in existence. It sits under Python's lxml, PHP's XML extensions, and countless system utilities, which is why a mid-severity crash bug here ends up in advisories for AIX, Power HMC, and dozens of Linux distributions.

The root cause: hashing an empty string

libxml2 interns strings in a dictionary to avoid storing duplicate node and attribute names repeatedly. xmlDictComputeFastKey computes a hash for each string so the dictionary can bucket it. The flaw is in how the function handled empty strings.

When hashing, the function read the first byte of the string. For a non-empty string that byte is a real character. For an empty string, the "first byte" is whatever happened to be sitting in that memory location; instead of being treated as the null terminator, an arbitrary value could be read. That makes the computed hash non-deterministic: the same empty string can hash to different values depending on uninitialized memory.

A hash table depends on a value always hashing to the same bucket. When it does not, the dictionary's internal bookkeeping desynchronizes. Entries get misplaced, lookups fail in ways the code never expected, and cleanup logic can end up freeing the same allocation twice. That double free is the memory-corruption condition, and in practice it manifests as a crash.

Impact and severity

The realistic worst case documented in the advisories is denial of service: persuade a victim to open specially crafted XML and the parsing process crashes. NVD assigns CVSS 5.5, an availability impact reachable when a user or service processes attacker-supplied content.

Affected:   libxml2 < 2.10.4
Fixed in:   libxml2 2.10.4
Root cause: xmlDictComputeFastKey in dict.c reads an
            indeterminate first byte for empty strings
Impact:     non-deterministic hashing -> corrupted dict
            state -> double free -> DoS (crash)
CVSS 3.1:   5.5 (medium)

Whether a double free escalates beyond a crash depends heavily on the allocator and platform. The conservative, and the officially documented, classification is denial of service. Do not assume code execution, but do not dismiss a double free as harmless either; treat it as a crash you must patch.

Where it hides in your stack

The reason this CVE shows up in so many scans is transitive embedding. You rarely depend on libxml2 directly. It arrives underneath:

  • Python's lxml (which often bundles libxml2)
  • PHP's DOM, SimpleXML, and XMLReader extensions
  • libxslt and anything built on it
  • system tools like xmllint
  • OS packages that ship libxml2 as a shared library

Because the vulnerable code is a shared C library, one outdated copy in a base image can taint every service built from it. To find your exposure, query the linked version directly. In a container:

xmllint --version 2>&1 | head -1
# or from Python, the linked build under lxml:
python -c "import lxml.etree as e; print(e.LIBXML_VERSION)"

Any build below 2.10.4 (represented as 20904 in the integer form lxml prints) is in scope. An SCA tool that reads your SBOM will flag the embedded libxml2 across services so you are not manually inspecting each image.

Remediation

Upgrade libxml2 to 2.10.4 or later. The fix handles empty strings correctly in xmlDictComputeFastKey, producing deterministic hashes regardless of what is in the underlying buffer.

How you apply the upgrade depends on how libxml2 reaches you:

  • System package: update via your distribution (apt, dnf, apk) to the patched libxml2, then rebuild dependent images.
  • Bundled in lxml: upgrade lxml to a release that ships libxml2 2.10.4 or newer, then verify LIBXML_VERSION in the running process.
  • Statically linked in a vendor product: apply the vendor's patched release; IBM, F5, and others published fixed builds.

There is no meaningful application-level workaround because the flaw is in the core string dictionary that every parse touches. Patch the library. After deploying, confirm the running version rather than trusting the lockfile, since cached layers and bundled copies routinely keep a stale libxml2 alive.

FAQ

Is CVE-2023-29469 exploitable for code execution?

The documented impact is denial of service via a double free that crashes the process. A double free is a memory-corruption primitive, but the advisories classify this as DoS. Patch it regardless.

What version of libxml2 fixes it?

libxml2 2.10.4. Any earlier version is affected.

I do not depend on libxml2. Why is my scanner flagging it?

libxml2 is almost always transitive. It ships inside Python's lxml, PHP's XML extensions, libxslt, and many OS packages. Check the linked version with xmllint --version or lxml.etree.LIBXML_VERSION.

Do I need to change my application code?

No. The bug is inside libxml2's internal string dictionary, which every parse uses, so there is no safe application-level workaround. Upgrade the library and rebuild.

Never miss an update

Weekly insights on software supply chain security, delivered to your inbox.