Operating system, libraries and runtimes

libexpat: CVE-2026-115506 billion laughs detection delay

libexpat billion laughs check

libexpat is the XML parser inside Python, Apache httpd, many PHP and Perl builds, Subversion, Mercurial, D-Bus, fontconfig, Firefox and a very long list of other programs. Its defense against "billion laughs" entity amplification notices the amplification too late (CVE-2026-115506), so a modest crafted XML document can still burn a lot of CPU and memory before the protection steps in. Any service that parses XML from an untrusted source can be knocked over. Upstream attached the fix to the 2.9.1 milestone.

CVE CVE-2026-115506 (published 22 October 2026; upstream issue #1160)
Severity High: 7.5 (CVSS 3), listed as 8.7 on the CVSS 4.0 scale. Authentication is not needed wherever XML from an untrusted source is parsed
Affected libexpat through 2.9.0, which is every release so far
Fix Merged upstream on 23 October 2026 (pull request #1433) and milestoned for Expat 2.9.1
Exploited No exploitation has been reported

Install Expat 2.9.1 or later when it reaches you. Upstream attached the fix to the 2.9.1 milestone: the script reports every copy at 2.9.0 or older as affected and 2.9.1 or newer as fixed. Distribution packages usually backport fixes without changing the version string. The script reads the owning package's changelog for a mention of the CVE.

Quick start

  1. Download the script. The repository is mirrored on GitHub and Codeberg; pick a base URL.

    # BASE from GitHub:   https://raw.githubusercontent.com/PrivacySafe/security-patches/refs/heads/main
    # BASE from Codeberg: https://codeberg.org/PrivacySafe/security-patches/raw/branch/main
    # curl -fLO "$BASE/expat/expat-check.sh"
    curl -fLO https://raw.githubusercontent.com/PrivacySafe/security-patches/refs/heads/main/expat/expat-check.sh
    
  2. Read it before running it, then make it executable.

    less expat-check.sh
    chmod +x expat-check.sh
    
  3. Check this host: system libraries, Python interpreters and running processes.

    ./expat-check.sh check
    
  4. Also look for programs that carry their own copy (vendored or statically linked). This reads files and can take a while on a large tree.

    sudo ./expat-check.sh deep /opt /srv /var/www /usr/local
    

Commands

Command What it does
check (default) Finds libexpat*.so* files under /lib /lib64 /usr/lib /usr/lib64 /usr/local/lib /usr/local/lib64 /opt and reads the version string compiled into each; asks each Python interpreter it can find for pyexpat.EXPAT_VERSION; lists running processes that have libexpat mapped and flags those still running a library that was replaced on disk
deep [DIR ...] Everything in check, then scans executables and shared objects in the given directories (default /usr/local /opt /srv /var/www /home) for an embedded expat_X.Y.Z string
version Prints the script version

Environment: EXPAT_LIB_DIRS replaces the library search directories and EXPAT_PY_DIRS replaces the directories searched for interpreters.

Exit codes: 0 every copy found is fixed (or none found), 1 an affected copy was found, 2 bad arguments.

How to read the result

  • Every host will show affected until a fixed package ships. That is accurate, not noise. Use the script to track the position and to find the copies that will need attention.
  • Package-managed copies: if the changelog does not mention the CVE yet, the script says so. If it does, the script reports a likely backported fix.
  • Interpreters carry their own pyexpat. A Python built with a bundled expat (a python.org build, pyenv, or a container image) shows a different version from the system library, which is why the script asks the interpreter directly.
  • Running processes keep the old library in memory until they restart. After you upgrade, restart the services the script lists. A process shown as "running a replaced libexpat" is already on borrowed time.
  • deep finds vendored copies , which a package update will not touch. Those need an update from whoever ships the program.
  • Run as root to see every process's mappings.

Reducing exposure until the fix ships

These are PrivacySafe's recommendations. They limit damage from a hostile document, they do not remove the flaw.

  • Cap the size of XML request bodies at the reverse proxy.
  • Run services that parse untrusted XML under CPU and memory limits (systemd CPUQuota and MemoryMax) so a single request cannot starve the host.
  • libexpat exposes calls that tune its amplification protection, but the upstream notes do not say that tightening them avoids this particular flaw, so do not rely on that.

Sources

Before you run it

  • The fix. Install Expat 2.9.1 or later when it reaches you (upstream attached the fix for CVE-2026-115506 to that milestone). The script reports every copy at 2.9.0 or older as affected and 2.9.1 or newer as fixed. Run it again after you upgrade.
  • Distribution packages usually backport without changing the version. The script checks for that before calling a packaged copy affected.
  • It looks in several places. It finds libexpat shared libraries, reads the version compiled into each, asks installed Python interpreters for pyexpat.EXPAT_VERSION, lists running processes that have libexpat mapped (flagging those still running a replaced copy), and can scan directories for programs that carry their own copy.
  • There is no hunt. The flaw is a denial of service, and no indicators have been published.
  • It also checks running docker and podman containers, unless you name a directory or set SCAN_CONTAINERS=0. A read-only probe inside each running docker and podman container captures the libexpat shared libraries and, by running each Python interpreter's import pyexpat, its expat version into a private temporary directory. The script then runs once per container, in isolation, against that copy, so one container's configuration never changes another's result. Running processes are examined on the host only. The copies are removed on exit. A path shown as .../docker_NAME/... is inside the container NAME. It is skipped when you name a binary or directory yourself.

The Security Patches software and this documentation are dedicated to the public domain under CC0 1.0 Universal. Other pages on privacysafe.app/docs are CC BY-SA 4.0 unless the page says otherwise.

Edit this page on Codeberg

The PrivacySafe Ecosystem

Shop at Ivy Cyber

Ivy Cyber offers PrivacySafe products and services in the USA and has partnerships with 3NSoft in Canada and Kuip Limited in the European Union. Enterprise services and consulting are available at ivycyber.com

PrivacySafe Foundation is a 501(c)(3) nonprofit public charity that supports the development of the PrivacySafe applications, along with the components they rely upon: 3NWeb protocols, filesystem, documentation, and reference implementations.

PrivacySafe Foundation

Donations to PrivacySafe Foundation support education, research, documentation, and public code. Purchases from Ivy Cyber support PrivacySafe products, services, hosting, and technical support. Together, we're building a better web.