How XML Parsers Resolve Circular Entities Safely
This article explains how XML parsers detect and safely resolve circular entity reference definitions to prevent infinite loops, stack overflows, and denial-of-service vulnerabilities. XML documents allow custom entity declarations that substitute text strings during parsing; however, when entities reference one another in a loop, parser safety depends on graph-based cycle detection, recursion depth limits, entity count thresholds, and secure parser configurations.
The Risk of Circular Entity References
An XML entity is a shortcut that represents a block of data. In a Document Type Definition (DTD), entities can be nested within one another. A circular reference occurs when an entity directly or indirectly references itself:
- Direct Cycle: Entity
&a;expands to text containing&a;. - Indirect Cycle: Entity
&a;expands to&b;, and entity&b;expands back to&a;.
Without safety mechanisms, the parser attempts to recursively resolve the replacement text indefinitely, exhausting the system’s call stack or memory (often leading to a Denial of Service, similar to the “Billion Laughs” attack).
1. Call Stack Tracking and Visited Sets
The primary algorithmic defense against circular entity definitions is tracking active expansions using a stack or directed graph.
- Active Expansion Stack: When the parser begins resolving an entity, it pushes the entity’s identifier (such as its name) onto an active resolution stack.
- Cycle Check: Before resolving any child entity encountered inside the replacement text, the parser checks if the child’s identifier already exists on the active stack.
- Error Generation: If an entity is found to be
already present on the active stack, the parser halts resolution
immediately and throws a fatal error (e.g.,
Circular entity reference detectedorXML_ERR_ENTITY_LOOP). - Popping the Stack: Once an entity’s replacement text has been completely parsed and substituted without encountering duplicates, the parser pops that entity from the stack.
This directed acyclic graph (DAG) verification ensures that only forward references are processed.
2. Hard Limits on Recursion Depth
In addition to detecting direct loops, parsers enforce fixed recursion depth boundaries.
- Standard XML processing libraries (such as
libxml2,Xerces, and Java’s standardjavax.xml.parsers) set a default maximum nesting level for entity substitution (typically between 10 and 20 levels). - If entity expansion exceeds this maximum threshold, the parser terminates processing with an exception, regardless of whether a strict cycle was identified.
3. Entity Expansion and Node Count Limits
Circular definitions can sometimes be structured to multiply exponentially rather than loop infinitely (such as nested entity multiplication). To mitigate resource exhaustion:
- Cumulative Expansion Limits: Parsers monitor the total number of characters generated by entity expansions compared to the original document size.
- Threshold Triggering: If the cumulative expanded entity size exceeds predefined safety thresholds (for instance, a default 50 MB total expansion or an expansion ratio greater than 100:1), parsing aborts.
4. Disabling DTD Processing
The most robust architectural solution used by modern parsers is to restrict or completely disable inline DTD and general entity processing.
- Disabling External DTDs: Prevents resolving external resources and URIs.
- Disabling Entity Resolution: For modern web APIs
and service endpoints handling untrusted XML, entity expansion can be
turned off entirely (e.g., setting
XMLConstants.FEATURE_SECURE_PROCESSINGin Java, or settingresolve_entities=Falsein Python’slxml).
By combining active stack tracking, recursion depth constraints, and strict configuration defaults, XML parsers safely handle circular entity definitions without risking system stability.