Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
256 changes: 256 additions & 0 deletions SPECS/expat/Stop-updating-event-pointer-on-exit-for-reentry.patch
Original file line number Diff line number Diff line change
@@ -0,0 +1,256 @@
From 89a9c6807c982b4fa8aa806dd72771d6642dd8a1 Mon Sep 17 00:00:00 2001
From: Berkay Eren Ürün <berkay.ueruen@siemens.com
Date: Wed, 19 Mar 2025 02:20:49 +0100
Subject: [PATCH] Stop updating m_eventPtr on exit for reentry

The fix for recursive entity processing introduced a reenter flag that
returns the execution from the current function and switches to entity
processing.

The same fix also updates the m_eventPtr during this switch. However
this update changes the behaviour in certain cases as the older version
does not update the m_eventPtr while recursing into entity processing.

This commit removes the pointer update and restores the old behaviour.

Upstream Patch Reference: https://patch-diff.githubusercontent.com/raw/libexpat/libexpat/pull/989.patch
---
Changes | 15 ++++++++++++
lib/xmlparse.c | 12 ++++++---
tests/common.c | 25 +++++++++++++++++++
tests/common.h | 2 ++
tests/misc_tests.c | 61 ++++++++++++++++++++++++++++++++++++++++++++++
5 files changed, 112 insertions(+), 3 deletions(-)

diff --git a/Changes b/Changes
index 75c62d6..8c4ed04 100644
--- a/Changes
+++ b/Changes
@@ -29,6 +29,21 @@
!! THANK YOU! Sebastian Pipping -- Berlin, 2024-03-09 !!
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!

+ Bug fixes:
+ #980 #989 Restore event pointer behavior from Expat 2.6.4
+ (that the fix to CVE-2024-8176 changed in 2.7.0);
+ affected API functions are:
+ - XML_GetCurrentByteCount
+ - XML_GetCurrentByteIndex
+ - XML_GetCurrentColumnNumber
+ - XML_GetCurrentLineNumber
+ - XML_GetInputContext
+
+ Special thanks to:
+ Berkay Eren Ürün
+ and
+ Perl XML::Parser
+
Security fixes:
#893 #??? CVE-2024-8176 -- Fix crash from chaining a large number
of entities caused by stack overflow by resolving use of
diff --git a/lib/xmlparse.c b/lib/xmlparse.c
index 0bf913c..9ec287c 100644
--- a/lib/xmlparse.c
+++ b/lib/xmlparse.c
@@ -3754,12 +3754,13 @@ doContent(XML_Parser parser, int startTagLevel, const ENCODING *enc,
break;
/* LCOV_EXCL_STOP */
}
- *eventPP = s = next;
switch (parser->m_parsingStatus.parsing) {
case XML_SUSPENDED:
+ *eventPP = next;
*nextPtr = next;
return XML_ERROR_NONE;
case XML_FINISHED:
+ *eventPP = next;
return XML_ERROR_ABORTED;
case XML_PARSING:
if (parser->m_reenter) {
@@ -3768,6 +3769,7 @@ doContent(XML_Parser parser, int startTagLevel, const ENCODING *enc,
}
/* Fall through */
default:;
+ *eventPP = s = next;
}
}
/* not reached */
@@ -4684,12 +4686,13 @@ doCdataSection(XML_Parser parser, const ENCODING *enc, const char **startPtr,
/* LCOV_EXCL_STOP */
}

- *eventPP = s = next;
switch (parser->m_parsingStatus.parsing) {
case XML_SUSPENDED:
+ *eventPP = next;
*nextPtr = next;
return XML_ERROR_NONE;
case XML_FINISHED:
+ *eventPP = next;
return XML_ERROR_ABORTED;
case XML_PARSING:
if (parser->m_reenter) {
@@ -4697,6 +4700,7 @@ doCdataSection(XML_Parser parser, const ENCODING *enc, const char **startPtr,
}
/* Fall through */
default:;
+ *eventPP = s = next;
}
}
/* not reached */
@@ -6307,12 +6311,13 @@ epilogProcessor(XML_Parser parser, const char *s, const char *end,
default:
return XML_ERROR_JUNK_AFTER_DOC_ELEMENT;
}
- parser->m_eventPtr = s = next;
switch (parser->m_parsingStatus.parsing) {
case XML_SUSPENDED:
+ parser->m_eventPtr = next;
*nextPtr = next;
return XML_ERROR_NONE;
case XML_FINISHED:
+ parser->m_eventPtr = next;
return XML_ERROR_ABORTED;
case XML_PARSING:
if (parser->m_reenter) {
@@ -6320,6 +6325,7 @@ epilogProcessor(XML_Parser parser, const char *s, const char *end,
}
/* Fall through */
default:;
+ parser->m_eventPtr = s = next;
}
}
}
diff --git a/tests/common.c b/tests/common.c
index 3aea8d7..b267dbb 100644
--- a/tests/common.c
+++ b/tests/common.c
@@ -42,6 +42,8 @@
*/

#include <assert.h>
+#include <errno.h>
+#include <stdint.h> // for SIZE_MAX
#include <stdio.h>
#include <string.h>

@@ -294,3 +296,26 @@ duff_reallocator(void *ptr, size_t size) {
g_reallocation_count--;
return realloc(ptr, size);
}
+
+// Portable remake of strndup(3) for C99; does not care about space efficiency
+char *
+portable_strndup(const char *s, size_t n) {
+ if ((s == NULL) || (n == SIZE_MAX)) {
+ errno = EINVAL;
+ return NULL;
+ }
+
+ char *const buffer = (char *)malloc(n + 1);
+ if (buffer == NULL) {
+ errno = ENOMEM;
+ return NULL;
+ }
+
+ errno = 0;
+
+ memcpy(buffer, s, n);
+
+ buffer[n] = '\0';
+
+ return buffer;
+}
diff --git a/tests/common.h b/tests/common.h
index bc4c7da..8871130 100644
--- a/tests/common.h
+++ b/tests/common.h
@@ -146,6 +146,8 @@ extern void *duff_allocator(size_t size);

extern void *duff_reallocator(void *ptr, size_t size);

+extern char *portable_strndup(const char *s, size_t n);
+
#endif /* XML_COMMON_H */

#ifdef __cplusplus
diff --git a/tests/misc_tests.c b/tests/misc_tests.c
index f9a78f6..2b9f793 100644
--- a/tests/misc_tests.c
+++ b/tests/misc_tests.c
@@ -561,6 +561,66 @@ START_TEST(test_renter_loop_finite_content) {
}
END_TEST

+// Inspired by function XML_OriginalString of Perl's XML::Parser
+static char *
+dup_original_string(XML_Parser parser) {
+ const int byte_count = XML_GetCurrentByteCount(parser);
+
+ assert_true(byte_count >= 0);
+
+ int offset = -1;
+ int size = -1;
+
+ const char *const context = XML_GetInputContext(parser, &offset, &size);
+
+#if XML_CONTEXT_BYTES > 0
+ assert_true(context != NULL);
+ assert_true(offset >= 0);
+ assert_true(size >= 0);
+ return portable_strndup(context + offset, byte_count);
+#else
+ assert_true(context == NULL);
+ return NULL;
+#endif
+}
+
+static void
+on_characters_issue_980(void *userData, const XML_Char *s, int len) {
+ (void)s;
+ (void)len;
+ XML_Parser parser = (XML_Parser)userData;
+
+ char *const original_string = dup_original_string(parser);
+
+#if XML_CONTEXT_BYTES > 0
+ assert_true(original_string != NULL);
+ assert_true(strcmp(original_string, "&draft.day;") == 0);
+ free(original_string);
+#else
+ assert_true(original_string == NULL);
+#endif
+}
+
+START_TEST(test_misc_expected_event_ptr_issue_980) {
+ // NOTE: This is a tiny subset of sample "REC-xml-19980210.xml"
+ // from Perl's XML::Parser
+ const char *const doc = "<!DOCTYPE day [\n"
+ " <!ENTITY draft.day '10'>\n"
+ "]>\n"
+ "<day>&draft.day;</day>\n";
+
+ XML_Parser parser = XML_ParserCreate(NULL);
+ XML_SetUserData(parser, parser);
+ XML_SetCharacterDataHandler(parser, on_characters_issue_980);
+
+ assert_true(_XML_Parse_SINGLE_BYTES(parser, doc, (int)strlen(doc),
+ /*isFinal=*/XML_TRUE)
+ == XML_STATUS_OK);
+
+ XML_ParserFree(parser);
+}
+END_TEST
+
void
make_miscellaneous_test_case(Suite *s) {
TCase *tc_misc = tcase_create("miscellaneous tests");
@@ -588,4 +648,5 @@ make_miscellaneous_test_case(Suite *s) {
tcase_add_test(tc_misc, test_misc_resumeparser_not_crashing);
tcase_add_test(tc_misc, test_misc_stopparser_rejects_unstarted_parser);
tcase_add_test__if_xml_ge(tc_misc, test_renter_loop_finite_content);
+ tcase_add_test(tc_misc, test_misc_expected_event_ptr_issue_980);
}
--
2.45.4

7 changes: 6 additions & 1 deletion SPECS/expat/expat.spec
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
Summary: An XML parser library
Name: expat
Version: 2.6.4
Release: 4%{?dist}
Release: 5%{?dist}
License: MIT
Vendor: Microsoft Corporation
Distribution: Azure Linux
Expand All @@ -13,6 +13,7 @@ Patch0: CVE-2024-8176.patch
Patch1: CVE-2025-59375.patch
Patch2: CVE-2026-24515.patch
Patch3: CVE-2026-25210.patch
Patch4: Stop-updating-event-pointer-on-exit-for-reentry.patch
Requires: %{name}-libs = %{version}-%{release}

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

%changelog
* Tue Mar 31 2026 Aditya Singh <v-aditysing@microsoft.com> - 2.6.4-5
- Patch to restore event pointer behavior from Expat 2.6.4
- which was changed due to fix for CVE-2024-8176.

* Mon Feb 02 2026 Azure Linux Security Servicing Account <azurelinux-security@microsoft.com> - 2.6.4-4
- Patch for CVE-2026-25210

Expand Down
111 changes: 111 additions & 0 deletions SPECS/perl-XML-Parser/CVE-2006-10002.patch
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
From 31f0c03a1a265f5a5703531628f4b5bc0341d5c8 Mon Sep 17 00:00:00 2001
From: Toddr Bot <toddbot@rinaldo.us>
Date: Mon, 16 Mar 2026 20:55:31 +0000
Subject: [PATCH] Fix buffer overflow in parse_stream when filehandle has :utf8
layer

When a filehandle has a :utf8 PerlIO layer, Perl's read() returns
decoded characters, but SvPV() gives back the UTF-8 byte
representation which can be larger than the pre-allocated XML buffer.
Previously this caused heap corruption (double free / buffer overflow),
and a later workaround (BUFSIZE * 6 + croak) prevented the corruption
but still crashed.

Fix by re-obtaining the expat buffer at the actual byte size when the
read produces more bytes than initially allocated. This handles UTF-8
streams gracefully without wasting memory on an oversized buffer.

Fixes https://github.com/cpan-authors/XML-Parser/issues/64
(migrated from rt.cpan.org #19859)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Signed-off-by: Azure Linux Security Servicing Account <azurelinux-security@microsoft.com>
Upstream-reference: https://github.com/cpan-authors/XML-Parser/commit/6b291f4d260fc124a6ec80382b87a918f372bc6b.patch
---
Expat/Expat.xs | 15 +++++++++++----
t/utf8_stream.t | 40 ++++++++++++++++++++++++++++++++++++++++
2 files changed, 51 insertions(+), 4 deletions(-)
create mode 100644 t/utf8_stream.t

diff --git a/Expat/Expat.xs b/Expat/Expat.xs
index dbad380..356df84 100644
--- a/Expat/Expat.xs
+++ b/Expat/Expat.xs
@@ -343,8 +343,8 @@ parse_stream(XML_Parser parser, SV * ioref)
}
else {
tbuff = newSV(0);
- tsiz = newSViv(BUFSIZE); /* in UTF-8 characters */
- buffsize = BUFSIZE * 6; /* in bytes that encode an UTF-8 string */
+ tsiz = newSViv(BUFSIZE);
+ buffsize = BUFSIZE;
}

while (! done)
@@ -387,8 +387,15 @@ parse_stream(XML_Parser parser, SV * ioref)

tb = SvPV(tbuff, br);
if (br > 0) {
- if (br > buffsize)
- croak("The input buffer is not large enough for read UTF-8 decoded string");
+ if (br > buffsize) {
+ /* The byte count from SvPV can exceed buffsize when the
+ filehandle has a :utf8 layer, since Perl reads buffsize
+ characters but multi-byte UTF-8 chars produce more bytes.
+ Re-obtain the buffer at the required size. */
+ buffer = XML_GetBuffer(parser, br);
+ if (! buffer)
+ croak("Ran out of memory for input buffer");
+ }
Copy(tb, buffer, br, char);
} else
done = 1;
diff --git a/t/utf8_stream.t b/t/utf8_stream.t
new file mode 100644
index 0000000..a7e55f7
--- /dev/null
+++ b/t/utf8_stream.t
@@ -0,0 +1,40 @@
+BEGIN { print "1..2\n"; }
+END { print "not ok 1\n" unless $loaded; }
+use XML::Parser;
+$loaded = 1;
+print "ok 1\n";
+
+################################################################
+# Test parsing from a filehandle with :utf8 layer
+# Regression test for rt.cpan.org #19859 / GitHub issue #64
+# A UTF-8 stream caused buffer overflow because SvPV byte count
+# could exceed the pre-allocated XML_GetBuffer size.
+
+use File::Temp qw(tempfile);
+
+# Create a temp file with UTF-8 XML content containing multi-byte chars
+my ($fh, $tmpfile) = tempfile(UNLINK => 1);
+binmode($fh, ':raw');
+# Write raw UTF-8 bytes: XML with Chinese characters (3 bytes each in UTF-8)
+# U+4E16 U+754C (世界 = "world") repeated to create substantial multi-byte content
+my $body = "\xe4\xb8\x96\xe7\x95\x8c" x 20000; # 120000 bytes / 40000 chars of 3-byte UTF-8
+print $fh qq(<?xml version="1.0" encoding="UTF-8"?>\n<doc>$body</doc>\n);
+close($fh);
+
+my $text = '';
+my $parser = XML::Parser->new(
+ Handlers => {
+ Char => sub { $text .= $_[1]; },
+ }
+);
+
+# Open with :utf8 layer - this is what triggers the bug
+open(my $in, '<:utf8', $tmpfile) or die "Cannot open $tmpfile: $!";
+eval { $parser->parse($in); };
+close($in);
+
+if ($@ eq '' && length($text) > 0) {
+ print "ok 2\n";
+} else {
+ print "not ok 2 # $@\n";
+}
--
2.45.4

Loading
Loading