Engineer working on a laptop connected to a device test rig, surrounded by wiring harnesses and test hardware

Firmware vulnerabilities & fixes

zlib, Expat and libxml2 vulnerabilities in embedded firmware: finding, triaging and patching them

Last updated:

zlib, Expat and libxml2 vulnerabilities in embedded firmware are found by locating every copy of each library in the image, shared or statically linked, reading its version, and checking it against each project's own changelog: zlib 1.4 (4 November 2026), Expat 2.10.0 (2 November 2026) and libxml2 2.15.4 (1 September 2026) all list security fixes. Triage then asks whether the vulnerable function is reachable from untrusted input on that device, and the fix is an upgrade, a backported patch, or a parser setting that closes the risky path.

Key takeaways

  • Because they sit underneath other software rather than in front of it.
  • Start from the unpacked root file system and check three kinds of evidence for each library: the file name, the version string inside the binary, and the build metadata.
  • zlib bugs are mostly heap overflows and out-of-bounds reads in the stream code.
  • Each project publishes differently, and the difference decides how a device maker tracks them.

Why do zlib, Expat and libxml2 show up in so many firmware scans?

Because they sit underneath other software rather than in front of it. zlib does the compression for gzip, PNG images, HTTP content encoding, SSH and firmware update archives. Expat and libxml2 are the two common C XML parsers, used by UPnP and SOAP device-management stacks, configuration readers and scripting runtimes; libxslt is built on libxml2. A device can ship all three without any product feature that mentions compression or XML.

They also turn up more than once in a single image. Besides the shared libz.so, libexpat.so and libxml2.so, copies get compiled into individual binaries. CPython is one example: its source tree carries its own Expat in Modules/expat, and on 2026-11-12 that copy's expat.h declared version 2.10.0. A device that bundles a Python interpreter built against that copy has an Expat that the package manager does not list.

The bootloader and kernel are a separate case. U-Boot's include/u-boot/zlib.h still carries the header text "version 1.2.3, July 18th, 2005", and the Linux kernel's lib/zlib_inflate/inflate.c says it is "Based on zlib 1.2.3 but modified for the Linux Kernel". Both are maintained forks, not upstream zlib, so an upstream zlib version range does not map onto them; check them against the specific CVE's code path. Our U-Boot bootloader security and Linux kernel CVEs on embedded devices guides cover those two components.

How do you find which versions a firmware image ships?

Start from the unpacked root file system and check three kinds of evidence for each library: the file name, the version string inside the binary, and the build metadata.

Library Shared library name Version evidence inside the binary Static or bundled copies
zlib libz.so.1 " inflate 1.4 Copyright 1995-2026 Mark Adler " and the matching deflate string Same strings appear in any binary that compiled zlib in; U-Boot and kernel forks need separate review
Expat libexpat.so.1 XML_ExpatVersion() returns "expat_2.10.0" style strings CPython's pyexpat, other programs that vendor xmlparse.c
libxml2 libxml2.so.2 (before 2.14), libxml2.so.16 (2.14 and later) xmlParserVersion holds a number string, 2.15.4 is "21504" Symbol names such as xmlParseDocument in unstripped binaries

The zlib strings come from inftrees.c and deflate.c in the zlib source; a comment in inftrees.c asks product makers to "keep this copyright string in the executable of your product", which is why it survives static linking. A practical sequence:

  1. Search every ELF file, not only /lib and /usr/lib, for inflate 1., deflate 1. and expat_2.; each hit outside the shared library is a separate copy to track.
  2. Run readelf -d on each executable and list which ones name libz.so.1, libexpat.so.1, libxml2.so.2 or libxml2.so.16 as a dependency. That tells you which programs a shared-library upgrade fixes.
  3. Read the package database (opkg on OpenWrt-style images) or the Yocto or Buildroot manifest. Treat the version there as the upstream base: distributions and vendors backport fixes without changing it.
  4. Record the bootloader and kernel zlib forks as separate components rather than as "zlib 1.2.3".

Our firmware SBOM guide covers turning this into a component inventory, and the Yocto and Buildroot SBOM guide covers getting it from the build system instead of from the binary.

Which vulnerability classes matter for zlib, Expat and libxml2?

zlib: inflate and deflate memory corruption

zlib bugs are mostly heap overflows and out-of-bounds reads in the stream code. CVE-2022-37434 is the example most scanners report: NVD describes "a heap-based buffer over-read or buffer overflow in inflate in inflate.c via a large gzip header extra field" in zlib through 1.2.12, and adds that "only applications that call inflateGetHeader are affected". zlib's ChangeLog records the 1.2.13 fix as "Fix a bug when getting a gzip header extra field with inflate()", without the CVE number. CVE-2018-25032 is the compression-side counterpart: memory corruption "when deflating (i.e., when compressing) if the input has many distant matches", fixed in 1.2.12.

The newest release widens the picture. The zlib home page lists security fixes in zlib 1.4, including a buffer overflow in non-blocking gzwrite() (CVE-2026-85091) and an inflateGetDictionary() bug that "could write up to 8 MB past the caller's 32K buffer", which zlib says "Affects 1.2.8 through 1.3.2". That range covers nearly every zlib a current device ships. The same release fixes several flaws in contrib/minizip, which is a separate ZIP library in the zlib tree; only devices that build minizip need those.

Expat and libxml2: entity expansion

A small XML document that nests entity references can expand into gigabytes of output, the "billion laughs" attack. The Expat XML security page says Expat versions before 2.4.0 were affected (CVE-2013-0340) and that by default Expat now "stops processing inputs if the output is more than 100 times larger than the input and larger than 8 MiB". Variants keep appearing: the Expat Changes file lists CVE-2026-115506 in 2.10.0 to "Detect billion laughs attacks quicker and more of its variations", with the note that "a layer of compression around XML can significantly reduce the minimum attack payload size". On a device that inflates a compressed configuration or update manifest before parsing it, zlib and the XML parser form one attack path.

libxml2's NEWS file records that its entity expansion protection "has been greatly improved" in 2.11.0. The XML_PARSE_HUGE option relaxes libxml2's size limits, and CVE-2022-40303 shows what that costs: before 2.10.3, "When parsing a multi-gigabyte XML document with the XML_PARSE_HUGE parser option enabled, several integer counters can overflow". Firmware that sets this option for untrusted input should drop it.

On external entities (XXE), Expat's security page states that "By default, Expat does not access external URLs" and only does so if the application installs an external entity handler. Review any firmware code that calls XML_SetExternalEntityRefHandler on network input.

Integer overflows on 32-bit targets

Several Expat fixes apply only where unsigned int and size_t are the same width, which is the normal case on 32-bit ARM and MIPS. Expat 2.6.3 fixed CVE-2024-45491 and CVE-2024-45492, integer overflows in dtdCopy and nextScaffoldPart "on 32-bit platforms (where UINT_MAX equals SIZE_MAX)", with impact described as "denial of service to potentially artitrary code execution" [sic]. Expat 2.8.2 fixed a batch of further integer overflows (CVE-2026-56403 to CVE-2026-56408 in the library, three more in the xmlwf tool), and 2.9.0 fixed CVE-2026-102633, an "Integer overflow in function expat_realloc on 32bit platforms". In libxml2, CVE-2025-6021 is an integer overflow in xmlBuildQName that "can lead to a stack-based buffer overflow", listed in libxml2 2.14.4's NEWS as "Fix integer overflow in xmlBuildQName". Our memory corruption in embedded firmware guide covers the general pattern.

Malformed input passed through to the application

Some parser bugs damage the program above the parser rather than the parser itself. Expat 2.4.5 fixed CVE-2022-25235, where malformed UTF-8 passed to the application "can cause arbitrary damage (e.g. code execution)"; the Changes file says "Exploits with code execution are known to exist". Expat 2.8.5 fixed a related UTF-16 issue, CVE-2026-93990, with an upstream CVSS 3.1 score of 9.8.

Where does each project publish security fixes?

Each project publishes differently, and the difference decides how a device maker tracks them.

  • zlib. Release notes on zlib.net and the ChangeLog. Releases are infrequent (1.3.1 in January 2024, 1.3.2 in February 2026, 1.4 in November 2026), and ChangeLog lines often describe a fix without naming a CVE, so map them against NVD records.
  • Expat. Every security fix appears in the Changes file with its CVE and pull request, and releases are announced on libexpat.github.io; there were ten releases with security fixes between January and November 2026. The maintainer also lists reported but unfixed issues in issue #1160. On 2026-11-12 it had 26 open entries, most marked as awaiting validation, and the Changes file header states "Expat has UNFIXED SECURITY ISSUES!" Expect further Expat releases and plan update capacity for them.
  • libxml2. The NEWS file on GNOME GitLab has a Security heading per release; v2.15.4's six security entries name the fixed function, not a CVE. The project's README says it is "maintained by volunteers", that "It's NOT recommended to use this software to process untrusted data", and that security reports are handled "as a best effort". A device that parses network XML with libxml2 carries that position into its own risk assessment.

How should a device maker triage these CVEs?

A version match produces a long list; most of it is not reachable on a given device. Work through each match in this order:

  1. Confirm the copy and versionWhich binary holds it, which version string, and whether the vendor or distribution has backported the fix.
  2. Check reachabilityFor zlib: does any program call inflateGetHeader, inflateGetDictionary or the gz* file functions on untrusted data? For Expat and libxml2: does the device parse XML received over the network, from uploaded files or from update packages, or only fixed files from its own read-only partition?
  3. Check the targetA 32-bit-only overflow such as CVE-2024-45491 applies to a 32-bit ARM or MIPS build and not to a 64-bit one.
  4. Check exploitation dataOn 2026-11-10 the CISA KEV catalog (version 2026.11.10, 1,779 entries) listed no zlib, Expat or libxml2 library CVE; its one zlib-related entry, CVE-2025-14847, is a MongoDB Server bug in handling zlib-compressed protocol headers. FIRST EPSS scores on the same date were 0.517 for CVE-2018-25032, 0.414 for CVE-2022-40303, 0.192 for CVE-2022-37434 and 0.024 for CVE-2022-40674. EPSS is a probability of exploitation activity, not a severity, so use it to order work among reachable findings. Our KEV and EPSS prioritization guide explains both.
  5. Record the decisionFor each version-matched CVE that does not apply, write down why (function not called, option not set, 32-bit only, fix backported). A machine-readable format such as OpenVEX lets customers and scanners see the same decision.

How do you patch zlib, Expat and libxml2 in firmware?

Upgrading the shared library through Yocto or Buildroot fixes every program that links it dynamically. It does not fix statically linked copies: each program that compiled zlib or Expat in needs its own rebuild, which is why the inventory step matters. Bootloader and kernel zlib forks are patched through U-Boot and kernel updates, not through the zlib package.

Some upgrades change the interface. libxml2 2.14 moved the soname from libxml2.so.2 to libxml2.so.16, so programs linked against the old name must be rebuilt rather than given a new library file. libxml2 2.15 needs the XML_PARSE_UNZIP option to read compressed data and removed the built-in HTTP client. zlib 1.4 lists behaviour changes, for example deflateReset() now resets a gzip header set by deflateSetHeader().

Where an upgrade cannot ship quickly, configuration narrows the exposure. Keep Expat's billion laughs limits at their defaults or tighten them through XML_SetBillionLaughsAttackProtectionMaximumAmplification; do not install external entity handlers for untrusted input; drop XML_PARSE_HUGE from libxml2 calls that read network data; and do not build zlib's contrib/minizip or contrib/iostream2 unless the device uses them. To confirm what a vendor update actually changed, our firmware diffing and patch analysis guide covers comparing two images. Related library guides: libcurl, OpenSSL and glibc, uClibc and musl.

AI-assisted analysis: what SoftWSP actually runs

SoftWSP's automated scan unpacks a firmware image, catalogs the software components it can identify in the extracted file system, and records each one's name, version and identifiers. Where the image carries identifiable zlib, Expat or libxml2 version evidence, that component is matched against CVE records, CISA's KEV catalog and FIRST's EPSS scores. This matching is deterministic code: the code scores, and AI narrates.

AI writes the plain-language explanation on top of that matching: which zlib, Expat and libxml2 CVEs matched, which are KEV-listed or have a high EPSS score, and what version evidence they rest on, with every sentence citing the component, version and record behind it. An engineer reviews every finding before the report is delivered. The judgements this guide describes as manual stay with that engineer: whether a statically linked copy or a bootloader or kernel fork is affected, whether firmware calls inflateGetHeader or parses untrusted XML, whether the target is 32-bit, and whether a vendor backport already closes a version-matched CVE.

For a specific image, see device & firmware security services.

This guide draws on the zlib home page and ChangeLog, the Expat Changes file, issue #1160, home page and XML security page, the libxml2 NEWS file and README, the U-Boot, Linux kernel and CPython source trees, NVD records for CVE-2018-25032, CVE-2022-37434, CVE-2022-40303 and CVE-2025-6021, CISA's KEV catalog (version 2026.11.10) and FIRST EPSS data dated 2026-11-10, all read directly on 2026-11-12.

Put this guide to work

Frequently asked questions

How do I find the zlib, Expat and libxml2 versions inside a firmware image?

Unpack the root file system and search every ELF file, not only the shared libraries. zlib embeds the strings " inflate 1.x Copyright ... Mark Adler " and " deflate 1.x Copyright ... Jean-loup Gailly and Mark Adler ", so a strings search shows the version even in statically linked copies. Expat's XML_ExpatVersion() returns a string of the form "expat_2.10.0", which is also searchable. libxml2 stores its version as a number string (2.15.4 becomes "21504"), so pair a search for libxml2.so.2 or libxml2.so.16 with readelf -d on each binary and the package database or build manifest. Separately check U-Boot and the Linux kernel, which carry their own zlib forks.

Which zlib, Expat and libxml2 CVEs matter most on a device?

The ones whose code path the device actually reaches with attacker-controlled input. CVE-2022-37434, the zlib inflate heap overflow through a large gzip header extra field, only affects applications that call inflateGetHeader. CVE-2024-45491 and CVE-2024-45492 in Expat are integer overflows on 32-bit platforms, which describes many embedded ARM and MIPS targets. Entity-expansion (billion laughs) denial of service matters wherever a device parses XML from the network: Expat added protection in 2.4.0 against CVE-2013-0340, and libxml2's CVE-2022-40303 integer overflows need the XML_PARSE_HUGE option, which also lifts libxml2's safety limits.

Are any zlib, Expat or libxml2 vulnerabilities in CISA's KEV catalog?

No library CVE from the three projects appears in the CISA Known Exploited Vulnerabilities catalog version 2026.11.10 (1,779 entries). The only entry that mentions zlib, CVE-2025-14847, is a MongoDB Server flaw in how it handles zlib-compressed protocol headers, not a zlib library bug. Ranking therefore leans on reachability and FIRST EPSS: on 2026-11-10 EPSS gave CVE-2018-25032 (zlib deflate) 0.517 and CVE-2022-40303 (libxml2) 0.414, against 0.024 for Expat's CVE-2022-40674.

Where do zlib, Expat and libxml2 publish their security fixes?

zlib lists fixes on the zlib.net home page and in the ChangeLog in github.com/madler/zlib; ChangeLog lines often describe the fix without a CVE number. Expat lists each fix with its CVE in the Changes file in github.com/libexpat/libexpat, announces releases on libexpat.github.io and tracks unfixed reports in GitHub issue #1160. libxml2 lists fixes under a Security heading in the NEWS file on gitlab.gnome.org/GNOME/libxml2; recent entries name the fixed function rather than a CVE.

Does SoftWSP decide whether a zlib, Expat or libxml2 CVE is reachable in my firmware?

No, not automatically. SoftWSP's scan identifies software components and versions in the unpacked firmware and matches them against CVE records, CISA KEV and FIRST EPSS data; AI then writes a summary that cites the component, version and record behind each finding, and an engineer reviews it. Whether firmware calls inflateGetHeader, parses untrusted XML, runs on a 32-bit target or carries a vendor backport is decided in that engineer review.

Sources

  1. zlib home page (zlib 1.4 release notes, 4 November 2026)
  2. madler/zlib: ChangeLog
  3. libexpat: Expat Changes (release notes and security fixes)
  4. libexpat: issue #1160, Unfixed non-public security issues
  5. libexpat: Expat XML parser home page (release news)
  6. libexpat: XML Security (XXE and billion laughs)
  7. GNOME/libxml2: NEWS (v2.15.4, 1 September 2026)
  8. GNOME/libxml2: README (Security section)
  9. U-Boot: include/u-boot/zlib.h (bundled zlib 1.2.3 header)
  10. Linux kernel: lib/zlib_inflate/inflate.c
  11. CPython: Modules/expat (bundled Expat copy)
  12. NIST NVD: CVE-2022-37434 (zlib inflate gzip header)
  13. NIST NVD: CVE-2018-25032 (zlib deflate memory corruption)
  14. NIST NVD: CVE-2022-40303 (libxml2 XML_PARSE_HUGE integer overflows)
  15. NIST NVD: CVE-2025-6021 (libxml2 xmlBuildQName)
  16. CISA: Known Exploited Vulnerabilities (KEV) Catalog
  17. FIRST: Exploit Prediction Scoring System (EPSS)
Was this guide helpful?

In this cluster

zlib, Expat and libxml2 vulnerabilities in embedded firmware: finding, triaging and patching them