Skip to content

Commit d5be4a7

Browse files
azurelinux-securityKanishk-Bansalv-aadityajslobodzian
authored
[AutoPR- Security] Patch perl-XML-Parser for CVE-2006-10003, CVE-2006-10002 [HIGH] (#16241)
Co-authored-by: Kanishk Bansal <103916909+Kanishk-Bansal@users.noreply.github.com> Co-authored-by: Aditya Singh <v-aditysing@microsoft.com> Co-authored-by: jslobodzian <joslobo@microsoft.com>
1 parent d7ca6c2 commit d5be4a7

File tree

9 files changed

+470
-22
lines changed

9 files changed

+470
-22
lines changed
Lines changed: 256 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,256 @@
1+
From 89a9c6807c982b4fa8aa806dd72771d6642dd8a1 Mon Sep 17 00:00:00 2001
2+
From: Berkay Eren Ürün <berkay.ueruen@siemens.com
3+
Date: Wed, 19 Mar 2025 02:20:49 +0100
4+
Subject: [PATCH] Stop updating m_eventPtr on exit for reentry
5+
6+
The fix for recursive entity processing introduced a reenter flag that
7+
returns the execution from the current function and switches to entity
8+
processing.
9+
10+
The same fix also updates the m_eventPtr during this switch. However
11+
this update changes the behaviour in certain cases as the older version
12+
does not update the m_eventPtr while recursing into entity processing.
13+
14+
This commit removes the pointer update and restores the old behaviour.
15+
16+
Upstream Patch Reference: https://patch-diff.githubusercontent.com/raw/libexpat/libexpat/pull/989.patch
17+
---
18+
Changes | 15 ++++++++++++
19+
lib/xmlparse.c | 12 ++++++---
20+
tests/common.c | 25 +++++++++++++++++++
21+
tests/common.h | 2 ++
22+
tests/misc_tests.c | 61 ++++++++++++++++++++++++++++++++++++++++++++++
23+
5 files changed, 112 insertions(+), 3 deletions(-)
24+
25+
diff --git a/Changes b/Changes
26+
index 75c62d6..8c4ed04 100644
27+
--- a/Changes
28+
+++ b/Changes
29+
@@ -29,6 +29,21 @@
30+
!! THANK YOU! Sebastian Pipping -- Berlin, 2024-03-09 !!
31+
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
32+
33+
+ Bug fixes:
34+
+ #980 #989 Restore event pointer behavior from Expat 2.6.4
35+
+ (that the fix to CVE-2024-8176 changed in 2.7.0);
36+
+ affected API functions are:
37+
+ - XML_GetCurrentByteCount
38+
+ - XML_GetCurrentByteIndex
39+
+ - XML_GetCurrentColumnNumber
40+
+ - XML_GetCurrentLineNumber
41+
+ - XML_GetInputContext
42+
+
43+
+ Special thanks to:
44+
+ Berkay Eren Ürün
45+
+ and
46+
+ Perl XML::Parser
47+
+
48+
Security fixes:
49+
#893 #??? CVE-2024-8176 -- Fix crash from chaining a large number
50+
of entities caused by stack overflow by resolving use of
51+
diff --git a/lib/xmlparse.c b/lib/xmlparse.c
52+
index 0bf913c..9ec287c 100644
53+
--- a/lib/xmlparse.c
54+
+++ b/lib/xmlparse.c
55+
@@ -3754,12 +3754,13 @@ doContent(XML_Parser parser, int startTagLevel, const ENCODING *enc,
56+
break;
57+
/* LCOV_EXCL_STOP */
58+
}
59+
- *eventPP = s = next;
60+
switch (parser->m_parsingStatus.parsing) {
61+
case XML_SUSPENDED:
62+
+ *eventPP = next;
63+
*nextPtr = next;
64+
return XML_ERROR_NONE;
65+
case XML_FINISHED:
66+
+ *eventPP = next;
67+
return XML_ERROR_ABORTED;
68+
case XML_PARSING:
69+
if (parser->m_reenter) {
70+
@@ -3768,6 +3769,7 @@ doContent(XML_Parser parser, int startTagLevel, const ENCODING *enc,
71+
}
72+
/* Fall through */
73+
default:;
74+
+ *eventPP = s = next;
75+
}
76+
}
77+
/* not reached */
78+
@@ -4684,12 +4686,13 @@ doCdataSection(XML_Parser parser, const ENCODING *enc, const char **startPtr,
79+
/* LCOV_EXCL_STOP */
80+
}
81+
82+
- *eventPP = s = next;
83+
switch (parser->m_parsingStatus.parsing) {
84+
case XML_SUSPENDED:
85+
+ *eventPP = next;
86+
*nextPtr = next;
87+
return XML_ERROR_NONE;
88+
case XML_FINISHED:
89+
+ *eventPP = next;
90+
return XML_ERROR_ABORTED;
91+
case XML_PARSING:
92+
if (parser->m_reenter) {
93+
@@ -4697,6 +4700,7 @@ doCdataSection(XML_Parser parser, const ENCODING *enc, const char **startPtr,
94+
}
95+
/* Fall through */
96+
default:;
97+
+ *eventPP = s = next;
98+
}
99+
}
100+
/* not reached */
101+
@@ -6307,12 +6311,13 @@ epilogProcessor(XML_Parser parser, const char *s, const char *end,
102+
default:
103+
return XML_ERROR_JUNK_AFTER_DOC_ELEMENT;
104+
}
105+
- parser->m_eventPtr = s = next;
106+
switch (parser->m_parsingStatus.parsing) {
107+
case XML_SUSPENDED:
108+
+ parser->m_eventPtr = next;
109+
*nextPtr = next;
110+
return XML_ERROR_NONE;
111+
case XML_FINISHED:
112+
+ parser->m_eventPtr = next;
113+
return XML_ERROR_ABORTED;
114+
case XML_PARSING:
115+
if (parser->m_reenter) {
116+
@@ -6320,6 +6325,7 @@ epilogProcessor(XML_Parser parser, const char *s, const char *end,
117+
}
118+
/* Fall through */
119+
default:;
120+
+ parser->m_eventPtr = s = next;
121+
}
122+
}
123+
}
124+
diff --git a/tests/common.c b/tests/common.c
125+
index 3aea8d7..b267dbb 100644
126+
--- a/tests/common.c
127+
+++ b/tests/common.c
128+
@@ -42,6 +42,8 @@
129+
*/
130+
131+
#include <assert.h>
132+
+#include <errno.h>
133+
+#include <stdint.h> // for SIZE_MAX
134+
#include <stdio.h>
135+
#include <string.h>
136+
137+
@@ -294,3 +296,26 @@ duff_reallocator(void *ptr, size_t size) {
138+
g_reallocation_count--;
139+
return realloc(ptr, size);
140+
}
141+
+
142+
+// Portable remake of strndup(3) for C99; does not care about space efficiency
143+
+char *
144+
+portable_strndup(const char *s, size_t n) {
145+
+ if ((s == NULL) || (n == SIZE_MAX)) {
146+
+ errno = EINVAL;
147+
+ return NULL;
148+
+ }
149+
+
150+
+ char *const buffer = (char *)malloc(n + 1);
151+
+ if (buffer == NULL) {
152+
+ errno = ENOMEM;
153+
+ return NULL;
154+
+ }
155+
+
156+
+ errno = 0;
157+
+
158+
+ memcpy(buffer, s, n);
159+
+
160+
+ buffer[n] = '\0';
161+
+
162+
+ return buffer;
163+
+}
164+
diff --git a/tests/common.h b/tests/common.h
165+
index bc4c7da..8871130 100644
166+
--- a/tests/common.h
167+
+++ b/tests/common.h
168+
@@ -146,6 +146,8 @@ extern void *duff_allocator(size_t size);
169+
170+
extern void *duff_reallocator(void *ptr, size_t size);
171+
172+
+extern char *portable_strndup(const char *s, size_t n);
173+
+
174+
#endif /* XML_COMMON_H */
175+
176+
#ifdef __cplusplus
177+
diff --git a/tests/misc_tests.c b/tests/misc_tests.c
178+
index f9a78f6..2b9f793 100644
179+
--- a/tests/misc_tests.c
180+
+++ b/tests/misc_tests.c
181+
@@ -561,6 +561,66 @@ START_TEST(test_renter_loop_finite_content) {
182+
}
183+
END_TEST
184+
185+
+// Inspired by function XML_OriginalString of Perl's XML::Parser
186+
+static char *
187+
+dup_original_string(XML_Parser parser) {
188+
+ const int byte_count = XML_GetCurrentByteCount(parser);
189+
+
190+
+ assert_true(byte_count >= 0);
191+
+
192+
+ int offset = -1;
193+
+ int size = -1;
194+
+
195+
+ const char *const context = XML_GetInputContext(parser, &offset, &size);
196+
+
197+
+#if XML_CONTEXT_BYTES > 0
198+
+ assert_true(context != NULL);
199+
+ assert_true(offset >= 0);
200+
+ assert_true(size >= 0);
201+
+ return portable_strndup(context + offset, byte_count);
202+
+#else
203+
+ assert_true(context == NULL);
204+
+ return NULL;
205+
+#endif
206+
+}
207+
+
208+
+static void
209+
+on_characters_issue_980(void *userData, const XML_Char *s, int len) {
210+
+ (void)s;
211+
+ (void)len;
212+
+ XML_Parser parser = (XML_Parser)userData;
213+
+
214+
+ char *const original_string = dup_original_string(parser);
215+
+
216+
+#if XML_CONTEXT_BYTES > 0
217+
+ assert_true(original_string != NULL);
218+
+ assert_true(strcmp(original_string, "&draft.day;") == 0);
219+
+ free(original_string);
220+
+#else
221+
+ assert_true(original_string == NULL);
222+
+#endif
223+
+}
224+
+
225+
+START_TEST(test_misc_expected_event_ptr_issue_980) {
226+
+ // NOTE: This is a tiny subset of sample "REC-xml-19980210.xml"
227+
+ // from Perl's XML::Parser
228+
+ const char *const doc = "<!DOCTYPE day [\n"
229+
+ " <!ENTITY draft.day '10'>\n"
230+
+ "]>\n"
231+
+ "<day>&draft.day;</day>\n";
232+
+
233+
+ XML_Parser parser = XML_ParserCreate(NULL);
234+
+ XML_SetUserData(parser, parser);
235+
+ XML_SetCharacterDataHandler(parser, on_characters_issue_980);
236+
+
237+
+ assert_true(_XML_Parse_SINGLE_BYTES(parser, doc, (int)strlen(doc),
238+
+ /*isFinal=*/XML_TRUE)
239+
+ == XML_STATUS_OK);
240+
+
241+
+ XML_ParserFree(parser);
242+
+}
243+
+END_TEST
244+
+
245+
void
246+
make_miscellaneous_test_case(Suite *s) {
247+
TCase *tc_misc = tcase_create("miscellaneous tests");
248+
@@ -588,4 +648,5 @@ make_miscellaneous_test_case(Suite *s) {
249+
tcase_add_test(tc_misc, test_misc_resumeparser_not_crashing);
250+
tcase_add_test(tc_misc, test_misc_stopparser_rejects_unstarted_parser);
251+
tcase_add_test__if_xml_ge(tc_misc, test_renter_loop_finite_content);
252+
+ tcase_add_test(tc_misc, test_misc_expected_event_ptr_issue_980);
253+
}
254+
--
255+
2.45.4
256+

SPECS/expat/expat.spec

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
Summary: An XML parser library
33
Name: expat
44
Version: 2.6.4
5-
Release: 4%{?dist}
5+
Release: 5%{?dist}
66
License: MIT
77
Vendor: Microsoft Corporation
88
Distribution: Azure Linux
@@ -13,6 +13,7 @@ Patch0: CVE-2024-8176.patch
1313
Patch1: CVE-2025-59375.patch
1414
Patch2: CVE-2026-24515.patch
1515
Patch3: CVE-2026-25210.patch
16+
Patch4: Stop-updating-event-pointer-on-exit-for-reentry.patch
1617
Requires: %{name}-libs = %{version}-%{release}
1718

1819
%description
@@ -70,6 +71,10 @@ rm -rf %{buildroot}/%{_docdir}/%{name}
7071
%{_libdir}/libexpat.so.1*
7172

7273
%changelog
74+
* Tue Mar 31 2026 Aditya Singh <v-aditysing@microsoft.com> - 2.6.4-5
75+
- Patch to restore event pointer behavior from Expat 2.6.4
76+
- which was changed due to fix for CVE-2024-8176.
77+
7378
* Mon Feb 02 2026 Azure Linux Security Servicing Account <azurelinux-security@microsoft.com> - 2.6.4-4
7479
- Patch for CVE-2026-25210
7580

Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
1+
From 31f0c03a1a265f5a5703531628f4b5bc0341d5c8 Mon Sep 17 00:00:00 2001
2+
From: Toddr Bot <toddbot@rinaldo.us>
3+
Date: Mon, 16 Mar 2026 20:55:31 +0000
4+
Subject: [PATCH] Fix buffer overflow in parse_stream when filehandle has :utf8
5+
layer
6+
7+
When a filehandle has a :utf8 PerlIO layer, Perl's read() returns
8+
decoded characters, but SvPV() gives back the UTF-8 byte
9+
representation which can be larger than the pre-allocated XML buffer.
10+
Previously this caused heap corruption (double free / buffer overflow),
11+
and a later workaround (BUFSIZE * 6 + croak) prevented the corruption
12+
but still crashed.
13+
14+
Fix by re-obtaining the expat buffer at the actual byte size when the
15+
read produces more bytes than initially allocated. This handles UTF-8
16+
streams gracefully without wasting memory on an oversized buffer.
17+
18+
Fixes https://github.com/cpan-authors/XML-Parser/issues/64
19+
(migrated from rt.cpan.org #19859)
20+
21+
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
22+
Signed-off-by: Azure Linux Security Servicing Account <azurelinux-security@microsoft.com>
23+
Upstream-reference: https://github.com/cpan-authors/XML-Parser/commit/6b291f4d260fc124a6ec80382b87a918f372bc6b.patch
24+
---
25+
Expat/Expat.xs | 15 +++++++++++----
26+
t/utf8_stream.t | 40 ++++++++++++++++++++++++++++++++++++++++
27+
2 files changed, 51 insertions(+), 4 deletions(-)
28+
create mode 100644 t/utf8_stream.t
29+
30+
diff --git a/Expat/Expat.xs b/Expat/Expat.xs
31+
index dbad380..356df84 100644
32+
--- a/Expat/Expat.xs
33+
+++ b/Expat/Expat.xs
34+
@@ -343,8 +343,8 @@ parse_stream(XML_Parser parser, SV * ioref)
35+
}
36+
else {
37+
tbuff = newSV(0);
38+
- tsiz = newSViv(BUFSIZE); /* in UTF-8 characters */
39+
- buffsize = BUFSIZE * 6; /* in bytes that encode an UTF-8 string */
40+
+ tsiz = newSViv(BUFSIZE);
41+
+ buffsize = BUFSIZE;
42+
}
43+
44+
while (! done)
45+
@@ -387,8 +387,15 @@ parse_stream(XML_Parser parser, SV * ioref)
46+
47+
tb = SvPV(tbuff, br);
48+
if (br > 0) {
49+
- if (br > buffsize)
50+
- croak("The input buffer is not large enough for read UTF-8 decoded string");
51+
+ if (br > buffsize) {
52+
+ /* The byte count from SvPV can exceed buffsize when the
53+
+ filehandle has a :utf8 layer, since Perl reads buffsize
54+
+ characters but multi-byte UTF-8 chars produce more bytes.
55+
+ Re-obtain the buffer at the required size. */
56+
+ buffer = XML_GetBuffer(parser, br);
57+
+ if (! buffer)
58+
+ croak("Ran out of memory for input buffer");
59+
+ }
60+
Copy(tb, buffer, br, char);
61+
} else
62+
done = 1;
63+
diff --git a/t/utf8_stream.t b/t/utf8_stream.t
64+
new file mode 100644
65+
index 0000000..a7e55f7
66+
--- /dev/null
67+
+++ b/t/utf8_stream.t
68+
@@ -0,0 +1,40 @@
69+
+BEGIN { print "1..2\n"; }
70+
+END { print "not ok 1\n" unless $loaded; }
71+
+use XML::Parser;
72+
+$loaded = 1;
73+
+print "ok 1\n";
74+
+
75+
+################################################################
76+
+# Test parsing from a filehandle with :utf8 layer
77+
+# Regression test for rt.cpan.org #19859 / GitHub issue #64
78+
+# A UTF-8 stream caused buffer overflow because SvPV byte count
79+
+# could exceed the pre-allocated XML_GetBuffer size.
80+
+
81+
+use File::Temp qw(tempfile);
82+
+
83+
+# Create a temp file with UTF-8 XML content containing multi-byte chars
84+
+my ($fh, $tmpfile) = tempfile(UNLINK => 1);
85+
+binmode($fh, ':raw');
86+
+# Write raw UTF-8 bytes: XML with Chinese characters (3 bytes each in UTF-8)
87+
+# U+4E16 U+754C (世界 = "world") repeated to create substantial multi-byte content
88+
+my $body = "\xe4\xb8\x96\xe7\x95\x8c" x 20000; # 120000 bytes / 40000 chars of 3-byte UTF-8
89+
+print $fh qq(<?xml version="1.0" encoding="UTF-8"?>\n<doc>$body</doc>\n);
90+
+close($fh);
91+
+
92+
+my $text = '';
93+
+my $parser = XML::Parser->new(
94+
+ Handlers => {
95+
+ Char => sub { $text .= $_[1]; },
96+
+ }
97+
+);
98+
+
99+
+# Open with :utf8 layer - this is what triggers the bug
100+
+open(my $in, '<:utf8', $tmpfile) or die "Cannot open $tmpfile: $!";
101+
+eval { $parser->parse($in); };
102+
+close($in);
103+
+
104+
+if ($@ eq '' && length($text) > 0) {
105+
+ print "ok 2\n";
106+
+} else {
107+
+ print "not ok 2 # $@\n";
108+
+}
109+
--
110+
2.45.4
111+

0 commit comments

Comments
 (0)