CVE-2026-93990 Overview
CVE-2026-93990 is an input validation flaw in the Expat XML parser through version 2.8.4. The library fails to validate low surrogates that follow high surrogates in UTF-16 encoded input. Attackers can craft malformed UTF-16 XML documents containing lone high surrogates that consume the following code unit. This behavior hides markup characters from the parser and enables XML injection against applications that rely on Expat for parsing. The weakness maps to CWE-176: Improper Handling of Unicode Encoding.
Critical Impact
Malformed UTF-16 sequences can smuggle XML markup past Expat, allowing injection of attacker-controlled elements or attributes into parsed documents.
Affected Products
- Expat (libexpat) versions up to and including 2.8.4
- Applications and language bindings that embed Expat for UTF-16 XML parsing
- Downstream distributions that ship the vulnerable Expat library
Discovery Timeline
- 2026-09-19 - CVE-2026-93990 published to the National Vulnerability Database
- 2026-09-22 - Last updated in NVD database
Technical Details for CVE-2026-93990
Vulnerability Analysis
Expat is a widely used stream-oriented XML parser written in C. When processing UTF-16 encoded XML, the parser must correctly decode surrogate pairs in the range U+D800 to U+DFFF. A valid surrogate pair consists of a high surrogate followed by a low surrogate. Expat through 2.8.4 decodes the high surrogate and advances past the following code unit without confirming it is a valid low surrogate. An attacker can insert a lone high surrogate before markup bytes such as <, >, or quote characters. The parser then treats those markup bytes as continuation of the surrogate pair and skips them. Downstream consumers observing the reserialized or interpreted document see a different structure than what the parser processed, breaking the trust boundary between parsing and application logic.
Root Cause
The root cause is missing validation of the second UTF-16 code unit during surrogate decoding in expat/lib/xmltok_impl.c. The IS_INVALID_CHAR check for UTF-16 leads was defined as a no-op, so malformed sequences passed through without rejection. Pull request #1282 adds an explicit high-surrogate check to reject unpaired surrogates.
Attack Vector
Exploitation requires the target application to parse attacker-controlled UTF-16 XML with Expat. The attack is network-reachable, requires no privileges, and needs no user interaction. Injected markup can alter document semantics used for authentication decisions, configuration, or serialized data exchange.
// Patch excerpt from expat/lib/xmltok_impl.c
// Merge pull request #1282: utf16-high-surrogate-check
#ifdef XML_TOK_IMPL_C
-# ifndef IS_INVALID_CHAR // i.e. for UTF-16 and XML_MIN_SIZE not defined
-# define IS_INVALID_CHAR(enc, ptr, n) (0)
-# endif
-
# define INVALID_LEAD_CASE(n, ptr, nextTokPtr) \
case BT_LEAD##n: \
if (end - ptr < n) \
Source: GitHub commit ff6e1d7. The removed no-op macro previously allowed malformed UTF-16 leads through unchecked; the patched code enforces surrogate validation.
Detection Methods for CVE-2026-93990
Indicators of Compromise
- UTF-16 encoded XML payloads containing high surrogate code units (0xD800-0xDBFF) that are not immediately followed by a valid low surrogate (0xDC00-0xDFFF).
- XML documents where reserialized output differs structurally from the raw input, indicating parser-level markup hiding.
- Application logs showing Expat parse results that conflict with downstream validators or canonicalizers.
Detection Strategies
- Inventory processes and containers using libexpat at version 2.8.4 or earlier through software composition analysis.
- Add pre-parse validation that rejects UTF-16 inputs containing unpaired surrogates before handing data to Expat.
- Compare parsed element and attribute counts against a strict schema to surface injected markup.
Monitoring Recommendations
- Log XML parser errors and warnings from services accepting UTF-16 input, and alert on sudden increases.
- Capture and retain sample payloads at ingress points that declare encoding="UTF-16" for offline inspection.
- Track outbound behavior of services that consume parsed XML for anomalies in downstream authorization or data flow.
How to Mitigate CVE-2026-93990
Immediate Actions Required
- Upgrade Expat to a release that includes the fix from pull request #1282 once published by the maintainers.
- Rebuild and redistribute any statically linked binaries, language bindings, or container images that bundle Expat.
- Restrict or reject UTF-16 encoded XML at application ingress where UTF-8 is acceptable.
Patch Information
The fix is committed as ff6e1d7e750bbe245178f51a47a965dc8342861a in the libexpat repository. The patch removes the no-op IS_INVALID_CHAR macro path for UTF-16 and enforces validation of the low surrogate that must follow a high surrogate. Additional details are available in the VulnCheck advisory.
Workarounds
- Transcode incoming XML to UTF-8 before parsing, rejecting any input that contains unpaired surrogates during conversion.
- Enforce a strict allowlist of accepted XML encodings at the application boundary.
- Apply schema validation with canonicalization after parsing to detect structural drift caused by hidden markup.
# Example: verify installed Expat version and audit linked binaries
dpkg -l | grep -i expat
rpm -qa | grep -i expat
# Identify processes that have libexpat mapped in memory
for pid in $(pgrep -a . | awk '{print $1}'); do
grep -l 'libexpat' /proc/$pid/maps 2>/dev/null && echo "pid=$pid"
done
# Pre-parse guard: reject UTF-16 XML containing unpaired high surrogates
python3 -c '
import sys
data = sys.stdin.buffer.read().decode("utf-16", errors="strict")
print("ok", len(data))
'
Disclaimer: This content was generated using AI. While we strive for accuracy, please verify critical information with official sources.

