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:
RHEL Packaging Agent 2026-07-31 11:27:29 +00:00
parent 4eb28dada0
commit 5b5ef91cf2
2 changed files with 297 additions and 1 deletions

View 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 parsers
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 parents 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);

View File

@ -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