On 2026-10-16 Debian published DSA-6566-1 for expat, the XML parsing C library. The advisory fixes six CVEs. According to Debian, processing a malformed XML file can cause denial of service or possibly arbitrary code execution. The fixed version for Debian 13 (trixie) is 2.9.0-1~deb13u1.
Most copies of this advisory online just repost the DSA text. Upgrading libexpat1 is only the first step. Expat is a shared library, so every long-running process that loaded the old libexpat.so.1 keeps the vulnerable code in memory until you restart it. Some software also ships its own copy of expat, which apt never updates. This libexpat checklist covers the upgrade, the restart, and the copies outside dpkg.
What DSA-6566-1 fixes
The DSA ships upstream Expat 2.9.0 to trixie. Upstream released the fixes over three versions: 2.8.4 on 2026-08-31, 2.8.5 on 2026-09-22 and 2.9.0 on 2026-10-05. The CVSS scores below are the ones listed on NVD, with the scoring source for each. NVD shows different scores from different sources, so the table names the source.
| CVE | Issue | Fixed upstream | CVSS (source) |
|---|---|---|---|
| CVE-2026-66046 | Quadratic runtime in storeAtts() when a document has many attributes with non-normalized values: CPU exhaustion | 2.8.4 | 3.1: 7.5, 4.0: 8.7 (VulnCheck) |
| CVE-2026-76956 | Wrong handling of the getentropy() return code gives weak hash salts, which allows hash flooding. Affects 2.8.2 and 2.8.3 only | 2.8.4 | 3.1: 7.5 (NIST), 5.9 (MITRE) |
| CVE-2026-76957 | No handler call depth tracking with custom encoding callbacks, which can cause a use-after-free | 2.8.4 | 3.1: 7.8 (NIST), 4.9 (MITRE) |
| CVE-2026-93990 | A UTF-16 high surrogate is not checked for a following low surrogate | 2.8.5 | 3.1: 7.5, 4.0: 8.7 (VulnCheck). The upstream changelog lists 9.8 |
| CVE-2026-77214 | XML_ParseBuffer does not check the caller-supplied length against the buffer size, which causes a heap over-read | 2.9.0 | 3.1: 8.2, 4.0: 8.3 (VulnCheck) |
| CVE-2026-102633 | Integer overflow in expat_realloc() on 32-bit platforms, which can lead to a heap buffer overflow | 2.9.0 | 3.1: 5.9, 4.0: 8.2 (VulnCheck) |
Who is affected: Debian package status
Package status from the Debian security tracker on the day of writing:
| Release | Vulnerable version | Fixed version | Notes |
|---|---|---|---|
| Debian 13 trixie | 2.8.3-1~deb13u1 | 2.9.0-1~deb13u1 (DSA-6566-1) | Fixes all six CVEs |
| Debian 12 bookworm | 2.5.0-1+deb12u2 | 2.5.0-1+deb12u4 (partial, DLA-4807-1 and others) | CVE-2026-77214 and CVE-2026-102633 are still open. CVE-2026-76956 does not apply |
| forky and sid | n/a | 2.9.0-1 | Fixed |
On trixie the source package expat builds libexpat1 (the runtime library), libexpat1-dev and expat (the xmlwf tool). Nearly every server has libexpat1 installed as a dependency. On trixie, dbus-daemon, git and libaprutil1t64 all depend on it. Apache httpd uses libaprutil1t64 for its XML handling, including WebDAV request bodies.
How to check your systems
Installed package version
dpkg -l 'libexpat1*' expat
apt policy libexpat1
A patched host shows:
libexpat1:
Installed: 2.9.0-1~deb13u1
Candidate: 2.9.0-1~deb13u1
To check many hosts from a script, compare the version with dpkg's own version logic instead of string matching:
v=$(dpkg-query -W -f='${Version}' libexpat1)
if dpkg --compare-versions "$v" ge '2.9.0-1~deb13u1'; then
echo "libexpat1 $v: fixed"
else
echo "libexpat1 $v: VULNERABLE"
fi
Which processes use expat right now
Before you upgrade, see which processes have the library mapped. This list shows which services are exposed on this host, which is more accurate than any generic list:
for maps in /proc/[0-9]*/maps; do
if grep -q 'libexpat\.so\.1' "$maps" 2>/dev/null; then
pid=${maps#/proc/}
pid=${pid%/maps}
printf '%s\t%s\t%s\n' "$pid" "$(cat /proc/"$pid"/comm 2>/dev/null)" "$(ps -o unit= -p "$pid")"
fi
done
Typical results are apache2, dbus-daemon, Python services that parse XML, svnserve, git HTTP helpers and monitoring agents. If your SAML or SOAP endpoint is missing from the list, that endpoint probably uses a different parser, such as libxml2. In that case it is not affected by this DSA. The command does not prove that the endpoint is safe from other XML bugs.
For Python you can ask the interpreter directly which Expat version it loaded:
python3 -c 'import xml.parsers.expat as e; print(e.EXPAT_VERSION)'
After the upgrade this should print expat_2.9.0. Run it with the interpreter of each virtualenv as well. If the output differs from the system library version, that interpreter is not using Debian's copy.
Fix: upgrade, then restart what still maps the old library
apt update
apt install --only-upgrade libexpat1 libexpat1-dev expat
apt skips any of these packages that are not installed (with a short notice). apt full-upgrade works too and also picks up any other pending DSAs.
Find processes still running the old copy
When dpkg replaces the file, running processes keep the old, deleted inode mapped. The kernel marks those mappings with (deleted):
grep -l 'libexpat\.so\.1.* (deleted)' /proc/[0-9]*/maps 2>/dev/null
Every PID in that output still runs vulnerable code, even though dpkg -l shows the fixed version. needrestart does the same check per service. In list mode it only reports and restarts nothing:
apt install needrestart
needrestart -r l -l
If unattended-upgrades applied the DSA overnight, the package is already fixed but the daemons are not. Under unattended-upgrades, interactive needrestart falls back to list-only mode, so check this by hand or let your monitoring do it. needrestart -p gives Nagios-compatible output.
Restart the affected services
Warning: restarting services drops active connections and requests. Run these commands in a maintenance window and test configs before restarting (for example apachectl configtest). Restarting dbus-daemon on a running system can break logind sessions and other services. If the system bus shows up in the list, reboot the host instead.
apachectl configtest
systemctl restart apache2
systemctl restart svnserve.service
needrestart -r i
Use the unit names your host actually has. svnserve.service is only an example. Short-lived tools like git or xmlwf pick up the new library on their next run. When the (deleted) grep returns nothing, you are done. The OpenSSL DSA-6531-1 restart walkthrough uses the same pattern and covers more about restarting services that hold a shared library.
Copies apt does not patch
The tracker only lists the expat source package for these CVEs. Anything outside dpkg is your job to find:
- Vendored or bundled shared objects in
/opt, virtualenvs, AppImages and vendor agents. - Static builds that compiled expat into the binary (Go/Rust tooling with C bindings, self-built software in
/usr/local). - Container images, which carry their own
libexpat1until you rebuild them.
Search for library files outside the multiarch directory that Debian manages:
find / -xdev \( -name 'libexpat.so*' -o -name 'libexpat*.a' \) -not -path '/usr/lib/*-linux-gnu/*' 2>/dev/null
Static copies keep the version string that XML_ExpatVersion() returns, in the form expat_2.x.y. Search for that string in binaries:
grep -rlaE 'expat_2\.[0-9]+\.[0-9]+' /opt /usr/local /srv 2>/dev/null
grep -aoE 'expat_2\.[0-9]+\.[0-9]+' /usr/local/bin/example-tool | sort -u
The second line checks one hit. Replace the path with a file from the first command. A match does not always prove a static build, but any version below 2.9.0 is worth asking the vendor about.
For Debian-based container images, query dpkg inside each image. This command starts a throwaway container with dpkg-query as the entrypoint. It does not start the application:
for img in $(docker image ls --format '{{.Repository}}:{{.Tag}}' | grep -v '<none>'); do
printf '%s\t' "$img"
docker run --rm --entrypoint dpkg-query "$img" -W -f='${Version}\n' libexpat1 2>/dev/null || echo 'no dpkg or no libexpat1'
done
Rebuild affected images with a fresh apt-get upgrade layer or a new base image, then redeploy. The approach is the same one used for liblzma in DSA-6549-1.
Which CVEs matter for servers parsing untrusted XML
If you expose WebDAV, SOAP, SAML, XML-RPC or upload handlers that accept XML, rank the six CVEs like this:
- CVE-2026-66046 is the most practical risk. A single well-formed document of a few megabytes causes high CPU use. The attacker needs no authentication, no external entities and no special parser options. Any endpoint that parses XML before authentication is a target.
- CVE-2026-76956 also enables a remote DoS through hash flooding. It only affects 2.8.2 and 2.8.3, and trixie shipped 2.8.3, so stock trixie is affected.
- CVE-2026-93990 affects documents encoded in UTF-16. It lets crafted input change the decoded characters. That matters for anything that makes security decisions from parsed text.
- CVE-2026-77214 depends on how the application calls
XML_ParseBuffer. The flaw is a length passed by the caller that expat does not check. The over-read can disclose adjacent heap memory. - CVE-2026-76957 only affects applications that register custom encoding callbacks.
- CVE-2026-102633 only affects 32-bit platforms (i386, armhf, armel).
Until every service is restarted, limit the impact of the DoS bugs. Set request body limits on XML endpoints (for example LimitXMLRequestBody in Apache), set CPU quotas on the services, and keep anonymous XML endpoints behind authentication where possible. systemd sandboxing also limits what a memory-corruption bug in a parser can reach.
What to watch next
- Track the expat page on the Debian security tracker. CVE-2025-66382 is still marked open and postponed for trixie, so expect a follow-up update.
- Bookworm LTS users still have open CVEs, including CVE-2026-77214 and CVE-2026-102633. Watch for a new DLA.
- Expat has had a steady stream of CVEs this year (2.8.4, 2.8.5 and 2.9.0 all contained security fixes). Automate the
(deleted)mapping check so restarts are not forgotten after the next DSA.
Takeaways
- Upgrade
libexpat1to2.9.0-1~deb13u1and verify withapt policy libexpat1. - The dpkg version alone proves nothing about running daemons. Grep
/proc/*/mapsfor(deleted)mappings or useneedrestart -r l -l. - Restart Apache, Subversion, Python services and agents. Reboot if
dbus-daemonis affected. - Search
/opt,/usr/local, virtualenvs and container images for bundled or static expat copies. - CVE-2026-66046 is the most practical risk for any endpoint that parses unauthenticated XML. Patch those hosts first.
Sources
- DSA-6566-1 expat - security update (debian-security-announce)
- Debian security tracker: expat
- Debian security tracker: CVE-2026-66046
- Debian security tracker: CVE-2026-76956
- Debian security tracker: CVE-2026-76957
- Debian security tracker: CVE-2026-77214
- Debian security tracker: CVE-2026-93990
- Debian security tracker: CVE-2026-102633
- NVD API: CVE-2026-66046
- NVD API: CVE-2026-76956
- NVD API: CVE-2026-76957
- NVD API: CVE-2026-77214
- NVD API: CVE-2026-93990
- NVD API: CVE-2026-102633
- libexpat Changes (upstream changelog)
- Debian trixie package: libexpat1
- Debian trixie package: libaprutil1t64
- Debian trixie package: dbus-daemon
- Debian trixie package: git
- needrestart(1) man page, Debian trixie
- Python docs: xml.parsers.expat
Comments