Skip to content

Commit a6f47eb

Browse files
azurelinux-securityarchana25-msKanishk-Bansal
authored
[AutoPR- Security] Patch expat for CVE-2026-32778, CVE-2026-32777, CVE-2026-32776 [MEDIUM] (#16230)
Co-authored-by: Archana Shettigar <v-shettigara@microsoft.com> Co-authored-by: Kanishk Bansal <103916909+Kanishk-Bansal@users.noreply.github.com>
1 parent 41bec98 commit a6f47eb

8 files changed

Lines changed: 346 additions & 15 deletions

File tree

SPECS/expat/CVE-2026-32776.patch

Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
From bfc23e1f8e08527a455ec4fdd47f410058ff2a7d Mon Sep 17 00:00:00 2001
2+
From: Francesco Bertolaccini <francesco.bertolaccini@trailofbits.com>
3+
Date: Tue, 3 Mar 2026 16:41:43 +0100
4+
Subject: [PATCH] Fix NULL function-pointer dereference for empty external
5+
parameter entities
6+
7+
When an external parameter entity with empty text is referenced inside
8+
an entity declaration value, the sub-parser created to handle it receives
9+
0 bytes of input. Processing enters entityValueInitProcessor which calls
10+
storeEntityValue() with the parser's encoding; since no bytes were ever
11+
processed, encoding detection has not yet occurred and the encoding is
12+
still the initial probing encoding set up by XmlInitEncoding(). That
13+
encoding only populates scanners[] (for prolog and content), not
14+
literalScanners[]. XmlEntityValueTok() calls through
15+
literalScanners[XML_ENTITY_VALUE_LITERAL] which is NULL, causing a
16+
SEGV.
17+
18+
Skip the tokenization loop entirely when entityTextPtr >= entityTextEnd,
19+
and initialize the `next` pointer before the early exit so that callers
20+
(callStoreEntityValue) receive a valid value through nextPtr.
21+
22+
Signed-off-by: Azure Linux Security Servicing Account <azurelinux-security@microsoft.com>
23+
Upstream-reference: https://github.com/libexpat/libexpat/pull/1158.patch
24+
---
25+
lib/xmlparse.c | 9 ++++++++-
26+
tests/basic_tests.c | 19 +++++++++++++++++++
27+
2 files changed, 27 insertions(+), 1 deletion(-)
28+
29+
diff --git a/lib/xmlparse.c b/lib/xmlparse.c
30+
index 0bf913c..d126f5b 100644
31+
--- a/lib/xmlparse.c
32+
+++ b/lib/xmlparse.c
33+
@@ -6765,7 +6765,14 @@ storeEntityValue(XML_Parser parser, const ENCODING *enc,
34+
return XML_ERROR_NO_MEMORY;
35+
}
36+
37+
- const char *next;
38+
+ const char *next = entityTextPtr;
39+
+
40+
+ /* Nothing to tokenize. */
41+
+ if (entityTextPtr >= entityTextEnd) {
42+
+ result = XML_ERROR_NONE;
43+
+ goto endEntityValue;
44+
+ }
45+
+
46+
for (;;) {
47+
next
48+
= entityTextPtr; /* XmlEntityValueTok doesn't always set the last arg */
49+
diff --git a/tests/basic_tests.c b/tests/basic_tests.c
50+
index 2db2a76..cfb09f3 100644
51+
--- a/tests/basic_tests.c
52+
+++ b/tests/basic_tests.c
53+
@@ -6123,6 +6123,24 @@ START_TEST(test_varying_buffer_fills) {
54+
}
55+
END_TEST
56+
57+
+START_TEST(test_empty_ext_param_entity_in_value) {
58+
+ const char *text = "<!DOCTYPE r SYSTEM \"ext.dtd\"><r/>";
59+
+ ExtOption options[] = {
60+
+ {XCS("ext.dtd"), "<!ENTITY % pe SYSTEM \"empty\">"
61+
+ "<!ENTITY ge \"%pe;\">"},
62+
+ {XCS("empty"), ""},
63+
+ {NULL, NULL},
64+
+ };
65+
+
66+
+ XML_SetParamEntityParsing(g_parser, XML_PARAM_ENTITY_PARSING_ALWAYS);
67+
+ XML_SetExternalEntityRefHandler(g_parser, external_entity_optioner);
68+
+ XML_SetUserData(g_parser, options);
69+
+ if (_XML_Parse_SINGLE_BYTES(g_parser, text, (int)strlen(text), XML_TRUE)
70+
+ == XML_STATUS_ERROR)
71+
+ xml_failure(g_parser);
72+
+}
73+
+END_TEST
74+
+
75+
void
76+
make_basic_test_case(Suite *s) {
77+
TCase *tc_basic = tcase_create("basic tests");
78+
@@ -6368,6 +6386,7 @@ make_basic_test_case(Suite *s) {
79+
tcase_add_test(tc_basic, test_empty_element_abort);
80+
tcase_add_test__ifdef_xml_dtd(tc_basic,
81+
test_pool_integrity_with_unfinished_attr);
82+
+ tcase_add_test__ifdef_xml_dtd(tc_basic, test_empty_ext_param_entity_in_value);
83+
tcase_add_test__if_xml_ge(tc_basic, test_entity_ref_no_elements);
84+
tcase_add_test__if_xml_ge(tc_basic, test_deep_nested_entity);
85+
tcase_add_test__if_xml_ge(tc_basic, test_deep_nested_attribute_entity);
86+
--
87+
2.45.4
88+

SPECS/expat/CVE-2026-32777.patch

Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
From f86ec6b7d5a7280caeb68d5432fd45ee8f41f274 Mon Sep 17 00:00:00 2001
2+
From: Sebastian Pipping <sebastian@pipping.org>
3+
Date: Sun, 1 Mar 2026 20:16:13 +0100
4+
Subject: [PATCH 1/2] lib: Reject XML_TOK_INSTANCE_START infinite loop in
5+
entityValueProcessor
6+
7+
.. that OSS-Fuzz/ClusterFuzz uncovered
8+
9+
Upstream-reference: https://github.com/libexpat/libexpat/pull/1162.patch
10+
---
11+
lib/xmlparse.c | 11 ++++++++++-
12+
tests/misc_tests.c | 30 ++++++++++++++++++++++
13+
2 file changed, 40 insertions(+), 1 deletion(-)
14+
15+
diff --git a/lib/xmlparse.c b/lib/xmlparse.c
16+
index d126f5b..88361a7 100644
17+
--- a/lib/xmlparse.c
18+
+++ b/lib/xmlparse.c
19+
@@ -5068,7 +5068,7 @@ entityValueInitProcessor(XML_Parser parser, const char *s, const char *end,
20+
}
21+
/* If we get this token, we have the start of what might be a
22+
normal tag, but not a declaration (i.e. it doesn't begin with
23+
- "<!"). In a DTD context, that isn't legal.
24+
+ "<!" or "<?"). In a DTD context, that isn't legal.
25+
*/
26+
else if (tok == XML_TOK_INSTANCE_START) {
27+
*nextPtr = next;
28+
@@ -5157,6 +5157,15 @@ entityValueProcessor(XML_Parser parser, const char *s, const char *end,
29+
/* found end of entity value - can store it now */
30+
return storeEntityValue(parser, enc, s, end, XML_ACCOUNT_DIRECT, NULL);
31+
}
32+
+ /* If we get this token, we have the start of what might be a
33+
+ normal tag, but not a declaration (i.e. it doesn't begin with
34+
+ "<!" or "<?"). In a DTD context, that isn't legal.
35+
+ */
36+
+ else if (tok == XML_TOK_INSTANCE_START) {
37+
+ *nextPtr = next;
38+
+ return XML_ERROR_SYNTAX;
39+
+ }
40+
+
41+
start = next;
42+
}
43+
}
44+
diff --git a/tests/misc_tests.c b/tests/misc_tests.c
45+
index f9a78f666..f506dbcad 100644
46+
--- a/tests/misc_tests.c
47+
+++ b/tests/misc_tests.c
48+
@@ -561,6 +561,35 @@ START_TEST(test_renter_loop_finite_content) {
49+
}
50+
END_TEST
51+
52+
+START_TEST(test_misc_no_infinite_loop_issue_1161) {
53+
+ XML_Parser parser = XML_ParserCreate(NULL);
54+
+
55+
+ const char *text = "<!DOCTYPE d SYSTEM 'secondary.txt'>";
56+
+
57+
+ struct ExtOption options[] = {
58+
+ {XCS("secondary.txt"),
59+
+ "<!ENTITY % p SYSTEM 'tertiary.txt'><!ENTITY g '%p;'>"},
60+
+ {XCS("tertiary.txt"), "<?xml version='1.0'?><a"},
61+
+ {NULL, NULL},
62+
+ };
63+
+
64+
+ XML_SetUserData(parser, options);
65+
+ XML_SetParamEntityParsing(parser, XML_PARAM_ENTITY_PARSING_ALWAYS);
66+
+ XML_SetExternalEntityRefHandler(parser, external_entity_optioner);
67+
+
68+
+ assert_true(_XML_Parse_SINGLE_BYTES(parser, text, (int)strlen(text), XML_TRUE)
69+
+ == XML_STATUS_ERROR);
70+
+
71+
+#if defined(XML_DTD)
72+
+ assert_true(XML_GetErrorCode(parser) == XML_ERROR_EXTERNAL_ENTITY_HANDLING);
73+
+#else
74+
+ assert_true(XML_GetErrorCode(parser) == XML_ERROR_NO_ELEMENTS);
75+
+#endif
76+
+
77+
+ XML_ParserFree(parser);
78+
+}
79+
+END_TEST
80+
+
81+
void
82+
make_miscellaneous_test_case(Suite *s) {
83+
TCase *tc_misc = tcase_create("miscellaneous tests");
84+
@@ -588,4 +617,5 @@ make_miscellaneous_test_case(Suite *s) {
85+
tcase_add_test(tc_misc, test_misc_resumeparser_not_crashing);
86+
tcase_add_test(tc_misc, test_misc_stopparser_rejects_unstarted_parser);
87+
tcase_add_test__if_xml_ge(tc_misc, test_renter_loop_finite_content);
88+
+ tcase_add_test(tc_misc, test_misc_no_infinite_loop_issue_1161);
89+
}
90+
--
91+
2.45.4
92+

SPECS/expat/CVE-2026-32778.patch

Lines changed: 145 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,145 @@
1+
From a53914fd5ca23af40d48ef248cdb42fb22ae7dd1 Mon Sep 17 00:00:00 2001
2+
From: laserbear <10689391+Laserbear@users.noreply.github.com>
3+
Date: Sun, 8 Mar 2026 17:28:06 -0700
4+
Subject: [PATCH 1/2] copy prefix name to pool before lookup
5+
6+
.. so that we cannot end up with a zombie PREFIX in the pool
7+
that has NULL for a name.
8+
9+
Co-authored-by: Sebastian Pipping <sebastian@pipping.org>
10+
---
11+
lib/xmlparse.c | 43 +++++++++++++++++++++++++++++++++++--------
12+
1 file changed, 35 insertions(+), 8 deletions(-)
13+
14+
diff --git a/lib/xmlparse.c b/lib/xmlparse.c
15+
index 88361a7..69027f6 100644
16+
--- a/lib/xmlparse.c
17+
+++ b/lib/xmlparse.c
18+
@@ -590,6 +590,8 @@ static XML_Char *poolStoreString(STRING_POOL *pool, const ENCODING *enc,
19+
static XML_Bool FASTCALL poolGrow(STRING_POOL *pool);
20+
static const XML_Char *FASTCALL poolCopyString(STRING_POOL *pool,
21+
const XML_Char *s);
22+
+static const XML_Char *FASTCALL poolCopyStringNoFinish(STRING_POOL *pool,
23+
+ const XML_Char *s);
24+
static const XML_Char *poolCopyStringN(STRING_POOL *pool, const XML_Char *s,
25+
int n);
26+
static const XML_Char *FASTCALL poolAppendString(STRING_POOL *pool,
27+
@@ -7431,16 +7433,24 @@ setContext(XML_Parser parser, const XML_Char *context) {
28+
else {
29+
if (! poolAppendChar(&parser->m_tempPool, XML_T('\0')))
30+
return XML_FALSE;
31+
- prefix
32+
- = (PREFIX *)lookup(parser, &dtd->prefixes,
33+
- poolStart(&parser->m_tempPool), sizeof(PREFIX));
34+
- if (! prefix)
35+
+ const XML_Char *const prefixName = poolCopyStringNoFinish(
36+
+ &dtd->pool, poolStart(&parser->m_tempPool));
37+
+ if (! prefixName) {
38+
return XML_FALSE;
39+
- if (prefix->name == poolStart(&parser->m_tempPool)) {
40+
- prefix->name = poolCopyString(&dtd->pool, prefix->name);
41+
- if (! prefix->name)
42+
- return XML_FALSE;
43+
}
44+
+
45+
+ prefix = (PREFIX *)lookup(parser, &dtd->prefixes, prefixName,
46+
+ sizeof(PREFIX));
47+
+
48+
+ const bool prefixNameUsed = prefix && prefix->name == prefixName;
49+
+ if (prefixNameUsed)
50+
+ poolFinish(&dtd->pool);
51+
+ else
52+
+ poolDiscard(&dtd->pool);
53+
+
54+
+ if (! prefix)
55+
+ return XML_FALSE;
56+
+
57+
poolDiscard(&parser->m_tempPool);
58+
}
59+
for (context = s + 1; *context != CONTEXT_SEP && *context != XML_T('\0');
60+
@@ -8029,6 +8039,23 @@ poolCopyString(STRING_POOL *pool, const XML_Char *s) {
61+
return s;
62+
}
63+
64+
+// A version of `poolCopyString` that does not call `poolFinish`
65+
+// and reverts any partial advancement upon failure.
66+
+static const XML_Char *FASTCALL
67+
+poolCopyStringNoFinish(STRING_POOL *pool, const XML_Char *s) {
68+
+ const XML_Char *const original = s;
69+
+ do {
70+
+ if (! poolAppendChar(pool, *s)) {
71+
+ // Revert any previously successful advancement
72+
+ const ptrdiff_t advancedBy = s - original;
73+
+ if (advancedBy > 0)
74+
+ pool->ptr -= advancedBy;
75+
+ return NULL;
76+
+ }
77+
+ } while (*s++);
78+
+ return pool->start;
79+
+}
80+
+
81+
static const XML_Char *
82+
poolCopyStringN(STRING_POOL *pool, const XML_Char *s, int n) {
83+
if (! pool->ptr && ! poolGrow(pool)) {
84+
--
85+
2.45.4
86+
87+
88+
From 777bd1a1b2196ac14cbc5efb71a3078583c8dfb2 Mon Sep 17 00:00:00 2001
89+
From: laserbear <10689391+Laserbear@users.noreply.github.com>
90+
Date: Sun, 8 Mar 2026 17:28:06 -0700
91+
Subject: [PATCH 2/2] test that we do not end up with a zombie PREFIX in the
92+
pool
93+
94+
Signed-off-by: Azure Linux Security Servicing Account <azurelinux-security@microsoft.com>
95+
Upstream-reference: https://github.com/libexpat/libexpat/pull/1163.patch
96+
---
97+
tests/nsalloc_tests.c | 27 +++++++++++++++++++++++++++
98+
1 file changed, 27 insertions(+)
99+
100+
diff --git a/tests/nsalloc_tests.c b/tests/nsalloc_tests.c
101+
index a8f5718..d284a58 100644
102+
--- a/tests/nsalloc_tests.c
103+
+++ b/tests/nsalloc_tests.c
104+
@@ -1505,6 +1505,32 @@ START_TEST(test_nsalloc_prefixed_element) {
105+
}
106+
END_TEST
107+
108+
+/* Verify that retry after OOM in setContext() does not crash.
109+
+ */
110+
+START_TEST(test_nsalloc_setContext_zombie) {
111+
+ const char *text = "<doc>Hello</doc>";
112+
+ unsigned int i;
113+
+ const unsigned int max_alloc_count = 30;
114+
+
115+
+ for (i = 0; i < max_alloc_count; i++) {
116+
+ g_allocation_count = (int)i;
117+
+ if (XML_Parse(g_parser, text, (int)strlen(text), XML_TRUE)
118+
+ != XML_STATUS_ERROR)
119+
+ break;
120+
+ /* Retry on the same parser — must not crash */
121+
+ g_allocation_count = ALLOC_ALWAYS_SUCCEED;
122+
+ XML_Parse(g_parser, text, (int)strlen(text), XML_TRUE);
123+
+
124+
+ nsalloc_teardown();
125+
+ nsalloc_setup();
126+
+ }
127+
+ if (i == 0)
128+
+ fail("Parsing worked despite failing allocations");
129+
+ else if (i == max_alloc_count)
130+
+ fail("Parsing failed even at maximum allocation count");
131+
+}
132+
+END_TEST
133+
+
134+
void
135+
make_nsalloc_test_case(Suite *s) {
136+
TCase *tc_nsalloc = tcase_create("namespace allocation tests");
137+
@@ -1539,4 +1565,5 @@ make_nsalloc_test_case(Suite *s) {
138+
tcase_add_test__if_xml_ge(tc_nsalloc, test_nsalloc_long_default_in_ext);
139+
tcase_add_test(tc_nsalloc, test_nsalloc_long_systemid_in_ext);
140+
tcase_add_test(tc_nsalloc, test_nsalloc_prefixed_element);
141+
+ tcase_add_test(tc_nsalloc, test_nsalloc_setContext_zombie);
142+
}
143+
--
144+
2.45.4
145+

SPECS/expat/expat.spec

Lines changed: 7 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,9 @@ 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: CVE-2026-32776.patch
17+
Patch5: CVE-2026-32777.patch
18+
Patch6: CVE-2026-32778.patch
1619
Requires: %{name}-libs = %{version}-%{release}
1720

1821
%description
@@ -70,6 +73,9 @@ rm -rf %{buildroot}/%{_docdir}/%{name}
7073
%{_libdir}/libexpat.so.1*
7174

7275
%changelog
76+
* Wed Mar 18 2026 Azure Linux Security Servicing Account <azurelinux-security@microsoft.com> - 2.6.4-5
77+
- Patch for CVE-2026-32778, CVE-2026-32777, CVE-2026-32776
78+
7379
* Mon Feb 02 2026 Azure Linux Security Servicing Account <azurelinux-security@microsoft.com> - 2.6.4-4
7480
- Patch for CVE-2026-25210
7581

toolkit/resources/manifests/package/pkggen_core_aarch64.txt

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -99,9 +99,9 @@ elfutils-libelf-0.189-6.azl3.aarch64.rpm
9999
elfutils-libelf-devel-0.189-6.azl3.aarch64.rpm
100100
elfutils-libelf-devel-static-0.189-6.azl3.aarch64.rpm
101101
elfutils-libelf-lang-0.189-6.azl3.aarch64.rpm
102-
expat-2.6.4-4.azl3.aarch64.rpm
103-
expat-devel-2.6.4-4.azl3.aarch64.rpm
104-
expat-libs-2.6.4-4.azl3.aarch64.rpm
102+
expat-2.6.4-5.azl3.aarch64.rpm
103+
expat-devel-2.6.4-5.azl3.aarch64.rpm
104+
expat-libs-2.6.4-5.azl3.aarch64.rpm
105105
libpipeline-1.5.7-1.azl3.aarch64.rpm
106106
libpipeline-devel-1.5.7-1.azl3.aarch64.rpm
107107
gdbm-1.23-1.azl3.aarch64.rpm

toolkit/resources/manifests/package/pkggen_core_x86_64.txt

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -99,9 +99,9 @@ elfutils-libelf-0.189-6.azl3.x86_64.rpm
9999
elfutils-libelf-devel-0.189-6.azl3.x86_64.rpm
100100
elfutils-libelf-devel-static-0.189-6.azl3.x86_64.rpm
101101
elfutils-libelf-lang-0.189-6.azl3.x86_64.rpm
102-
expat-2.6.4-4.azl3.x86_64.rpm
103-
expat-devel-2.6.4-4.azl3.x86_64.rpm
104-
expat-libs-2.6.4-4.azl3.x86_64.rpm
102+
expat-2.6.4-5.azl3.x86_64.rpm
103+
expat-devel-2.6.4-5.azl3.x86_64.rpm
104+
expat-libs-2.6.4-5.azl3.x86_64.rpm
105105
libpipeline-1.5.7-1.azl3.x86_64.rpm
106106
libpipeline-devel-1.5.7-1.azl3.x86_64.rpm
107107
gdbm-1.23-1.azl3.x86_64.rpm

toolkit/resources/manifests/package/toolchain_aarch64.txt

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -94,10 +94,10 @@ elfutils-libelf-0.189-6.azl3.aarch64.rpm
9494
elfutils-libelf-devel-0.189-6.azl3.aarch64.rpm
9595
elfutils-libelf-devel-static-0.189-6.azl3.aarch64.rpm
9696
elfutils-libelf-lang-0.189-6.azl3.aarch64.rpm
97-
expat-2.6.4-4.azl3.aarch64.rpm
98-
expat-debuginfo-2.6.4-4.azl3.aarch64.rpm
99-
expat-devel-2.6.4-4.azl3.aarch64.rpm
100-
expat-libs-2.6.4-4.azl3.aarch64.rpm
97+
expat-2.6.4-5.azl3.aarch64.rpm
98+
expat-debuginfo-2.6.4-5.azl3.aarch64.rpm
99+
expat-devel-2.6.4-5.azl3.aarch64.rpm
100+
expat-libs-2.6.4-5.azl3.aarch64.rpm
101101
file-5.45-1.azl3.aarch64.rpm
102102
file-debuginfo-5.45-1.azl3.aarch64.rpm
103103
file-devel-5.45-1.azl3.aarch64.rpm

0 commit comments

Comments
 (0)