Fix CVE-2026-56132: out-of-bound scaffolding index store in doProlog
Backport upstream PR #1272 (5 commits) to fix CVE-2026-56132, an out-of-bound scaffolding index store in doProlog. The fix introduces independent tracking of scaffIndex allocation size and adds bounds checking at use sites, preventing out-of-bounds writes during XML DTD parsing. CVE: CVE-2026-56132 Upstream patches: - https://github.com/libexpat/libexpat/pull/1272.patch Resolves: RHEL-221012 This commit was backported by Ymir, a Red Hat Enterprise Linux software maintenance AI agent. Assisted-by: Ymir
This commit is contained in:
parent
4eb28dada0
commit
5b5ef91cf2
288
expat-2.5.0-CVE-2026-56132.patch
Normal file
288
expat-2.5.0-CVE-2026-56132.patch
Normal file
@ -0,0 +1,288 @@
|
||||
From b5b73d23072fd277b3816b45c027238260467947 Mon Sep 17 00:00:00 2001
|
||||
From: Matthew Fernandez <matthew.fernandez@gmail.com>
|
||||
Date: Thu, 4 Jun 2026 17:01:02 -0700
|
||||
Subject: [PATCH 1/5] lib: Remove reuse of `m_groupSize` to count
|
||||
`m_scaffIndex` allocation
|
||||
|
||||
The sizes of the two arrays `m_groupConnector` and `scaffIndex` need to
|
||||
vary independently. This change is a step towards allowing this.
|
||||
|
||||
Anthropic: ANT-2026-00037
|
||||
Anthropic: ANT-2026-03621
|
||||
Anthropic: ANT-2026-03867
|
||||
Co-authored-by: Alessandro Gario <alessandro.gario@trailofbits.com>
|
||||
---
|
||||
expat/lib/xmlparse.c | 6 ++++++
|
||||
1 file changed, 6 insertions(+)
|
||||
|
||||
diff --git a/expat/lib/xmlparse.c b/expat/lib/xmlparse.c
|
||||
index d7d815e8..3b5e90b1 100644
|
||||
--- a/expat/lib/xmlparse.c
|
||||
+++ b/expat/lib/xmlparse.c
|
||||
@@ -398,6 +398,7 @@ typedef struct {
|
||||
unsigned scaffCount;
|
||||
int scaffLevel;
|
||||
int *scaffIndex;
|
||||
+ size_t scaffIndexSize;
|
||||
} DTD;
|
||||
|
||||
enum EntityType {
|
||||
@@ -5812,6 +5813,7 @@ doProlog(XML_Parser parser, const ENCODING *enc, const char *s, const char *end,
|
||||
if (new_scaff_index == NULL)
|
||||
return XML_ERROR_NO_MEMORY;
|
||||
dtd->scaffIndex = new_scaff_index;
|
||||
+ dtd->scaffIndexSize = parser->m_groupSize;
|
||||
}
|
||||
} else {
|
||||
parser->m_groupConnector
|
||||
@@ -7358,6 +7360,7 @@ dtdCreate(XML_Parser parser) {
|
||||
|
||||
p->in_eldecl = XML_FALSE;
|
||||
p->scaffIndex = NULL;
|
||||
+ p->scaffIndexSize = 0;
|
||||
p->scaffold = NULL;
|
||||
p->scaffLevel = 0;
|
||||
p->scaffSize = 0;
|
||||
@@ -7399,6 +7402,7 @@ dtdReset(DTD *p, XML_Parser parser) {
|
||||
|
||||
FREE(parser, p->scaffIndex);
|
||||
p->scaffIndex = NULL;
|
||||
+ p->scaffIndexSize = 0;
|
||||
FREE(parser, p->scaffold);
|
||||
p->scaffold = NULL;
|
||||
|
||||
@@ -7586,6 +7590,7 @@ dtdCopy(XML_Parser oldParser, DTD *newDtd, const DTD *oldDtd,
|
||||
newDtd->scaffSize = oldDtd->scaffSize;
|
||||
newDtd->scaffLevel = oldDtd->scaffLevel;
|
||||
newDtd->scaffIndex = oldDtd->scaffIndex;
|
||||
+ newDtd->scaffIndexSize = oldDtd->scaffIndexSize;
|
||||
|
||||
return 1;
|
||||
} /* End dtdCopy */
|
||||
@@ -8090,6 +8095,7 @@ nextScaffoldPart(XML_Parser parser) {
|
||||
dtd->scaffIndex = (int *)MALLOC(parser, parser->m_groupSize * sizeof(int));
|
||||
if (! dtd->scaffIndex)
|
||||
return -1;
|
||||
+ dtd->scaffIndexSize = parser->m_groupSize;
|
||||
dtd->scaffIndex[0] = 0;
|
||||
}
|
||||
|
||||
|
||||
From af2f86e32624be829ddc0ce7168d18f921bdef4e Mon Sep 17 00:00:00 2001
|
||||
From: Matthew Fernandez <matthew.fernandez@gmail.com>
|
||||
Date: Thu, 4 Jun 2026 17:01:02 -0700
|
||||
Subject: [PATCH 2/5] lib: doProlog: Fix out-of-bound scaffolding index store
|
||||
MIME-Version: 1.0
|
||||
Content-Type: text/plain; charset=UTF-8
|
||||
Content-Transfer-Encoding: 8bit
|
||||
|
||||
The scaffold backing array is reallocated using the caller parser’s
|
||||
per-parser `m_groupSize`, but the DTD struct (which carries
|
||||
`scaffIndex`) is shared between a parent parser and any external
|
||||
parameter-entity sub-parser created via
|
||||
`XML_ExternalEntityParserCreate(parent, NULL, …)`. A sub-parser whose
|
||||
group nesting is shallower than the parent’s can `REALLOC` the shared
|
||||
`scaffIndex` down to its own size; when the parent resumes and parses a
|
||||
deeper element content model, its bounds check passes (its private
|
||||
`m_groupSize` is still large enough), the doubling-grow path is skipped,
|
||||
and the next write lands past the shrunken buffer.
|
||||
|
||||
Anthropic: ANT-2026-00037
|
||||
Anthropic: ANT-2026-03621
|
||||
Anthropic: ANT-2026-03867
|
||||
Co-authored-by: Alessandro Gario <alessandro.gario@trailofbits.com>
|
||||
Reported-by: Trail of Bits, in collaboration with Anthropic
|
||||
---
|
||||
expat/lib/xmlparse.c | 15 +++++++++++++++
|
||||
1 file changed, 15 insertions(+)
|
||||
|
||||
diff --git a/expat/lib/xmlparse.c b/expat/lib/xmlparse.c
|
||||
index 3b5e90b1..317b08e0 100644
|
||||
--- a/expat/lib/xmlparse.c
|
||||
+++ b/expat/lib/xmlparse.c
|
||||
@@ -5830,6 +5830,21 @@ doProlog(XML_Parser parser, const ENCODING *enc, const char *s, const char *end,
|
||||
if (myindex < 0)
|
||||
return XML_ERROR_NO_MEMORY;
|
||||
assert(dtd->scaffIndex != NULL);
|
||||
+ if ((size_t)dtd->scaffLevel >= dtd->scaffIndexSize) {
|
||||
+ /* Detect and prevent integer overflow */
|
||||
+ if (dtd->scaffIndexSize > SIZE_MAX / 2 / sizeof(int)) {
|
||||
+ return XML_ERROR_NO_MEMORY;
|
||||
+ }
|
||||
+ assert(dtd->scaffIndexSize > 0);
|
||||
+ const size_t new_size = dtd->scaffIndexSize * 2;
|
||||
+ int *const new_scaff_index
|
||||
+ = REALLOC(parser, dtd->scaffIndex, new_size * sizeof(int));
|
||||
+ if (new_scaff_index == NULL) {
|
||||
+ return XML_ERROR_NO_MEMORY;
|
||||
+ }
|
||||
+ dtd->scaffIndex = new_scaff_index;
|
||||
+ dtd->scaffIndexSize = new_size;
|
||||
+ }
|
||||
dtd->scaffIndex[dtd->scaffLevel] = myindex;
|
||||
dtd->scaffLevel++;
|
||||
dtd->scaffold[myindex].type = XML_CTYPE_SEQ;
|
||||
|
||||
From 8b1debf703fadb5e11074f98f33a8bd10b561f3e Mon Sep 17 00:00:00 2001
|
||||
From: Matthew Fernandez <matthew.fernandez@gmail.com>
|
||||
Date: Thu, 4 Jun 2026 17:01:02 -0700
|
||||
Subject: [PATCH 3/5] tests: Add a test case for scaffolding array limits in
|
||||
shared DTDs
|
||||
|
||||
This test case provokes the bug fixed in the previous commit.
|
||||
|
||||
Anthropic: ANT-2026-00037
|
||||
Anthropic: ANT-2026-03621
|
||||
Anthropic: ANT-2026-03867
|
||||
Co-authored-by: Alessandro Gario <alessandro.gario@trailofbits.com>
|
||||
Reported-by: Trail of Bits, in collaboration with Anthropic
|
||||
---
|
||||
expat/tests/runtests.c | 30 ++++++++++++++++++++++++++++++
|
||||
1 file changed, 30 insertions(+)
|
||||
|
||||
diff --git a/expat/tests/runtests.c b/expat/tests/runtests.c
|
||||
index 659ed030..c760733d 100644
|
||||
--- a/expat/tests/runtests.c
|
||||
+++ b/expat/tests/runtests.c
|
||||
@@ -5681,6 +5681,34 @@ START_TEST(test_skipped_external_entity) {
|
||||
}
|
||||
END_TEST
|
||||
|
||||
+START_TEST(test_scaff_index_shared_across_external_entity_parser) {
|
||||
+ const char text[]
|
||||
+ = "<!DOCTYPE doc [\n"
|
||||
+ "<!ELEMENT a "
|
||||
+ "((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((b))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))>\n"
|
||||
+ "<!ENTITY % e SYSTEM 'ext'>\n"
|
||||
+ "%e;\n"
|
||||
+ "<!ELEMENT c "
|
||||
+ "(((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((d)))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))>\n"
|
||||
+ "]>\n"
|
||||
+ "<doc/>";
|
||||
+ ExtOption options[]
|
||||
+ = {{XCS("ext"),
|
||||
+ "<!ELEMENT x "
|
||||
+ "((((((((((((((((((((((((((((((((y))))))))))))))))))))))))))))))))>"},
|
||||
+ {NULL, NULL}};
|
||||
+
|
||||
+ XML_SetParamEntityParsing(g_parser, XML_PARAM_ENTITY_PARSING_ALWAYS);
|
||||
+ XML_SetUserData(g_parser, options);
|
||||
+ XML_SetExternalEntityRefHandler(g_parser, external_entity_optioner);
|
||||
+ XML_SetElementDeclHandler(g_parser, dummy_element_decl_handler);
|
||||
+
|
||||
+ if (_XML_Parse_SINGLE_BYTES(g_parser, text, (int)strlen(text), XML_TRUE)
|
||||
+ == XML_STATUS_ERROR)
|
||||
+ xml_failure(g_parser);
|
||||
+}
|
||||
+END_TEST
|
||||
+
|
||||
/* Test a different form of unknown external entity */
|
||||
typedef struct ext_hdlr_data {
|
||||
const char *parse_text;
|
||||
@@ -13988,6 +14016,8 @@ make_suite(void) {
|
||||
tcase_add_test(tc_basic, test_trailing_cr_in_att_value);
|
||||
tcase_add_test(tc_basic, test_standalone_internal_entity);
|
||||
tcase_add_test(tc_basic, test_skipped_external_entity);
|
||||
+ tcase_add_test__ifdef_xml_dtd(
|
||||
+ tc_basic, test_scaff_index_shared_across_external_entity_parser);
|
||||
tcase_add_test(tc_basic, test_skipped_null_loaded_ext_entity);
|
||||
tcase_add_test(tc_basic, test_skipped_unloaded_ext_entity);
|
||||
tcase_add_test__ifdef_xml_dtd(tc_basic, test_param_entity_with_trailing_cr);
|
||||
|
||||
From 4ab78126be566310e293adef807a9b919e90f8df Mon Sep 17 00:00:00 2001
|
||||
From: Matthew Fernandez <matthew.fernandez@gmail.com>
|
||||
Date: Thu, 4 Jun 2026 17:01:02 -0700
|
||||
Subject: [PATCH 4/5] lib: Remove unnecessary `scaffIndex` expansion
|
||||
|
||||
Following the previous changes, all locations that append entries to
|
||||
`scaffIndex` handle expanding the array if it is not already large
|
||||
enough. So this extra expansion code is no longer necessary. In some
|
||||
cases such as processing siblings with alternating scaffolding counts,
|
||||
this logic would actually _shrink_ the array only to then later
|
||||
re-expand it.
|
||||
|
||||
Anthropic: ANT-2026-00037
|
||||
Anthropic: ANT-2026-03621
|
||||
Anthropic: ANT-2026-03867
|
||||
Co-authored-by: Alessandro Gario <alessandro.gario@trailofbits.com>
|
||||
---
|
||||
expat/lib/xmlparse.c | 18 ------------------
|
||||
1 file changed, 18 deletions(-)
|
||||
|
||||
diff --git a/expat/lib/xmlparse.c b/expat/lib/xmlparse.c
|
||||
index 317b08e0..80fcb968 100644
|
||||
--- a/expat/lib/xmlparse.c
|
||||
+++ b/expat/lib/xmlparse.c
|
||||
@@ -5797,24 +5797,6 @@ doProlog(XML_Parser parser, const ENCODING *enc, const char *s, const char *end,
|
||||
parser->m_groupConnector = new_connector;
|
||||
}
|
||||
|
||||
- if (dtd->scaffIndex) {
|
||||
- /* Detect and prevent integer overflow.
|
||||
- * The preprocessor guard addresses the "always false" warning
|
||||
- * from -Wtype-limits on platforms where
|
||||
- * sizeof(unsigned int) < sizeof(size_t), e.g. on x86_64. */
|
||||
-#if UINT_MAX >= SIZE_MAX
|
||||
- if (parser->m_groupSize > (size_t)(-1) / sizeof(int)) {
|
||||
- return XML_ERROR_NO_MEMORY;
|
||||
- }
|
||||
-#endif
|
||||
-
|
||||
- int *const new_scaff_index = (int *)REALLOC(
|
||||
- parser, dtd->scaffIndex, parser->m_groupSize * sizeof(int));
|
||||
- if (new_scaff_index == NULL)
|
||||
- return XML_ERROR_NO_MEMORY;
|
||||
- dtd->scaffIndex = new_scaff_index;
|
||||
- dtd->scaffIndexSize = parser->m_groupSize;
|
||||
- }
|
||||
} else {
|
||||
parser->m_groupConnector
|
||||
= (char *)MALLOC(parser, parser->m_groupSize = 32);
|
||||
|
||||
From 9a4202e63b9a89e2e1ec14996934ff98697c6721 Mon Sep 17 00:00:00 2001
|
||||
From: Matthew Fernandez <matthew.fernandez@gmail.com>
|
||||
Date: Thu, 4 Jun 2026 17:01:02 -0700
|
||||
Subject: [PATCH 5/5] lib: Remove indented scoping of `new_connector` local
|
||||
|
||||
Following the previous change, the lifetime of `new_connector` as
|
||||
constrained by this introduced scope was identical to the parent scope.
|
||||
---
|
||||
expat/lib/xmlparse.c | 23 ++++++++++-------------
|
||||
1 file changed, 10 insertions(+), 13 deletions(-)
|
||||
|
||||
diff --git a/expat/lib/xmlparse.c b/expat/lib/xmlparse.c
|
||||
index 80fcb968..5c255c00 100644
|
||||
--- a/expat/lib/xmlparse.c
|
||||
+++ b/expat/lib/xmlparse.c
|
||||
@@ -5782,21 +5782,18 @@ doProlog(XML_Parser parser, const ENCODING *enc, const char *s, const char *end,
|
||||
case XML_ROLE_GROUP_OPEN:
|
||||
if (parser->m_prologState.level >= parser->m_groupSize) {
|
||||
if (parser->m_groupSize) {
|
||||
- {
|
||||
- /* Detect and prevent integer overflow */
|
||||
- if (parser->m_groupSize > (unsigned int)(-1) / 2u) {
|
||||
- return XML_ERROR_NO_MEMORY;
|
||||
- }
|
||||
-
|
||||
- char *const new_connector = (char *)REALLOC(
|
||||
- parser, parser->m_groupConnector, parser->m_groupSize *= 2);
|
||||
- if (new_connector == NULL) {
|
||||
- parser->m_groupSize /= 2;
|
||||
- return XML_ERROR_NO_MEMORY;
|
||||
- }
|
||||
- parser->m_groupConnector = new_connector;
|
||||
+ /* Detect and prevent integer overflow */
|
||||
+ if (parser->m_groupSize > (unsigned int)(-1) / 2u) {
|
||||
+ return XML_ERROR_NO_MEMORY;
|
||||
}
|
||||
|
||||
+ char *const new_connector = (char *)REALLOC(
|
||||
+ parser, parser->m_groupConnector, parser->m_groupSize *= 2);
|
||||
+ if (new_connector == NULL) {
|
||||
+ parser->m_groupSize /= 2;
|
||||
+ return XML_ERROR_NO_MEMORY;
|
||||
+ }
|
||||
+ parser->m_groupConnector = new_connector;
|
||||
} else {
|
||||
parser->m_groupConnector
|
||||
= (char *)MALLOC(parser, parser->m_groupSize = 32);
|
||||
10
expat.spec
10
expat.spec
@ -3,7 +3,7 @@
|
||||
Summary: An XML parser library
|
||||
Name: expat
|
||||
Version: %(echo %{unversion} | sed 's/_/./g')
|
||||
Release: 2%{?dist}.1
|
||||
Release: 2%{?dist}.2
|
||||
Source: https://github.com/libexpat/libexpat/archive/R_%{unversion}.tar.gz#/expat-%{version}.tar.gz
|
||||
URL: https://libexpat.github.io/
|
||||
License: MIT
|
||||
@ -31,6 +31,9 @@ Patch8: expat-2.5.0-CVE-2026-45186.patch
|
||||
# https://issues.redhat.com/browse/RHEL-220979
|
||||
# https://github.com/libexpat/libexpat/pull/1246
|
||||
Patch9: expat-2.5.0-CVE-2026-50219.patch
|
||||
# https://issues.redhat.com/browse/RHEL-221012
|
||||
# https://github.com/libexpat/libexpat/pull/1272
|
||||
Patch10: expat-2.5.0-CVE-2026-56132.patch
|
||||
|
||||
%description
|
||||
This is expat, the C library for parsing XML, written by James Clark. Expat
|
||||
@ -69,6 +72,7 @@ pushd ..
|
||||
%patch7 -p1 -b .CVE-2025-59375
|
||||
%patch8 -p1 -b .CVE-2026-45186
|
||||
%patch9 -p1 -b .CVE-2026-50219
|
||||
%patch10 -p1 -b .CVE-2026-56132
|
||||
popd
|
||||
|
||||
sed -i 's/install-data-hook/do-nothing-please/' lib/Makefile.am
|
||||
@ -118,6 +122,10 @@ make check
|
||||
%{_libdir}/lib*.a
|
||||
|
||||
%changelog
|
||||
* Fri Jul 31 2026 RHEL Packaging Agent <redhat-ymir-agent@redhat.com> - 2.5.0-2.2
|
||||
- Fix CVE-2026-56132: out-of-bound scaffolding index store in doProlog
|
||||
- Resolves: RHEL-221012
|
||||
|
||||
* Fri Jul 31 2026 RHEL Packaging Agent <redhat-ymir-agent@redhat.com> - 2.5.0-2.1
|
||||
- Fix CVE-2026-50219
|
||||
- Resolves: RHEL-220979
|
||||
|
||||
Loading…
Reference in New Issue
Block a user