Data Corruption via Unrecognized Entity in StrPair::GetStr()
Assessment
- Difficulty
- 1/5
- Estimated time
- Under an hour
- Newbie friendliness
- 25/100
Research direction
The bug is in StrPair::GetStr() in tinyxml2.cpp, around lines 348-352, where the unrecognized-entity branch advances both pointers without copying *p to *q. Read the nearby recognized-entity and default-copy branches to see the intended pattern. Pull request #1086 is already open against this issue, so check it before starting. Done means a test parsing A&B&xyz;C returns text containing & at index 3.
Written by the indexing model from the issue text.
Description
Summary
| Field | Value |
|---|---|
| Affected Software | tinyxml2 |
| Affected Version | 11.0.0 (and all prior versions) |
| Vulnerability Type | Improper Input Validation / Data Integrity (CWE-20, CWE-457) |
| Severity | Medium (CVSS 3.1: 5.3 — AV:N/AC:L/PR:N/UI:N/S:U/C:L/I:L/A:N) |
| File | tinyxml2.cpp, function StrPair::GetStr(), lines 348–352 |
| Impact | Parsed text/attribute content is silently corrupted; stale buffer bytes replace & characters |
| Discovered By | Fasheng Miao, Zhaoyu Hu |
Description
When tinyxml2 parses XML text or attribute values containing entity references, the StrPair::GetStr() function processes entities in-place using a read pointer p and a write pointer q. When a recognized entity (e.g., & → &) is expanded, the output is shorter than the input, causing q to fall behind p.
When subsequently encountering an unrecognized entity reference (e.g., &xyz;), the code increments both p and q without copying *p to *q. The & character is silently dropped, and the byte at position q retains stale content from a previous processing step — leaking internal buffer data into the parsed string.
Root Cause
In tinyxml2.cpp, lines 348–352:
if ( !entityFound ) {
// fixme: treat as error?
++p; // advance read pointer past '&'
++q; // advance write pointer — BUT *q IS NEVER WRITTEN
}
Compare with the correct default character copy path at lines 355–358:
else {
*q = *p; // CORRECT: copy character from read to write position
++p;
++q;
}
The fix is trivial: add *q = *p; before the increments. The code even has a // fixme comment acknowledging the issue.
Proof-of-Concept
PoC Code
#include <cstdio>
#include "tinyxml2.h"
using namespace tinyxml2;
int main() {
// & expands to &, making q < p.
// Then &xyz; hits the bug: & is dropped, stale 'm' appears instead.
const char* xml = "<root>A&B&xyz;C</root>";
XMLDocument doc;
doc.Parse(xml);
const char* text = doc.FirstChildElement("root")->GetText();
printf("Input: A&B&xyz;C\n");
printf("Expected: A&B&xyz;C (or A&B with error)\n");
printf("Actual: %s\n", text);
// text[3] is 'm' (0x6d) instead of '&' (0x26)
return 0;
}
PoC Output
=== tinyxml2 Entity Processing Data Corruption ===
tinyxml2 version: 11.0.0
--- Test 1: Recognized entity followed by unrecognized entity ---
Input XML: <root>A&B&xyz;C</root>
Expected: A&B should parse to 'A&B', then &xyz; should preserve '&'
Expected text: 'A&B&xyz;C' (or 'A&B' with error on &xyz;)
Actual text: 'A&Bmxyz;C'
Text length: 9
Hex dump: 41 26 42 6d 78 79 7a 3b 43
** BUG CONFIRMED: text[3] = 'm' (0x6d), expected '&' (0x26)
The '&' was dropped and replaced by stale buffer content.
--- Test 2: Multiple recognized entities then unrecognized ---
Input XML: <root><>&&fake;X</root>
Expected: '<>&' then '&fake;X' or error
Actual text: '<>&;fake;X'
Hex dump: 3c 3e 26 3b 66 61 6b 65 3b 58
** BUG: text[3] = ';' (0x3b), expected '&' (0x26)
--- Test 4: Entity corruption in attribute value ---
Input XML: <root attr="A&B&xyz;C"/>
Attribute: 'A&Bmxyz;C'
** BUG: attr[3] = 'm' (0x6d), expected '&' (0x26)
Impact
-
Data Integrity: Parsed XML text and attribute values are silently corrupted. The
&character of unrecognized entity references is replaced by a byte from an earlier position in the same buffer. Applications that rely on exact string content from parsed XML will process incorrect data. -
Security-Sensitive String Comparisons: If the parsed text is used for authentication tokens, access control checks, URL validation, or similar security-sensitive comparisons, the corruption could cause incorrect matches or bypasses.
-
Stale Data Leakage: The leaked byte comes from a known position in the same input buffer (determined by how many prior entities were expanded). While this is not a cross-allocation information leak, it could be used to infer information about the input structure in certain scenarios.
-
Silent Failure: No error is reported. The
// fixme: treat as error?comment in the source code indicates the developers were aware this case was unhandled.
Affected API Surface
This affects ALL XML parsing through tinyxml2 when processEntities is enabled (the default). Both text content and attribute values are affected. The bug is triggered by any XML document containing a recognized entity followed by an unrecognized entity reference in the same text node or attribute value.
Suggested Fix
--- a/tinyxml2.cpp
+++ b/tinyxml2.cpp
@@ -346,8 +346,8 @@
}
if ( !entityFound ) {
- // fixme: treat as error?
- ++p;
+ // Unrecognized entity: copy '&' as-is
+ *q = *p;
++q;
+ ++p;
}
Alternative fix: return an error (return 0;) to reject XML with unrecognized entities, as the fixme comment suggests.
Environment
- OS: macOS (Darwin 25.3.0, arm64)
- Compiler: Apple clang 21.0.0
- tinyxml2 version: 11.0.0, built from source
Credit
This vulnerability was discovered by Fasheng Miao and Zhaoyu Hu.
- Dominant language
- C++
- Stars
- 5.8k
- Forks
- 2k
- PR merge metrics
- No merged PRs in 30d
Getting set up
This project ships no dev container, Dockerfile or contributing guide, so setting up is up to you: start from its README, and see our first-contribution guide for the general steps.
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
More from leethomason/tinyxml2
-
CDATA output containing ]]> is not well-formed XMLPossibly taken @bconnnnn claimed this 8 days ago. Open
Difficulty 2/5 1-3 hours Newbie friendliness 70/100
leethomason/tinyxml2#1097 ·
-
Difficulty 2/5 1-3 hours Newbie friendliness 62/100
leethomason/tinyxml2#923 · 2 comments ·
-
Difficulty 2/5 1-3 hours Newbie friendliness 62/100
leethomason/tinyxml2#914 ·
-
New release requestOpen
Difficulty 2/5 Under an hour Newbie friendliness 15/100
leethomason/tinyxml2#1087 · 4 comments ·
-
Difficulty 5/5 Over a week Newbie friendliness 18/100
leethomason/tinyxml2#1085 · 1 comment ·
All issues in leethomason/tinyxml2
Similar issues
-
Difficulty 2/5 1-3 hours Newbie friendliness 78/100
nasa/fprime#6225 · 1 comment ·
Maintainers usually reply within 1 day
-
content good first issue
Difficulty 1/5 Under an hour Newbie friendliness 95/100
cpinitiative/usaco-guide#6699 ·
Maintainers usually reply within 1 day
-
Difficulty 2/5 1-3 hours Newbie friendliness 65/100
codepointerapp/codepointer#231 ·
-
enhancement source/qq
Difficulty 2/5 1-3 hours Newbie friendliness 72/100
metasequoiaime/msime-windows#1054 ·
Maintainers usually reply within 1 day
-
Difficulty 2/5 1-3 hours Newbie friendliness 88/100
amnezia-vpn/amnezia-client#3297 · 1 comment ·
Maintainers usually reply within 1 day