diff mbox series

[scarthgap] expat: patch CVE-2024-8176

Message ID 20250317213335.1865158-1-peter.marko@siemens.com
State Rejected
Delegated to: Steve Sakoman
Headers show
Series [scarthgap] expat: patch CVE-2024-8176 | expand

Commit Message

Marko, Peter March 17, 2025, 9:33 p.m. UTC
From: Peter Marko <peter.marko@siemens.com>

Backport https://github.com/libexpat/libexpat/pull/973
Patch created by:
git diff 2fc36833334340ff7ddca374d86daa8744c1dfa3..99529768b4a722f46c69b04b874c1d45b3eb819c

Additional backport (containing changes in tests only) was needed to
apply it cleanly.

Signed-off-by: Peter Marko <peter.marko@siemens.com>
---
 ...ests-Cover-indirect-entity-recursion.patch |  103 ++
 .../expat/expat/CVE-2024-8176.patch           | 1477 +++++++++++++++++
 meta/recipes-core/expat/expat_2.6.4.bb        |    2 +
 3 files changed, 1582 insertions(+)
 create mode 100644 meta/recipes-core/expat/expat/0001-tests-Cover-indirect-entity-recursion.patch
 create mode 100644 meta/recipes-core/expat/expat/CVE-2024-8176.patch

Comments

Marko, Peter March 20, 2025, 3:28 p.m. UTC | #1
Please drop this patch for now.
The CVE fix causes a regression, see https://github.com/libexpat/libexpat/issues/980
I'll submit a new patch when the regression is fixed.

Expat 2.7.0 was already merged in master.
I think we can live with it and hope for a quick fix.
I'll be observing the regression discussions.

Peter

> -----Original Message-----
> From: Marko, Peter (FT D EU SK BFS1) <Peter.Marko@siemens.com>
> Sent: Monday, March 17, 2025 22:34
> To: openembedded-core@lists.openembedded.org
> Cc: Marko, Peter (FT D EU SK BFS1) <Peter.Marko@siemens.com>
> Subject: [OE-core][scarthgap][PATCH] expat: patch CVE-2024-8176
> 
> From: Peter Marko <peter.marko@siemens.com>
> 
> Backport https://github.com/libexpat/libexpat/pull/973
> Patch created by:
> git diff
> 2fc36833334340ff7ddca374d86daa8744c1dfa3..99529768b4a722f46c69b04b874c1
> d45b3eb819c
> 
> Additional backport (containing changes in tests only) was needed to
> apply it cleanly.
> 
> Signed-off-by: Peter Marko <peter.marko@siemens.com>
> ---
>  ...ests-Cover-indirect-entity-recursion.patch |  103 ++
>  .../expat/expat/CVE-2024-8176.patch           | 1477 +++++++++++++++++
>  meta/recipes-core/expat/expat_2.6.4.bb        |    2 +
>  3 files changed, 1582 insertions(+)
>  create mode 100644 meta/recipes-core/expat/expat/0001-tests-Cover-indirect-
> entity-recursion.patch
>  create mode 100644 meta/recipes-core/expat/expat/CVE-2024-8176.patch
> 
> diff --git a/meta/recipes-core/expat/expat/0001-tests-Cover-indirect-entity-
> recursion.patch b/meta/recipes-core/expat/expat/0001-tests-Cover-indirect-entity-
> recursion.patch
> new file mode 100644
> index 0000000000..802d762787
> --- /dev/null
> +++ b/meta/recipes-core/expat/expat/0001-tests-Cover-indirect-entity-
> recursion.patch
> @@ -0,0 +1,103 @@
> +From 3d5fdbb44e80ed789e4f6510542d77d6284fbd0e Mon Sep 17 00:00:00 2001
> +From: Sebastian Pipping <sebastian@pipping.org>
> +Date: Sat, 23 Nov 2024 14:20:21 +0100
> +Subject: [PATCH] tests: Cover indirect entity recursion
> +
> +Upstream-Status: Backport
> [https://github.com/libexpat/libexpat/commit/3d5fdbb44e80ed789e4f6510542d77d
> 6284fbd0e]
> +Signed-off-by: Peter Marko <peter.marko@siemens.com>
> +---
> + expat/tests/basic_tests.c | 74
> +++++++++++++++++++++++++++++++++++++++
> + 1 file changed, 74 insertions(+)
> +
> +diff --git a/expat/tests/basic_tests.c b/expat/tests/basic_tests.c
> +index d38b8fd1..d2306772 100644
> +--- a/expat/tests/basic_tests.c
> ++++ b/expat/tests/basic_tests.c
> +@@ -1202,6 +1202,79 @@ START_TEST(test_wfc_no_recursive_entity_refs) {
> + }
> + END_TEST
> +
> ++START_TEST(test_no_indirectly_recursive_entity_refs) {
> ++  struct TestCase {
> ++    const char *doc;
> ++    bool usesParameterEntities;
> ++  };
> ++
> ++  const struct TestCase cases[] = {
> ++      // general entity + character data
> ++      {"<!DOCTYPE a [\n"
> ++       "  <!ENTITY e1 '&e2;'>\n"
> ++       "  <!ENTITY e2 '&e1;'>\n"
> ++       "]><a>&e2;</a>\n",
> ++       false},
> ++
> ++      // general entity + attribute value
> ++      {"<!DOCTYPE a [\n"
> ++       "  <!ENTITY e1 '&e2;'>\n"
> ++       "  <!ENTITY e2 '&e1;'>\n"
> ++       "]><a k1='&e2;' />\n",
> ++       false},
> ++
> ++      // parameter entity
> ++      {"<!DOCTYPE doc [\n"
> ++       "  <!ENTITY % p1 '&#37;p2;'>\n"
> ++       "  <!ENTITY % p2 '&#37;p1;'>\n"
> ++       "  <!ENTITY % define_g \"<!ENTITY g '&#37;p2;'>\">\n"
> ++       "  %define_g;\n"
> ++       "]>\n"
> ++       "<doc/>\n",
> ++       true},
> ++  };
> ++  for (size_t i = 0; i < sizeof(cases) / sizeof(cases[0]); i++) {
> ++    const char *const doc = cases[i].doc;
> ++    const bool usesParameterEntities = cases[i].usesParameterEntities;
> ++
> ++    set_subtest("[%i] %s", (int)i, doc);
> ++
> ++#ifdef XML_DTD // both GE and DTD
> ++    const bool rejection_expected = true;
> ++#elif XML_GE == 1 // GE but not DTD
> ++    const bool rejection_expected = ! usesParameterEntities;
> ++#else             // neither DTD nor GE
> ++    const bool rejection_expected = false;
> ++#endif
> ++
> ++    XML_Parser parser = XML_ParserCreate(NULL);
> ++
> ++#ifdef XML_DTD
> ++    if (usesParameterEntities) {
> ++      assert_true(
> ++          XML_SetParamEntityParsing(parser,
> XML_PARAM_ENTITY_PARSING_ALWAYS)
> ++          == 1);
> ++    }
> ++#else
> ++    UNUSED_P(usesParameterEntities);
> ++#endif // XML_DTD
> ++
> ++    const enum XML_Status status
> ++        = _XML_Parse_SINGLE_BYTES(parser, doc, (int)strlen(doc),
> ++                                  /*isFinal*/ XML_TRUE);
> ++
> ++    if (rejection_expected) {
> ++      assert_true(status == XML_STATUS_ERROR);
> ++      assert_true(XML_GetErrorCode(parser) ==
> XML_ERROR_RECURSIVE_ENTITY_REF);
> ++    } else {
> ++      assert_true(status == XML_STATUS_OK);
> ++    }
> ++
> ++    XML_ParserFree(parser);
> ++  }
> ++}
> ++END_TEST
> ++
> + START_TEST(test_recursive_external_parameter_entity_2) {
> +   struct TestCase {
> +     const char *doc;
> +@@ -5969,6 +6042,7 @@ make_basic_test_case(Suite *s) {
> +   tcase_add_test(tc_basic, test_not_standalone_handler_reject);
> +   tcase_add_test(tc_basic, test_not_standalone_handler_accept);
> +   tcase_add_test__if_xml_ge(tc_basic, test_wfc_no_recursive_entity_refs);
> ++  tcase_add_test(tc_basic, test_no_indirectly_recursive_entity_refs);
> +   tcase_add_test__ifdef_xml_dtd(tc_basic, test_ext_entity_invalid_parse);
> +   tcase_add_test__if_xml_ge(tc_basic, test_dtd_default_handling);
> +   tcase_add_test(tc_basic, test_dtd_attr_handling);
> diff --git a/meta/recipes-core/expat/expat/CVE-2024-8176.patch b/meta/recipes-
> core/expat/expat/CVE-2024-8176.patch
> new file mode 100644
> index 0000000000..dc8a520161
> --- /dev/null
> +++ b/meta/recipes-core/expat/expat/CVE-2024-8176.patch
> @@ -0,0 +1,1477 @@
> +From 3f924a715cfa97e70df1c24334d2d728973d1020 Mon Sep 17 00:00:00 2001
> +From: Peter Marko <peter.marko@siemens.com>
> +Date: Mon, 17 Mar 2025 20:41:24 +0100
> +Subject: [PATCH] [CVE-2024-8176] Resolve the recursion during entity
> + processing to prevent stack overflow (fixes #893)
> +
> +Fixes #893
> +
> +CVE: CVE-2024-8176
> +Upstream-Status: Backport [https://github.com/libexpat/libexpat/pull/973]
> +Signed-off-by: Peter Marko <peter.marko@siemens.com>
> +---
> + expat/Changes             |  29 +-
> + expat/lib/xmlparse.c      | 564 ++++++++++++++++++++++++++++----------
> + expat/tests/alloc_tests.c |  27 ++
> + expat/tests/basic_tests.c | 247 +++++++++++++++--
> + expat/tests/handlers.c    |  14 +
> + expat/tests/handlers.h    |   5 +
> + expat/tests/misc_tests.c  |  43 +++
> + 7 files changed, 751 insertions(+), 178 deletions(-)
> +
> +diff --git a/expat/Changes b/expat/Changes
> +index aa19f70a..8c5db88c 100644
> +--- a/expat/Changes
> ++++ b/expat/Changes
> +@@ -11,7 +11,6 @@
> + !! The following topics need *additional skilled C developers* to progress   !!
> + !! in a timely manner or at all (loosely ordered by descending priority):    !!
> + !!                                                                           !!
> +-!! - <blink>fixing a complex non-public security issue</blink>,              !!
> + !! - teaming up on researching and fixing future security reports and        !!
> + !!   ClusterFuzz findings with few-days-max response times in communication  !!
> + !!   in order to (1) have a sound fix ready before the end of a 90 days      !!
> +@@ -30,6 +29,34 @@
> + !! THANK YOU!                        Sebastian Pipping -- Berlin, 2024-03-09 !!
> + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
> +
> ++Patches:
> ++        Security fixes:
> ++       #893 #???  CVE-2024-8176 -- Fix crash from chaining a large number
> ++                    of entities caused by stack overflow by resolving use of
> ++                    recursion, for all three uses of entities:
> ++                    - general entities in character data ("<e>&g1;</e>")
> ++                    - general entities in attribute values ("<e k1='&g1;'/>")
> ++                    - parameter entities ("%p1;")
> ++                    Known impact is (reliable and easy) denial of service:
> ++                    CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H/E:H/RL:O/RC:C
> ++                    (Base Score: 7.5, Temporal Score: 7.2)
> ++                    Please note that a layer of compression around XML can
> ++                    significantly reduce the minimum attack payload size.
> ++
> ++         Special thanks to:
> ++            Alexander Gieringer
> ++            Berkay Eren Ürün
> ++            Jann Horn
> ++            Sebastian Andrzej Siewior
> ++            Snild Dolkow
> ++            Thomas Pröll
> ++            Tomas Korbar
> ++                 and
> ++            Google Project Zero
> ++            Linutronix
> ++            Red Hat
> ++            Siemens
> ++
> + Release 2.6.4 Wed November 6 2024
> +         Security fixes:
> +             #915  CVE-2024-50602 -- Fix crash within function XML_ResumeParser
> +diff --git a/expat/lib/xmlparse.c b/expat/lib/xmlparse.c
> +index a4e091e7..473c791d 100644
> +--- a/expat/lib/xmlparse.c
> ++++ b/expat/lib/xmlparse.c
> +@@ -39,7 +39,7 @@
> +    Copyright (c) 2022      Sean McBride <sean@rogue-research.com>
> +    Copyright (c) 2023      Owain Davies <owaind@bath.edu>
> +    Copyright (c) 2023-2024 Sony Corporation / Snild Dolkow <snild@sony.com>
> +-   Copyright (c) 2024      Berkay Eren Ürün <berkay.ueruen@siemens.com>
> ++   Copyright (c) 2024-2025 Berkay Eren Ürün <berkay.ueruen@siemens.com>
> +    Copyright (c) 2024      Hanno Böck <hanno@gentoo.org>
> +    Licensed under the MIT license:
> +
> +@@ -325,6 +325,10 @@ typedef struct {
> +   const XML_Char *publicId;
> +   const XML_Char *notation;
> +   XML_Bool open;
> ++  XML_Bool hasMore; /* true if entity has not been completely processed */
> ++  /* An entity can be open while being already completely processed (hasMore
> ==
> ++    XML_FALSE). The reason is the delayed closing of entities until their inner
> ++    entities are processed and closed */
> +   XML_Bool is_param;
> +   XML_Bool is_internal; /* true if declared in internal subset outside PE */
> + } ENTITY;
> +@@ -415,6 +419,12 @@ typedef struct {
> +   int *scaffIndex;
> + } DTD;
> +
> ++enum EntityType {
> ++  ENTITY_INTERNAL,
> ++  ENTITY_ATTRIBUTE,
> ++  ENTITY_VALUE,
> ++};
> ++
> + typedef struct open_internal_entity {
> +   const char *internalEventPtr;
> +   const char *internalEventEndPtr;
> +@@ -422,6 +432,7 @@ typedef struct open_internal_entity {
> +   ENTITY *entity;
> +   int startTagLevel;
> +   XML_Bool betweenDecl; /* WFC: PE Between Declarations */
> ++  enum EntityType type;
> + } OPEN_INTERNAL_ENTITY;
> +
> + enum XML_Account {
> +@@ -481,8 +492,8 @@ static enum XML_Error doProlog(XML_Parser parser,
> const ENCODING *enc,
> +                                const char *next, const char **nextPtr,
> +                                XML_Bool haveMore, XML_Bool allowClosingDoctype,
> +                                enum XML_Account account);
> +-static enum XML_Error processInternalEntity(XML_Parser parser, ENTITY *entity,
> +-                                            XML_Bool betweenDecl);
> ++static enum XML_Error processEntity(XML_Parser parser, ENTITY *entity,
> ++                                    XML_Bool betweenDecl, enum EntityType type);
> + static enum XML_Error doContent(XML_Parser parser, int startTagLevel,
> +                                 const ENCODING *enc, const char *start,
> +                                 const char *end, const char **endPtr,
> +@@ -513,18 +524,22 @@ static enum XML_Error
> storeAttributeValue(XML_Parser parser,
> +                                           const char *ptr, const char *end,
> +                                           STRING_POOL *pool,
> +                                           enum XML_Account account);
> +-static enum XML_Error appendAttributeValue(XML_Parser parser,
> +-                                           const ENCODING *enc,
> +-                                           XML_Bool isCdata, const char *ptr,
> +-                                           const char *end, STRING_POOL *pool,
> +-                                           enum XML_Account account);
> ++static enum XML_Error
> ++appendAttributeValue(XML_Parser parser, const ENCODING *enc, XML_Bool
> isCdata,
> ++                     const char *ptr, const char *end, STRING_POOL *pool,
> ++                     enum XML_Account account, const char **nextPtr);
> + static ATTRIBUTE_ID *getAttributeId(XML_Parser parser, const ENCODING *enc,
> +                                     const char *start, const char *end);
> + static int setElementTypePrefix(XML_Parser parser, ELEMENT_TYPE
> *elementType);
> + #if XML_GE == 1
> + static enum XML_Error storeEntityValue(XML_Parser parser, const ENCODING
> *enc,
> +                                        const char *start, const char *end,
> +-                                       enum XML_Account account);
> ++                                       enum XML_Account account,
> ++                                       const char **nextPtr);
> ++static enum XML_Error callStoreEntityValue(XML_Parser parser,
> ++                                           const ENCODING *enc,
> ++                                           const char *start, const char *end,
> ++                                           enum XML_Account account);
> + #else
> + static enum XML_Error storeSelfEntityValue(XML_Parser parser, ENTITY *entity);
> + #endif
> +@@ -709,6 +724,10 @@ struct XML_ParserStruct {
> +   const char *m_positionPtr;
> +   OPEN_INTERNAL_ENTITY *m_openInternalEntities;
> +   OPEN_INTERNAL_ENTITY *m_freeInternalEntities;
> ++  OPEN_INTERNAL_ENTITY *m_openAttributeEntities;
> ++  OPEN_INTERNAL_ENTITY *m_freeAttributeEntities;
> ++  OPEN_INTERNAL_ENTITY *m_openValueEntities;
> ++  OPEN_INTERNAL_ENTITY *m_freeValueEntities;
> +   XML_Bool m_defaultExpandInternalEntities;
> +   int m_tagLevel;
> +   ENTITY *m_declEntity;
> +@@ -756,6 +775,7 @@ struct XML_ParserStruct {
> +   ACCOUNTING m_accounting;
> +   ENTITY_STATS m_entity_stats;
> + #endif
> ++  XML_Bool m_reenter;
> + };
> +
> + #define MALLOC(parser, s) (parser->m_mem.malloc_fcn((s)))
> +@@ -1028,7 +1048,29 @@ callProcessor(XML_Parser parser, const char *start,
> const char *end,
> + #if defined(XML_TESTING)
> +   g_bytesScanned += (unsigned)have_now;
> + #endif
> +-  const enum XML_Error ret = parser->m_processor(parser, start, end, endPtr);
> ++  // Run in a loop to eliminate dangerous recursion depths
> ++  enum XML_Error ret;
> ++  *endPtr = start;
> ++  while (1) {
> ++    // Use endPtr as the new start in each iteration, since it will
> ++    // be set to the next start point by m_processor.
> ++    ret = parser->m_processor(parser, *endPtr, end, endPtr);
> ++
> ++    // Make parsing status (and in particular XML_SUSPENDED) take
> ++    // precedence over re-enter flag when they disagree
> ++    if (parser->m_parsingStatus.parsing != XML_PARSING) {
> ++      parser->m_reenter = XML_FALSE;
> ++    }
> ++
> ++    if (! parser->m_reenter) {
> ++      break;
> ++    }
> ++
> ++    parser->m_reenter = XML_FALSE;
> ++    if (ret != XML_ERROR_NONE)
> ++      return ret;
> ++  }
> ++
> +   if (ret == XML_ERROR_NONE) {
> +     // if we consumed nothing, remember what we had on this parse attempt.
> +     if (*endPtr == start) {
> +@@ -1139,6 +1181,8 @@ parserCreate(const XML_Char *encodingName,
> +   parser->m_freeBindingList = NULL;
> +   parser->m_freeTagList = NULL;
> +   parser->m_freeInternalEntities = NULL;
> ++  parser->m_freeAttributeEntities = NULL;
> ++  parser->m_freeValueEntities = NULL;
> +
> +   parser->m_groupSize = 0;
> +   parser->m_groupConnector = NULL;
> +@@ -1241,6 +1285,8 @@ parserInit(XML_Parser parser, const XML_Char
> *encodingName) {
> +   parser->m_eventEndPtr = NULL;
> +   parser->m_positionPtr = NULL;
> +   parser->m_openInternalEntities = NULL;
> ++  parser->m_openAttributeEntities = NULL;
> ++  parser->m_openValueEntities = NULL;
> +   parser->m_defaultExpandInternalEntities = XML_TRUE;
> +   parser->m_tagLevel = 0;
> +   parser->m_tagStack = NULL;
> +@@ -1251,6 +1297,8 @@ parserInit(XML_Parser parser, const XML_Char
> *encodingName) {
> +   parser->m_unknownEncodingData = NULL;
> +   parser->m_parentParser = NULL;
> +   parser->m_parsingStatus.parsing = XML_INITIALIZED;
> ++  // Reentry can only be triggered inside m_processor calls
> ++  parser->m_reenter = XML_FALSE;
> + #ifdef XML_DTD
> +   parser->m_isParamEntity = XML_FALSE;
> +   parser->m_useForeignDTD = XML_FALSE;
> +@@ -1310,6 +1358,24 @@ XML_ParserReset(XML_Parser parser, const
> XML_Char *encodingName) {
> +     openEntity->next = parser->m_freeInternalEntities;
> +     parser->m_freeInternalEntities = openEntity;
> +   }
> ++  /* move m_openAttributeEntities to m_freeAttributeEntities (i.e. same task but
> ++   * for attributes) */
> ++  openEntityList = parser->m_openAttributeEntities;
> ++  while (openEntityList) {
> ++    OPEN_INTERNAL_ENTITY *openEntity = openEntityList;
> ++    openEntityList = openEntity->next;
> ++    openEntity->next = parser->m_freeAttributeEntities;
> ++    parser->m_freeAttributeEntities = openEntity;
> ++  }
> ++  /* move m_openValueEntities to m_freeValueEntities (i.e. same task but
> ++   * for value entities) */
> ++  openEntityList = parser->m_openValueEntities;
> ++  while (openEntityList) {
> ++    OPEN_INTERNAL_ENTITY *openEntity = openEntityList;
> ++    openEntityList = openEntity->next;
> ++    openEntity->next = parser->m_freeValueEntities;
> ++    parser->m_freeValueEntities = openEntity;
> ++  }
> +   moveToFreeBindingList(parser, parser->m_inheritedBindings);
> +   FREE(parser, parser->m_unknownEncodingMem);
> +   if (parser->m_unknownEncodingRelease)
> +@@ -1323,6 +1389,19 @@ XML_ParserReset(XML_Parser parser, const
> XML_Char *encodingName) {
> +   return XML_TRUE;
> + }
> +
> ++static XML_Bool
> ++parserBusy(XML_Parser parser) {
> ++  switch (parser->m_parsingStatus.parsing) {
> ++  case XML_PARSING:
> ++  case XML_SUSPENDED:
> ++    return XML_TRUE;
> ++  case XML_INITIALIZED:
> ++  case XML_FINISHED:
> ++  default:
> ++    return XML_FALSE;
> ++  }
> ++}
> ++
> + enum XML_Status XMLCALL
> + XML_SetEncoding(XML_Parser parser, const XML_Char *encodingName) {
> +   if (parser == NULL)
> +@@ -1331,8 +1410,7 @@ XML_SetEncoding(XML_Parser parser, const
> XML_Char *encodingName) {
> +      XXX There's no way for the caller to determine which of the
> +      XXX possible error cases caused the XML_STATUS_ERROR return.
> +   */
> +-  if (parser->m_parsingStatus.parsing == XML_PARSING
> +-      || parser->m_parsingStatus.parsing == XML_SUSPENDED)
> ++  if (parserBusy(parser))
> +     return XML_STATUS_ERROR;
> +
> +   /* Get rid of any previous encoding name */
> +@@ -1569,7 +1647,34 @@ XML_ParserFree(XML_Parser parser) {
> +     entityList = entityList->next;
> +     FREE(parser, openEntity);
> +   }
> +-
> ++  /* free m_openAttributeEntities and m_freeAttributeEntities */
> ++  entityList = parser->m_openAttributeEntities;
> ++  for (;;) {
> ++    OPEN_INTERNAL_ENTITY *openEntity;
> ++    if (entityList == NULL) {
> ++      if (parser->m_freeAttributeEntities == NULL)
> ++        break;
> ++      entityList = parser->m_freeAttributeEntities;
> ++      parser->m_freeAttributeEntities = NULL;
> ++    }
> ++    openEntity = entityList;
> ++    entityList = entityList->next;
> ++    FREE(parser, openEntity);
> ++  }
> ++  /* free m_openValueEntities and m_freeValueEntities */
> ++  entityList = parser->m_openValueEntities;
> ++  for (;;) {
> ++    OPEN_INTERNAL_ENTITY *openEntity;
> ++    if (entityList == NULL) {
> ++      if (parser->m_freeValueEntities == NULL)
> ++        break;
> ++      entityList = parser->m_freeValueEntities;
> ++      parser->m_freeValueEntities = NULL;
> ++    }
> ++    openEntity = entityList;
> ++    entityList = entityList->next;
> ++    FREE(parser, openEntity);
> ++  }
> +   destroyBindings(parser->m_freeBindingList, parser);
> +   destroyBindings(parser->m_inheritedBindings, parser);
> +   poolDestroy(&parser->m_tempPool);
> +@@ -1611,8 +1716,7 @@ XML_UseForeignDTD(XML_Parser parser, XML_Bool
> useDTD) {
> +     return XML_ERROR_INVALID_ARGUMENT;
> + #ifdef XML_DTD
> +   /* block after XML_Parse()/XML_ParseBuffer() has been called */
> +-  if (parser->m_parsingStatus.parsing == XML_PARSING
> +-      || parser->m_parsingStatus.parsing == XML_SUSPENDED)
> ++  if (parserBusy(parser))
> +     return XML_ERROR_CANT_CHANGE_FEATURE_ONCE_PARSING;
> +   parser->m_useForeignDTD = useDTD;
> +   return XML_ERROR_NONE;
> +@@ -1627,8 +1731,7 @@ XML_SetReturnNSTriplet(XML_Parser parser, int
> do_nst) {
> +   if (parser == NULL)
> +     return;
> +   /* block after XML_Parse()/XML_ParseBuffer() has been called */
> +-  if (parser->m_parsingStatus.parsing == XML_PARSING
> +-      || parser->m_parsingStatus.parsing == XML_SUSPENDED)
> ++  if (parserBusy(parser))
> +     return;
> +   parser->m_ns_triplets = do_nst ? XML_TRUE : XML_FALSE;
> + }
> +@@ -1897,8 +2000,7 @@ XML_SetParamEntityParsing(XML_Parser parser,
> +   if (parser == NULL)
> +     return 0;
> +   /* block after XML_Parse()/XML_ParseBuffer() has been called */
> +-  if (parser->m_parsingStatus.parsing == XML_PARSING
> +-      || parser->m_parsingStatus.parsing == XML_SUSPENDED)
> ++  if (parserBusy(parser))
> +     return 0;
> + #ifdef XML_DTD
> +   parser->m_paramEntityParsing = peParsing;
> +@@ -1915,8 +2017,7 @@ XML_SetHashSalt(XML_Parser parser, unsigned long
> hash_salt) {
> +   if (parser->m_parentParser)
> +     return XML_SetHashSalt(parser->m_parentParser, hash_salt);
> +   /* block after XML_Parse()/XML_ParseBuffer() has been called */
> +-  if (parser->m_parsingStatus.parsing == XML_PARSING
> +-      || parser->m_parsingStatus.parsing == XML_SUSPENDED)
> ++  if (parserBusy(parser))
> +     return 0;
> +   parser->m_hash_secret_salt = hash_salt;
> +   return 1;
> +@@ -2230,6 +2331,11 @@ XML_GetBuffer(XML_Parser parser, int len) {
> +   return parser->m_bufferEnd;
> + }
> +
> ++static void
> ++triggerReenter(XML_Parser parser) {
> ++  parser->m_reenter = XML_TRUE;
> ++}
> ++
> + enum XML_Status XMLCALL
> + XML_StopParser(XML_Parser parser, XML_Bool resumable) {
> +   if (parser == NULL)
> +@@ -2704,8 +2810,9 @@ static enum XML_Error PTRCALL
> + contentProcessor(XML_Parser parser, const char *start, const char *end,
> +                  const char **endPtr) {
> +   enum XML_Error result = doContent(
> +-      parser, 0, parser->m_encoding, start, end, endPtr,
> +-      (XML_Bool)! parser->m_parsingStatus.finalBuffer,
> XML_ACCOUNT_DIRECT);
> ++      parser, parser->m_parentParser ? 1 : 0, parser->m_encoding, start, end,
> ++      endPtr, (XML_Bool)! parser->m_parsingStatus.finalBuffer,
> ++      XML_ACCOUNT_DIRECT);
> +   if (result == XML_ERROR_NONE) {
> +     if (! storeRawNames(parser))
> +       return XML_ERROR_NO_MEMORY;
> +@@ -2793,6 +2900,11 @@ externalEntityInitProcessor3(XML_Parser parser,
> const char *start,
> +       return XML_ERROR_NONE;
> +     case XML_FINISHED:
> +       return XML_ERROR_ABORTED;
> ++    case XML_PARSING:
> ++      if (parser->m_reenter) {
> ++        return XML_ERROR_UNEXPECTED_STATE; // LCOV_EXCL_LINE
> ++      }
> ++      /* Fall through */
> +     default:
> +       start = next;
> +     }
> +@@ -2966,7 +3078,7 @@ doContent(XML_Parser parser, int startTagLevel, const
> ENCODING *enc,
> +             reportDefault(parser, enc, s, next);
> +           break;
> +         }
> +-        result = processInternalEntity(parser, entity, XML_FALSE);
> ++        result = processEntity(parser, entity, XML_FALSE, ENTITY_INTERNAL);
> +         if (result != XML_ERROR_NONE)
> +           return result;
> +       } else if (parser->m_externalEntityRefHandler) {
> +@@ -3092,7 +3204,9 @@ doContent(XML_Parser parser, int startTagLevel, const
> ENCODING *enc,
> +     }
> +       if ((parser->m_tagLevel == 0)
> +           && (parser->m_parsingStatus.parsing != XML_FINISHED)) {
> +-        if (parser->m_parsingStatus.parsing == XML_SUSPENDED)
> ++        if (parser->m_parsingStatus.parsing == XML_SUSPENDED
> ++            || (parser->m_parsingStatus.parsing == XML_PARSING
> ++                && parser->m_reenter))
> +           parser->m_processor = epilogProcessor;
> +         else
> +           return epilogProcessor(parser, next, end, nextPtr);
> +@@ -3153,7 +3267,9 @@ doContent(XML_Parser parser, int startTagLevel, const
> ENCODING *enc,
> +         }
> +         if ((parser->m_tagLevel == 0)
> +             && (parser->m_parsingStatus.parsing != XML_FINISHED)) {
> +-          if (parser->m_parsingStatus.parsing == XML_SUSPENDED)
> ++          if (parser->m_parsingStatus.parsing == XML_SUSPENDED
> ++              || (parser->m_parsingStatus.parsing == XML_PARSING
> ++                  && parser->m_reenter))
> +             parser->m_processor = epilogProcessor;
> +           else
> +             return epilogProcessor(parser, next, end, nextPtr);
> +@@ -3293,6 +3409,12 @@ doContent(XML_Parser parser, int startTagLevel,
> const ENCODING *enc,
> +       return XML_ERROR_NONE;
> +     case XML_FINISHED:
> +       return XML_ERROR_ABORTED;
> ++    case XML_PARSING:
> ++      if (parser->m_reenter) {
> ++        *nextPtr = next;
> ++        return XML_ERROR_NONE;
> ++      }
> ++      /* Fall through */
> +     default:;
> +     }
> +   }
> +@@ -4217,6 +4339,11 @@ doCdataSection(XML_Parser parser, const
> ENCODING *enc, const char **startPtr,
> +       return XML_ERROR_NONE;
> +     case XML_FINISHED:
> +       return XML_ERROR_ABORTED;
> ++    case XML_PARSING:
> ++      if (parser->m_reenter) {
> ++        return XML_ERROR_UNEXPECTED_STATE; // LCOV_EXCL_LINE
> ++      }
> ++      /* Fall through */
> +     default:;
> +     }
> +   }
> +@@ -4549,7 +4676,7 @@ entityValueInitProcessor(XML_Parser parser, const
> char *s, const char *end,
> +       }
> +       /* found end of entity value - can store it now */
> +       return storeEntityValue(parser, parser->m_encoding, s, end,
> +-                              XML_ACCOUNT_DIRECT);
> ++                              XML_ACCOUNT_DIRECT, NULL);
> +     } else if (tok == XML_TOK_XML_DECL) {
> +       enum XML_Error result;
> +       result = processXmlDecl(parser, 0, start, next);
> +@@ -4676,7 +4803,7 @@ entityValueProcessor(XML_Parser parser, const char
> *s, const char *end,
> +         break;
> +       }
> +       /* found end of entity value - can store it now */
> +-      return storeEntityValue(parser, enc, s, end, XML_ACCOUNT_DIRECT);
> ++      return storeEntityValue(parser, enc, s, end, XML_ACCOUNT_DIRECT,
> NULL);
> +     }
> +     start = next;
> +   }
> +@@ -5119,9 +5246,9 @@ doProlog(XML_Parser parser, const ENCODING *enc,
> const char *s, const char *end,
> + #if XML_GE == 1
> +         // This will store the given replacement text in
> +         // parser->m_declEntity->textPtr.
> +-        enum XML_Error result
> +-            = storeEntityValue(parser, enc, s + enc->minBytesPerChar,
> +-                               next - enc->minBytesPerChar, XML_ACCOUNT_NONE);
> ++        enum XML_Error result = callStoreEntityValue(
> ++            parser, enc, s + enc->minBytesPerChar, next - enc->minBytesPerChar,
> ++            XML_ACCOUNT_NONE);
> +         if (parser->m_declEntity) {
> +           parser->m_declEntity->textPtr = poolStart(&dtd->entityValuePool);
> +           parser->m_declEntity->textLen
> +@@ -5546,7 +5673,7 @@ doProlog(XML_Parser parser, const ENCODING *enc,
> const char *s, const char *end,
> +           enum XML_Error result;
> +           XML_Bool betweenDecl
> +               = (role == XML_ROLE_PARAM_ENTITY_REF ? XML_TRUE :
> XML_FALSE);
> +-          result = processInternalEntity(parser, entity, betweenDecl);
> ++          result = processEntity(parser, entity, betweenDecl, ENTITY_INTERNAL);
> +           if (result != XML_ERROR_NONE)
> +             return result;
> +           handleDefault = XML_FALSE;
> +@@ -5751,6 +5878,12 @@ doProlog(XML_Parser parser, const ENCODING
> *enc, const char *s, const char *end,
> +       return XML_ERROR_NONE;
> +     case XML_FINISHED:
> +       return XML_ERROR_ABORTED;
> ++    case XML_PARSING:
> ++      if (parser->m_reenter) {
> ++        *nextPtr = next;
> ++        return XML_ERROR_NONE;
> ++      }
> ++    /* Fall through */
> +     default:
> +       s = next;
> +       tok = XmlPrologTok(enc, s, end, &next);
> +@@ -5825,21 +5958,49 @@ epilogProcessor(XML_Parser parser, const char *s,
> const char *end,
> +       return XML_ERROR_NONE;
> +     case XML_FINISHED:
> +       return XML_ERROR_ABORTED;
> ++    case XML_PARSING:
> ++      if (parser->m_reenter) {
> ++        return XML_ERROR_UNEXPECTED_STATE; // LCOV_EXCL_LINE
> ++      }
> ++    /* Fall through */
> +     default:;
> +     }
> +   }
> + }
> +
> + static enum XML_Error
> +-processInternalEntity(XML_Parser parser, ENTITY *entity, XML_Bool
> betweenDecl) {
> +-  const char *textStart, *textEnd;
> +-  const char *next;
> +-  enum XML_Error result;
> +-  OPEN_INTERNAL_ENTITY *openEntity;
> ++processEntity(XML_Parser parser, ENTITY *entity, XML_Bool betweenDecl,
> ++              enum EntityType type) {
> ++  OPEN_INTERNAL_ENTITY *openEntity, **openEntityList, **freeEntityList;
> ++  switch (type) {
> ++  case ENTITY_INTERNAL:
> ++    parser->m_processor = internalEntityProcessor;
> ++    openEntityList = &parser->m_openInternalEntities;
> ++    freeEntityList = &parser->m_freeInternalEntities;
> ++    break;
> ++  case ENTITY_ATTRIBUTE:
> ++    openEntityList = &parser->m_openAttributeEntities;
> ++    freeEntityList = &parser->m_freeAttributeEntities;
> ++    break;
> ++  case ENTITY_VALUE:
> ++    openEntityList = &parser->m_openValueEntities;
> ++    freeEntityList = &parser->m_freeValueEntities;
> ++    break;
> ++    /* default case serves merely as a safety net in case of a
> ++     * wrong entityType. Therefore we exclude the following lines
> ++     * from the test coverage.
> ++     *
> ++     * LCOV_EXCL_START
> ++     */
> ++  default:
> ++    // Should not reach here
> ++    assert(0);
> ++    /* LCOV_EXCL_STOP */
> ++  }
> +
> +-  if (parser->m_freeInternalEntities) {
> +-    openEntity = parser->m_freeInternalEntities;
> +-    parser->m_freeInternalEntities = openEntity->next;
> ++  if (*freeEntityList) {
> ++    openEntity = *freeEntityList;
> ++    *freeEntityList = openEntity->next;
> +   } else {
> +     openEntity
> +         = (OPEN_INTERNAL_ENTITY *)MALLOC(parser,
> sizeof(OPEN_INTERNAL_ENTITY));
> +@@ -5847,55 +6008,34 @@ processInternalEntity(XML_Parser parser, ENTITY
> *entity, XML_Bool betweenDecl) {
> +       return XML_ERROR_NO_MEMORY;
> +   }
> +   entity->open = XML_TRUE;
> ++  entity->hasMore = XML_TRUE;
> + #if XML_GE == 1
> +   entityTrackingOnOpen(parser, entity, __LINE__);
> + #endif
> +   entity->processed = 0;
> +-  openEntity->next = parser->m_openInternalEntities;
> +-  parser->m_openInternalEntities = openEntity;
> ++  openEntity->next = *openEntityList;
> ++  *openEntityList = openEntity;
> +   openEntity->entity = entity;
> ++  openEntity->type = type;
> +   openEntity->startTagLevel = parser->m_tagLevel;
> +   openEntity->betweenDecl = betweenDecl;
> +   openEntity->internalEventPtr = NULL;
> +   openEntity->internalEventEndPtr = NULL;
> +-  textStart = (const char *)entity->textPtr;
> +-  textEnd = (const char *)(entity->textPtr + entity->textLen);
> +-  /* Set a safe default value in case 'next' does not get set */
> +-  next = textStart;
> +
> +-  if (entity->is_param) {
> +-    int tok
> +-        = XmlPrologTok(parser->m_internalEncoding, textStart, textEnd, &next);
> +-    result = doProlog(parser, parser->m_internalEncoding, textStart, textEnd,
> +-                      tok, next, &next, XML_FALSE, XML_FALSE,
> +-                      XML_ACCOUNT_ENTITY_EXPANSION);
> +-  } else {
> +-    result = doContent(parser, parser->m_tagLevel, parser->m_internalEncoding,
> +-                       textStart, textEnd, &next, XML_FALSE,
> +-                       XML_ACCOUNT_ENTITY_EXPANSION);
> ++  // Only internal entities make use of the reenter flag
> ++  // therefore no need to set it for other entity types
> ++  if (type == ENTITY_INTERNAL) {
> ++    triggerReenter(parser);
> +   }
> +-
> +-  if (result == XML_ERROR_NONE) {
> +-    if (textEnd != next && parser->m_parsingStatus.parsing ==
> XML_SUSPENDED) {
> +-      entity->processed = (int)(next - textStart);
> +-      parser->m_processor = internalEntityProcessor;
> +-    } else if (parser->m_openInternalEntities->entity == entity) {
> +-#if XML_GE == 1
> +-      entityTrackingOnClose(parser, entity, __LINE__);
> +-#endif /* XML_GE == 1 */
> +-      entity->open = XML_FALSE;
> +-      parser->m_openInternalEntities = openEntity->next;
> +-      /* put openEntity back in list of free instances */
> +-      openEntity->next = parser->m_freeInternalEntities;
> +-      parser->m_freeInternalEntities = openEntity;
> +-    }
> +-  }
> +-  return result;
> ++  return XML_ERROR_NONE;
> + }
> +
> + static enum XML_Error PTRCALL
> + internalEntityProcessor(XML_Parser parser, const char *s, const char *end,
> +                         const char **nextPtr) {
> ++  UNUSED_P(s);
> ++  UNUSED_P(end);
> ++  UNUSED_P(nextPtr);
> +   ENTITY *entity;
> +   const char *textStart, *textEnd;
> +   const char *next;
> +@@ -5905,68 +6045,67 @@ internalEntityProcessor(XML_Parser parser, const
> char *s, const char *end,
> +     return XML_ERROR_UNEXPECTED_STATE;
> +
> +   entity = openEntity->entity;
> +-  textStart = ((const char *)entity->textPtr) + entity->processed;
> +-  textEnd = (const char *)(entity->textPtr + entity->textLen);
> +-  /* Set a safe default value in case 'next' does not get set */
> +-  next = textStart;
> +
> +-  if (entity->is_param) {
> +-    int tok
> +-        = XmlPrologTok(parser->m_internalEncoding, textStart, textEnd, &next);
> +-    result = doProlog(parser, parser->m_internalEncoding, textStart, textEnd,
> +-                      tok, next, &next, XML_FALSE, XML_TRUE,
> +-                      XML_ACCOUNT_ENTITY_EXPANSION);
> +-  } else {
> +-    result = doContent(parser, openEntity->startTagLevel,
> +-                       parser->m_internalEncoding, textStart, textEnd, &next,
> +-                       XML_FALSE, XML_ACCOUNT_ENTITY_EXPANSION);
> +-  }
> ++  // This will return early
> ++  if (entity->hasMore) {
> ++    textStart = ((const char *)entity->textPtr) + entity->processed;
> ++    textEnd = (const char *)(entity->textPtr + entity->textLen);
> ++    /* Set a safe default value in case 'next' does not get set */
> ++    next = textStart;
> +
> +-  if (result != XML_ERROR_NONE)
> +-    return result;
> ++    if (entity->is_param) {
> ++      int tok
> ++          = XmlPrologTok(parser->m_internalEncoding, textStart, textEnd, &next);
> ++      result = doProlog(parser, parser->m_internalEncoding, textStart, textEnd,
> ++                        tok, next, &next, XML_FALSE, XML_FALSE,
> ++                        XML_ACCOUNT_ENTITY_EXPANSION);
> ++    } else {
> ++      result = doContent(parser, openEntity->startTagLevel,
> ++                         parser->m_internalEncoding, textStart, textEnd, &next,
> ++                         XML_FALSE, XML_ACCOUNT_ENTITY_EXPANSION);
> ++    }
> ++
> ++    if (result != XML_ERROR_NONE)
> ++      return result;
> ++    // Check if entity is complete, if not, mark down how much of it is
> ++    // processed
> ++    if (textEnd != next
> ++        && (parser->m_parsingStatus.parsing == XML_SUSPENDED
> ++            || (parser->m_parsingStatus.parsing == XML_PARSING
> ++                && parser->m_reenter))) {
> ++      entity->processed = (int)(next - (const char *)entity->textPtr);
> ++      return result;
> ++    }
> +
> +-  if (textEnd != next && parser->m_parsingStatus.parsing ==
> XML_SUSPENDED) {
> +-    entity->processed = (int)(next - (const char *)entity->textPtr);
> ++    // Entity is complete. We cannot close it here since we need to first
> ++    // process its possible inner entities (which are added to the
> ++    // m_openInternalEntities during doProlog or doContent calls above)
> ++    entity->hasMore = XML_FALSE;
> ++    triggerReenter(parser);
> +     return result;
> +-  }
> ++  } // End of entity processing, "if" block will return here
> +
> ++  // Remove fully processed openEntity from open entity list.
> + #if XML_GE == 1
> +   entityTrackingOnClose(parser, entity, __LINE__);
> + #endif
> ++  // openEntity is m_openInternalEntities' head, as we set it at the start of
> ++  // this function and we skipped doProlog and doContent calls with hasMore
> set
> ++  // to false. This means we can directly remove the head of
> ++  // m_openInternalEntities
> ++  assert(parser->m_openInternalEntities == openEntity);
> +   entity->open = XML_FALSE;
> +-  parser->m_openInternalEntities = openEntity->next;
> ++  parser->m_openInternalEntities = parser->m_openInternalEntities->next;
> ++
> +   /* put openEntity back in list of free instances */
> +   openEntity->next = parser->m_freeInternalEntities;
> +   parser->m_freeInternalEntities = openEntity;
> +
> +-  // If there are more open entities we want to stop right here and have the
> +-  // upcoming call to XML_ResumeParser continue with entity content, or it would
> +-  // be ignored altogether.
> +-  if (parser->m_openInternalEntities != NULL
> +-      && parser->m_parsingStatus.parsing == XML_SUSPENDED) {
> +-    return XML_ERROR_NONE;
> +-  }
> +-
> +-  if (entity->is_param) {
> +-    int tok;
> +-    parser->m_processor = prologProcessor;
> +-    tok = XmlPrologTok(parser->m_encoding, s, end, &next);
> +-    return doProlog(parser, parser->m_encoding, s, end, tok, next, nextPtr,
> +-                    (XML_Bool)! parser->m_parsingStatus.finalBuffer, XML_TRUE,
> +-                    XML_ACCOUNT_DIRECT);
> +-  } else {
> +-    parser->m_processor = contentProcessor;
> +-    /* see externalEntityContentProcessor vs contentProcessor */
> +-    result = doContent(parser, parser->m_parentParser ? 1 : 0,
> +-                       parser->m_encoding, s, end, nextPtr,
> +-                       (XML_Bool)! parser->m_parsingStatus.finalBuffer,
> +-                       XML_ACCOUNT_DIRECT);
> +-    if (result == XML_ERROR_NONE) {
> +-      if (! storeRawNames(parser))
> +-        return XML_ERROR_NO_MEMORY;
> +-    }
> +-    return result;
> ++  if (parser->m_openInternalEntities == NULL) {
> ++    parser->m_processor = entity->is_param ? prologProcessor :
> contentProcessor;
> +   }
> ++  triggerReenter(parser);
> ++  return XML_ERROR_NONE;
> + }
> +
> + static enum XML_Error PTRCALL
> +@@ -5982,8 +6121,70 @@ static enum XML_Error
> + storeAttributeValue(XML_Parser parser, const ENCODING *enc, XML_Bool
> isCdata,
> +                     const char *ptr, const char *end, STRING_POOL *pool,
> +                     enum XML_Account account) {
> +-  enum XML_Error result
> +-      = appendAttributeValue(parser, enc, isCdata, ptr, end, pool, account);
> ++  const char *next = ptr;
> ++  enum XML_Error result = XML_ERROR_NONE;
> ++
> ++  while (1) {
> ++    if (! parser->m_openAttributeEntities) {
> ++      result = appendAttributeValue(parser, enc, isCdata, next, end, pool,
> ++                                    account, &next);
> ++    } else {
> ++      OPEN_INTERNAL_ENTITY *const openEntity = parser-
> >m_openAttributeEntities;
> ++      if (! openEntity)
> ++        return XML_ERROR_UNEXPECTED_STATE;
> ++
> ++      ENTITY *const entity = openEntity->entity;
> ++      const char *const textStart
> ++          = ((const char *)entity->textPtr) + entity->processed;
> ++      const char *const textEnd
> ++          = (const char *)(entity->textPtr + entity->textLen);
> ++      /* Set a safe default value in case 'next' does not get set */
> ++      const char *nextInEntity = textStart;
> ++      if (entity->hasMore) {
> ++        result = appendAttributeValue(
> ++            parser, parser->m_internalEncoding, isCdata, textStart, textEnd,
> ++            pool, XML_ACCOUNT_ENTITY_EXPANSION, &nextInEntity);
> ++        if (result != XML_ERROR_NONE)
> ++          break;
> ++        // Check if entity is complete, if not, mark down how much of it is
> ++        // processed. A XML_SUSPENDED check here is not required as
> ++        // appendAttributeValue will never suspend the parser.
> ++        if (textEnd != nextInEntity) {
> ++          entity->processed
> ++              = (int)(nextInEntity - (const char *)entity->textPtr);
> ++          continue;
> ++        }
> ++
> ++        // Entity is complete. We cannot close it here since we need to first
> ++        // process its possible inner entities (which are added to the
> ++        // m_openAttributeEntities during appendAttributeValue)
> ++        entity->hasMore = XML_FALSE;
> ++        continue;
> ++      } // End of entity processing, "if" block skips the rest
> ++
> ++      // Remove fully processed openEntity from open entity list.
> ++#if XML_GE == 1
> ++      entityTrackingOnClose(parser, entity, __LINE__);
> ++#endif
> ++      // openEntity is m_openAttributeEntities' head, since we set it at the
> ++      // start of this function and because we skipped appendAttributeValue call
> ++      // with hasMore set to false. This means we can directly remove the head
> ++      // of m_openAttributeEntities
> ++      assert(parser->m_openAttributeEntities == openEntity);
> ++      entity->open = XML_FALSE;
> ++      parser->m_openAttributeEntities = parser->m_openAttributeEntities->next;
> ++
> ++      /* put openEntity back in list of free instances */
> ++      openEntity->next = parser->m_freeAttributeEntities;
> ++      parser->m_freeAttributeEntities = openEntity;
> ++    }
> ++
> ++    // Break if an error occurred or there is nothing left to process
> ++    if (result || (parser->m_openAttributeEntities == NULL && end == next)) {
> ++      break;
> ++    }
> ++  }
> ++
> +   if (result)
> +     return result;
> +   if (! isCdata && poolLength(pool) && poolLastChar(pool) == 0x20)
> +@@ -5996,7 +6197,7 @@ storeAttributeValue(XML_Parser parser, const
> ENCODING *enc, XML_Bool isCdata,
> + static enum XML_Error
> + appendAttributeValue(XML_Parser parser, const ENCODING *enc, XML_Bool
> isCdata,
> +                      const char *ptr, const char *end, STRING_POOL *pool,
> +-                     enum XML_Account account) {
> ++                     enum XML_Account account, const char **nextPtr) {
> +   DTD *const dtd = parser->m_dtd; /* save one level of indirection */
> + #ifndef XML_DTD
> +   UNUSED_P(account);
> +@@ -6014,6 +6215,9 @@ appendAttributeValue(XML_Parser parser, const
> ENCODING *enc, XML_Bool isCdata,
> + #endif
> +     switch (tok) {
> +     case XML_TOK_NONE:
> ++      if (nextPtr) {
> ++        *nextPtr = next;
> ++      }
> +       return XML_ERROR_NONE;
> +     case XML_TOK_INVALID:
> +       if (enc == parser->m_encoding)
> +@@ -6154,21 +6358,11 @@ appendAttributeValue(XML_Parser parser, const
> ENCODING *enc, XML_Bool isCdata,
> +         return XML_ERROR_ATTRIBUTE_EXTERNAL_ENTITY_REF;
> +       } else {
> +         enum XML_Error result;
> +-        const XML_Char *textEnd = entity->textPtr + entity->textLen;
> +-        entity->open = XML_TRUE;
> +-#if XML_GE == 1
> +-        entityTrackingOnOpen(parser, entity, __LINE__);
> +-#endif
> +-        result = appendAttributeValue(parser, parser->m_internalEncoding,
> +-                                      isCdata, (const char *)entity->textPtr,
> +-                                      (const char *)textEnd, pool,
> +-                                      XML_ACCOUNT_ENTITY_EXPANSION);
> +-#if XML_GE == 1
> +-        entityTrackingOnClose(parser, entity, __LINE__);
> +-#endif
> +-        entity->open = XML_FALSE;
> +-        if (result)
> +-          return result;
> ++        result = processEntity(parser, entity, XML_FALSE, ENTITY_ATTRIBUTE);
> ++        if ((result == XML_ERROR_NONE) && (nextPtr != NULL)) {
> ++          *nextPtr = next;
> ++        }
> ++        return result;
> +       }
> +     } break;
> +     default:
> +@@ -6197,7 +6391,7 @@ appendAttributeValue(XML_Parser parser, const
> ENCODING *enc, XML_Bool isCdata,
> + static enum XML_Error
> + storeEntityValue(XML_Parser parser, const ENCODING *enc,
> +                  const char *entityTextPtr, const char *entityTextEnd,
> +-                 enum XML_Account account) {
> ++                 enum XML_Account account, const char **nextPtr) {
> +   DTD *const dtd = parser->m_dtd; /* save one level of indirection */
> +   STRING_POOL *pool = &(dtd->entityValuePool);
> +   enum XML_Error result = XML_ERROR_NONE;
> +@@ -6215,8 +6409,9 @@ storeEntityValue(XML_Parser parser, const
> ENCODING *enc,
> +       return XML_ERROR_NO_MEMORY;
> +   }
> +
> ++  const char *next;
> +   for (;;) {
> +-    const char *next
> ++    next
> +         = entityTextPtr; /* XmlEntityValueTok doesn't always set the last arg */
> +     int tok = XmlEntityValueTok(enc, entityTextPtr, entityTextEnd, &next);
> +
> +@@ -6278,16 +6473,8 @@ storeEntityValue(XML_Parser parser, const
> ENCODING *enc,
> +           } else
> +             dtd->keepProcessing = dtd->standalone;
> +         } else {
> +-          entity->open = XML_TRUE;
> +-          entityTrackingOnOpen(parser, entity, __LINE__);
> +-          result = storeEntityValue(
> +-              parser, parser->m_internalEncoding, (const char *)entity->textPtr,
> +-              (const char *)(entity->textPtr + entity->textLen),
> +-              XML_ACCOUNT_ENTITY_EXPANSION);
> +-          entityTrackingOnClose(parser, entity, __LINE__);
> +-          entity->open = XML_FALSE;
> +-          if (result)
> +-            goto endEntityValue;
> ++          result = processEntity(parser, entity, XML_FALSE, ENTITY_VALUE);
> ++          goto endEntityValue;
> +         }
> +         break;
> +       }
> +@@ -6375,6 +6562,81 @@ endEntityValue:
> + #  ifdef XML_DTD
> +   parser->m_prologState.inEntityValue = oldInEntityValue;
> + #  endif /* XML_DTD */
> ++  // If 'nextPtr' is given, it should be updated during the processing
> ++  if (nextPtr != NULL) {
> ++    *nextPtr = next;
> ++  }
> ++  return result;
> ++}
> ++
> ++static enum XML_Error
> ++callStoreEntityValue(XML_Parser parser, const ENCODING *enc,
> ++                     const char *entityTextPtr, const char *entityTextEnd,
> ++                     enum XML_Account account) {
> ++  const char *next = entityTextPtr;
> ++  enum XML_Error result = XML_ERROR_NONE;
> ++  while (1) {
> ++    if (! parser->m_openValueEntities) {
> ++      result
> ++          = storeEntityValue(parser, enc, next, entityTextEnd, account, &next);
> ++    } else {
> ++      OPEN_INTERNAL_ENTITY *const openEntity = parser-
> >m_openValueEntities;
> ++      if (! openEntity)
> ++        return XML_ERROR_UNEXPECTED_STATE;
> ++
> ++      ENTITY *const entity = openEntity->entity;
> ++      const char *const textStart
> ++          = ((const char *)entity->textPtr) + entity->processed;
> ++      const char *const textEnd
> ++          = (const char *)(entity->textPtr + entity->textLen);
> ++      /* Set a safe default value in case 'next' does not get set */
> ++      const char *nextInEntity = textStart;
> ++      if (entity->hasMore) {
> ++        result = storeEntityValue(parser, parser->m_internalEncoding, textStart,
> ++                                  textEnd, XML_ACCOUNT_ENTITY_EXPANSION,
> ++                                  &nextInEntity);
> ++        if (result != XML_ERROR_NONE)
> ++          break;
> ++        // Check if entity is complete, if not, mark down how much of it is
> ++        // processed. A XML_SUSPENDED check here is not required as
> ++        // appendAttributeValue will never suspend the parser.
> ++        if (textEnd != nextInEntity) {
> ++          entity->processed
> ++              = (int)(nextInEntity - (const char *)entity->textPtr);
> ++          continue;
> ++        }
> ++
> ++        // Entity is complete. We cannot close it here since we need to first
> ++        // process its possible inner entities (which are added to the
> ++        // m_openValueEntities during storeEntityValue)
> ++        entity->hasMore = XML_FALSE;
> ++        continue;
> ++      } // End of entity processing, "if" block skips the rest
> ++
> ++      // Remove fully processed openEntity from open entity list.
> ++#  if XML_GE == 1
> ++      entityTrackingOnClose(parser, entity, __LINE__);
> ++#  endif
> ++      // openEntity is m_openValueEntities' head, since we set it at the
> ++      // start of this function and because we skipped storeEntityValue call
> ++      // with hasMore set to false. This means we can directly remove the head
> ++      // of m_openValueEntities
> ++      assert(parser->m_openValueEntities == openEntity);
> ++      entity->open = XML_FALSE;
> ++      parser->m_openValueEntities = parser->m_openValueEntities->next;
> ++
> ++      /* put openEntity back in list of free instances */
> ++      openEntity->next = parser->m_freeValueEntities;
> ++      parser->m_freeValueEntities = openEntity;
> ++    }
> ++
> ++    // Break if an error occurred or there is nothing left to process
> ++    if (result
> ++        || (parser->m_openValueEntities == NULL && entityTextEnd == next)) {
> ++      break;
> ++    }
> ++  }
> ++
> +   return result;
> + }
> +
> +diff --git a/expat/tests/alloc_tests.c b/expat/tests/alloc_tests.c
> +index e5d46ebe..12ea3b2a 100644
> +--- a/expat/tests/alloc_tests.c
> ++++ b/expat/tests/alloc_tests.c
> +@@ -19,6 +19,7 @@
> +    Copyright (c) 2020      Tim Gates <tim.gates@iress.com>
> +    Copyright (c) 2021      Donghee Na <donghee.na@python.org>
> +    Copyright (c) 2023      Sony Corporation / Snild Dolkow <snild@sony.com>
> ++   Copyright (c) 2025      Berkay Eren Ürün <berkay.ueruen@siemens.com>
> +    Licensed under the MIT license:
> +
> +    Permission is  hereby granted,  free of charge,  to any  person obtaining
> +@@ -450,6 +451,31 @@ START_TEST(test_alloc_internal_entity) {
> + }
> + END_TEST
> +
> ++START_TEST(test_alloc_parameter_entity) {
> ++  const char *text = "<!DOCTYPE foo ["
> ++                     "<!ENTITY % param1 \"<!ENTITY internal 'some_text'>\">"
> ++                     "%param1;"
> ++                     "]> <foo>&internal;content</foo>";
> ++  int i;
> ++  const int alloc_test_max_repeats = 30;
> ++
> ++  for (i = 0; i < alloc_test_max_repeats; i++) {
> ++    g_allocation_count = i;
> ++    XML_SetParamEntityParsing(g_parser,
> XML_PARAM_ENTITY_PARSING_ALWAYS);
> ++    if (_XML_Parse_SINGLE_BYTES(g_parser, text, (int)strlen(text), XML_TRUE)
> ++        != XML_STATUS_ERROR)
> ++      break;
> ++    alloc_teardown();
> ++    alloc_setup();
> ++  }
> ++  g_allocation_count = -1;
> ++  if (i == 0)
> ++    fail("Parameter entity processed despite duff allocator");
> ++  if (i == alloc_test_max_repeats)
> ++    fail("Parameter entity not processed at max allocation count");
> ++}
> ++END_TEST
> ++
> + /* Test the robustness against allocation failure of element handling
> +  * Based on test_dtd_default_handling().
> +  */
> +@@ -2079,6 +2105,7 @@ make_alloc_test_case(Suite *s) {
> +   tcase_add_test__ifdef_xml_dtd(tc_alloc, test_alloc_external_entity);
> +   tcase_add_test__ifdef_xml_dtd(tc_alloc, test_alloc_ext_entity_set_encoding);
> +   tcase_add_test__ifdef_xml_dtd(tc_alloc, test_alloc_internal_entity);
> ++  tcase_add_test__ifdef_xml_dtd(tc_alloc, test_alloc_parameter_entity);
> +   tcase_add_test__ifdef_xml_dtd(tc_alloc, test_alloc_dtd_default_handling);
> +   tcase_add_test(tc_alloc, test_alloc_explicit_encoding);
> +   tcase_add_test(tc_alloc, test_alloc_set_base);
> +diff --git a/expat/tests/basic_tests.c b/expat/tests/basic_tests.c
> +index d2306772..29be32cf 100644
> +--- a/expat/tests/basic_tests.c
> ++++ b/expat/tests/basic_tests.c
> +@@ -10,7 +10,7 @@
> +    Copyright (c) 2003      Greg Stein <gstein@users.sourceforge.net>
> +    Copyright (c) 2005-2007 Steven Solie <steven@solie.ca>
> +    Copyright (c) 2005-2012 Karl Waclawek <karl@waclawek.net>
> +-   Copyright (c) 2016-2024 Sebastian Pipping <sebastian@pipping.org>
> ++   Copyright (c) 2016-2025 Sebastian Pipping <sebastian@pipping.org>
> +    Copyright (c) 2017-2022 Rhodri James <rhodri@wildebeest.org.uk>
> +    Copyright (c) 2017      Joe Orton <jorton@redhat.com>
> +    Copyright (c) 2017      José Gutiérrez de la Concha <jose@zeroc.com>
> +@@ -19,6 +19,7 @@
> +    Copyright (c) 2020      Tim Gates <tim.gates@iress.com>
> +    Copyright (c) 2021      Donghee Na <donghee.na@python.org>
> +    Copyright (c) 2023-2024 Sony Corporation / Snild Dolkow <snild@sony.com>
> ++   Copyright (c) 2024-2025 Berkay Eren Ürün <berkay.ueruen@siemens.com>
> +    Licensed under the MIT license:
> +
> +    Permission is  hereby granted,  free of charge,  to any  person obtaining
> +@@ -1233,44 +1234,58 @@
> START_TEST(test_no_indirectly_recursive_entity_refs) {
> +        "<doc/>\n",
> +        true},
> +   };
> ++  const XML_Bool reset_or_not[] = {XML_TRUE, XML_FALSE};
> ++
> +   for (size_t i = 0; i < sizeof(cases) / sizeof(cases[0]); i++) {
> +-    const char *const doc = cases[i].doc;
> +-    const bool usesParameterEntities = cases[i].usesParameterEntities;
> ++    for (size_t j = 0; j < sizeof(reset_or_not) / sizeof(reset_or_not[0]);
> ++         j++) {
> ++      const XML_Bool reset_wanted = reset_or_not[j];
> ++      const char *const doc = cases[i].doc;
> ++      const bool usesParameterEntities = cases[i].usesParameterEntities;
> +
> +-    set_subtest("[%i] %s", (int)i, doc);
> ++      set_subtest("[%i,reset=%i] %s", (int)i, (int)j, doc);
> +
> + #ifdef XML_DTD // both GE and DTD
> +-    const bool rejection_expected = true;
> ++      const bool rejection_expected = true;
> + #elif XML_GE == 1 // GE but not DTD
> +-    const bool rejection_expected = ! usesParameterEntities;
> ++      const bool rejection_expected = ! usesParameterEntities;
> + #else             // neither DTD nor GE
> +-    const bool rejection_expected = false;
> ++      const bool rejection_expected = false;
> + #endif
> +
> +-    XML_Parser parser = XML_ParserCreate(NULL);
> ++      XML_Parser parser = XML_ParserCreate(NULL);
> +
> + #ifdef XML_DTD
> +-    if (usesParameterEntities) {
> +-      assert_true(
> +-          XML_SetParamEntityParsing(parser,
> XML_PARAM_ENTITY_PARSING_ALWAYS)
> +-          == 1);
> +-    }
> ++      if (usesParameterEntities) {
> ++        assert_true(
> ++            XML_SetParamEntityParsing(parser,
> XML_PARAM_ENTITY_PARSING_ALWAYS)
> ++            == 1);
> ++      }
> + #else
> +-    UNUSED_P(usesParameterEntities);
> ++      UNUSED_P(usesParameterEntities);
> + #endif // XML_DTD
> +
> +-    const enum XML_Status status
> +-        = _XML_Parse_SINGLE_BYTES(parser, doc, (int)strlen(doc),
> +-                                  /*isFinal*/ XML_TRUE);
> ++      const enum XML_Status status
> ++          = _XML_Parse_SINGLE_BYTES(parser, doc, (int)strlen(doc),
> ++                                    /*isFinal*/ XML_TRUE);
> +
> +-    if (rejection_expected) {
> +-      assert_true(status == XML_STATUS_ERROR);
> +-      assert_true(XML_GetErrorCode(parser) ==
> XML_ERROR_RECURSIVE_ENTITY_REF);
> +-    } else {
> +-      assert_true(status == XML_STATUS_OK);
> ++      if (rejection_expected) {
> ++        assert_true(status == XML_STATUS_ERROR);
> ++        assert_true(XML_GetErrorCode(parser) ==
> XML_ERROR_RECURSIVE_ENTITY_REF);
> ++      } else {
> ++        assert_true(status == XML_STATUS_OK);
> ++      }
> ++
> ++      if (reset_wanted) {
> ++        // This covers free'ing of (eventually) all three open entity lists by
> ++        // XML_ParserReset.
> ++        XML_ParserReset(parser, NULL);
> ++      }
> ++
> ++      // This covers free'ing of (eventually) all three open entity lists by
> ++      // XML_ParserFree (unless XML_ParserReset has already done that above).
> ++      XML_ParserFree(parser);
> +     }
> +-
> +-    XML_ParserFree(parser);
> +   }
> + }
> + END_TEST
> +@@ -4033,7 +4048,7 @@ START_TEST(test_skipped_null_loaded_ext_entity) {
> +       = {"<!ENTITY % pe1 SYSTEM 'http://example.org/two.ent'>\n"
> +          "<!ENTITY % pe2 '%pe1;'>\n"
> +          "%pe2;\n",
> +-         external_entity_null_loader};
> ++         external_entity_null_loader, NULL};
> +
> +   XML_SetUserData(g_parser, &test_data);
> +   XML_SetParamEntityParsing(g_parser,
> XML_PARAM_ENTITY_PARSING_ALWAYS);
> +@@ -4051,7 +4066,7 @@ START_TEST(test_skipped_unloaded_ext_entity) {
> +       = {"<!ENTITY % pe1 SYSTEM 'http://example.org/two.ent'>\n"
> +          "<!ENTITY % pe2 '%pe1;'>\n"
> +          "%pe2;\n",
> +-         NULL};
> ++         NULL, NULL};
> +
> +   XML_SetUserData(g_parser, &test_data);
> +   XML_SetParamEntityParsing(g_parser,
> XML_PARAM_ENTITY_PARSING_ALWAYS);
> +@@ -5351,6 +5366,151 @@
> START_TEST(test_pool_integrity_with_unfinished_attr) {
> + }
> + END_TEST
> +
> ++/* Test a possible early return location in internalEntityProcessor */
> ++START_TEST(test_entity_ref_no_elements) {
> ++  const char *const text = "<!DOCTYPE foo [\n"
> ++                           "<!ENTITY e1 \"test\">\n"
> ++                           "]> <foo>&e1;"; // intentionally missing newline
> ++
> ++  XML_Parser parser = XML_ParserCreate(NULL);
> ++  assert_true(_XML_Parse_SINGLE_BYTES(parser, text, (int)strlen(text),
> XML_TRUE)
> ++              == XML_STATUS_ERROR);
> ++  assert_true(XML_GetErrorCode(parser) == XML_ERROR_NO_ELEMENTS);
> ++  XML_ParserFree(parser);
> ++}
> ++END_TEST
> ++
> ++/* Tests if chained entity references lead to unbounded recursion */
> ++START_TEST(test_deep_nested_entity) {
> ++  const size_t N_LINES = 60000;
> ++  const size_t SIZE_PER_LINE = 50;
> ++
> ++  char *const text = (char *)malloc((N_LINES + 4) * SIZE_PER_LINE);
> ++  if (text == NULL) {
> ++    fail("malloc failed");
> ++  }
> ++
> ++  char *textPtr = text;
> ++
> ++  // Create the XML
> ++  textPtr += snprintf(textPtr, SIZE_PER_LINE,
> ++                      "<!DOCTYPE foo [\n"
> ++                      "	<!ENTITY s0 'deepText'>\n");
> ++
> ++  for (size_t i = 1; i < N_LINES; ++i) {
> ++    textPtr += snprintf(textPtr, SIZE_PER_LINE, "  <!ENTITY s%lu '&s%lu;'>\n",
> ++                        (long unsigned)i, (long unsigned)(i - 1));
> ++  }
> ++
> ++  snprintf(textPtr, SIZE_PER_LINE, "]> <foo>&s%lu;</foo>\n",
> ++           (long unsigned)(N_LINES - 1));
> ++
> ++  const XML_Char *const expected = XCS("deepText");
> ++
> ++  CharData storage;
> ++  CharData_Init(&storage);
> ++
> ++  XML_Parser parser = XML_ParserCreate(NULL);
> ++
> ++  XML_SetCharacterDataHandler(parser, accumulate_characters);
> ++  XML_SetUserData(parser, &storage);
> ++
> ++  if (_XML_Parse_SINGLE_BYTES(parser, text, (int)strlen(text), XML_TRUE)
> ++      == XML_STATUS_ERROR)
> ++    xml_failure(parser);
> ++
> ++  CharData_CheckXMLChars(&storage, expected);
> ++  XML_ParserFree(parser);
> ++  free(text);
> ++}
> ++END_TEST
> ++
> ++/* Tests if chained entity references in attributes
> ++lead to unbounded recursion */
> ++START_TEST(test_deep_nested_attribute_entity) {
> ++  const size_t N_LINES = 60000;
> ++  const size_t SIZE_PER_LINE = 100;
> ++
> ++  char *const text = (char *)malloc((N_LINES + 4) * SIZE_PER_LINE);
> ++  if (text == NULL) {
> ++    fail("malloc failed");
> ++  }
> ++
> ++  char *textPtr = text;
> ++
> ++  // Create the XML
> ++  textPtr += snprintf(textPtr, SIZE_PER_LINE,
> ++                      "<!DOCTYPE foo [\n"
> ++                      "	<!ENTITY s0 'deepText'>\n");
> ++
> ++  for (size_t i = 1; i < N_LINES; ++i) {
> ++    textPtr += snprintf(textPtr, SIZE_PER_LINE, "  <!ENTITY s%lu '&s%lu;'>\n",
> ++                        (long unsigned)i, (long unsigned)(i - 1));
> ++  }
> ++
> ++  snprintf(textPtr, SIZE_PER_LINE, "]> <foo name='&s%lu;'>mainText</foo>\n",
> ++           (long unsigned)(N_LINES - 1));
> ++
> ++  AttrInfo doc_info[] = {{XCS("name"), XCS("deepText")}, {NULL, NULL}};
> ++  ElementInfo info[] = {{XCS("foo"), 1, NULL, NULL}, {NULL, 0, NULL, NULL}};
> ++  info[0].attributes = doc_info;
> ++
> ++  XML_Parser parser = XML_ParserCreate(NULL);
> ++  ParserAndElementInfo parserPlusElemenInfo = {parser, info};
> ++
> ++  XML_SetStartElementHandler(parser, counting_start_element_handler);
> ++  XML_SetUserData(parser, &parserPlusElemenInfo);
> ++
> ++  if (_XML_Parse_SINGLE_BYTES(parser, text, (int)strlen(text), XML_TRUE)
> ++      == XML_STATUS_ERROR)
> ++    xml_failure(parser);
> ++
> ++  XML_ParserFree(parser);
> ++  free(text);
> ++}
> ++END_TEST
> ++
> ++START_TEST(test_deep_nested_entity_delayed_interpretation) {
> ++  const size_t N_LINES = 70000;
> ++  const size_t SIZE_PER_LINE = 100;
> ++
> ++  char *const text = (char *)malloc((N_LINES + 4) * SIZE_PER_LINE);
> ++  if (text == NULL) {
> ++    fail("malloc failed");
> ++  }
> ++
> ++  char *textPtr = text;
> ++
> ++  // Create the XML
> ++  textPtr += snprintf(textPtr, SIZE_PER_LINE,
> ++                      "<!DOCTYPE foo [\n"
> ++                      "	<!ENTITY %% s0 'deepText'>\n");
> ++
> ++  for (size_t i = 1; i < N_LINES; ++i) {
> ++    textPtr += snprintf(textPtr, SIZE_PER_LINE,
> ++                        "  <!ENTITY %% s%lu '&#37;s%lu;'>\n", (long unsigned)i,
> ++                        (long unsigned)(i - 1));
> ++  }
> ++
> ++  snprintf(textPtr, SIZE_PER_LINE,
> ++           "  <!ENTITY %% define_g \"<!ENTITY g '&#37;s%lu;'>\">\n"
> ++           "  %%define_g;\n"
> ++           "]>\n"
> ++           "<foo/>\n",
> ++           (long unsigned)(N_LINES - 1));
> ++
> ++  XML_Parser parser = XML_ParserCreate(NULL);
> ++
> ++  XML_SetParamEntityParsing(parser,
> XML_PARAM_ENTITY_PARSING_ALWAYS);
> ++  if (_XML_Parse_SINGLE_BYTES(parser, text, (int)strlen(text), XML_TRUE)
> ++      == XML_STATUS_ERROR)
> ++    xml_failure(parser);
> ++
> ++  XML_ParserFree(parser);
> ++  free(text);
> ++}
> ++END_TEST
> ++
> + START_TEST(test_nested_entity_suspend) {
> +   const char *const text = "<!DOCTYPE a [\n"
> +                            "  <!ENTITY e1 '<!--e1-->'>\n"
> +@@ -5381,6 +5541,35 @@ START_TEST(test_nested_entity_suspend) {
> + }
> + END_TEST
> +
> ++START_TEST(test_nested_entity_suspend_2) {
> ++  const char *const text = "<!DOCTYPE doc [\n"
> ++                           "  <!ENTITY ge1 'head1Ztail1'>\n"
> ++                           "  <!ENTITY ge2 'head2&ge1;tail2'>\n"
> ++                           "  <!ENTITY ge3 'head3&ge2;tail3'>\n"
> ++                           "]>\n"
> ++                           "<doc>&ge3;</doc>";
> ++  const XML_Char *const expected = XCS("head3") XCS("head2") XCS("head1")
> ++      XCS("Z") XCS("tail1") XCS("tail2") XCS("tail3");
> ++  CharData storage;
> ++  CharData_Init(&storage);
> ++  XML_Parser parser = XML_ParserCreate(NULL);
> ++  ParserPlusStorage parserPlusStorage = {parser, &storage};
> ++
> ++  XML_SetCharacterDataHandler(parser, accumulate_char_data_and_suspend);
> ++  XML_SetUserData(parser, &parserPlusStorage);
> ++
> ++  enum XML_Status status = XML_Parse(parser, text, (int)strlen(text),
> XML_TRUE);
> ++  while (status == XML_STATUS_SUSPENDED) {
> ++    status = XML_ResumeParser(parser);
> ++  }
> ++  if (status != XML_STATUS_OK)
> ++    xml_failure(parser);
> ++
> ++  CharData_CheckXMLChars(&storage, expected);
> ++  XML_ParserFree(parser);
> ++}
> ++END_TEST
> ++
> + /* Regression test for quadratic parsing on large tokens */
> + START_TEST(test_big_tokens_scale_linearly) {
> +   const struct {
> +@@ -6221,7 +6410,13 @@ make_basic_test_case(Suite *s) {
> +   tcase_add_test(tc_basic, test_empty_element_abort);
> +   tcase_add_test__ifdef_xml_dtd(tc_basic,
> +                                 test_pool_integrity_with_unfinished_attr);
> ++  tcase_add_test__if_xml_ge(tc_basic, test_entity_ref_no_elements);
> ++  tcase_add_test__if_xml_ge(tc_basic, test_deep_nested_entity);
> ++  tcase_add_test__if_xml_ge(tc_basic, test_deep_nested_attribute_entity);
> ++  tcase_add_test__if_xml_ge(tc_basic,
> ++                            test_deep_nested_entity_delayed_interpretation);
> +   tcase_add_test__if_xml_ge(tc_basic, test_nested_entity_suspend);
> ++  tcase_add_test__if_xml_ge(tc_basic, test_nested_entity_suspend_2);
> +   tcase_add_test(tc_basic, test_big_tokens_scale_linearly);
> +   tcase_add_test(tc_basic, test_set_reparse_deferral);
> +   tcase_add_test(tc_basic, test_reparse_deferral_is_inherited);
> +diff --git a/expat/tests/handlers.c b/expat/tests/handlers.c
> +index 0211985f..f15029e3 100644
> +--- a/expat/tests/handlers.c
> ++++ b/expat/tests/handlers.c
> +@@ -1882,6 +1882,20 @@ accumulate_entity_decl(void *userData, const
> XML_Char *entityName,
> +   CharData_AppendXMLChars(storage, XCS("\n"), 1);
> + }
> +
> ++void XMLCALL
> ++accumulate_char_data_and_suspend(void *userData, const XML_Char *s, int
> len) {
> ++  ParserPlusStorage *const parserPlusStorage = (ParserPlusStorage *)userData;
> ++
> ++  CharData_AppendXMLChars(parserPlusStorage->storage, s, len);
> ++
> ++  for (int i = 0; i < len; i++) {
> ++    if (s[i] == 'Z') {
> ++      XML_StopParser(parserPlusStorage->parser, /*resumable=*/XML_TRUE);
> ++      break;
> ++    }
> ++  }
> ++}
> ++
> + void XMLCALL
> + accumulate_start_element(void *userData, const XML_Char *name,
> +                          const XML_Char **atts) {
> +diff --git a/expat/tests/handlers.h b/expat/tests/handlers.h
> +index 8850bb94..4d6a08d5 100644
> +--- a/expat/tests/handlers.h
> ++++ b/expat/tests/handlers.h
> +@@ -325,6 +325,7 @@ extern int XMLCALL external_entity_devaluer(XML_Parser
> parser,
> + typedef struct ext_hdlr_data {
> +   const char *parse_text;
> +   XML_ExternalEntityRefHandler handler;
> ++  CharData *storage;
> + } ExtHdlrData;
> +
> + extern int XMLCALL external_entity_oneshot_loader(XML_Parser parser,
> +@@ -569,6 +570,10 @@ extern void XMLCALL accumulate_entity_decl(
> +     const XML_Char *systemId, const XML_Char *publicId,
> +     const XML_Char *notationName);
> +
> ++extern void XMLCALL accumulate_char_data_and_suspend(void *userData,
> ++                                                     const XML_Char *s,
> ++                                                     int len);
> ++
> + extern void XMLCALL accumulate_start_element(void *userData,
> +                                              const XML_Char *name,
> +                                              const XML_Char **atts);
> +diff --git a/expat/tests/misc_tests.c b/expat/tests/misc_tests.c
> +index 9afe0922..f9a78f66 100644
> +--- a/expat/tests/misc_tests.c
> ++++ b/expat/tests/misc_tests.c
> +@@ -59,6 +59,9 @@
> + #include "handlers.h"
> + #include "misc_tests.h"
> +
> ++void XMLCALL accumulate_characters_ext_handler(void *userData,
> ++                                               const XML_Char *s, int len);
> ++
> + /* Test that a failure to allocate the parser structure fails gracefully */
> + START_TEST(test_misc_alloc_create_parser) {
> +   XML_Memory_Handling_Suite memsuite = {duff_allocator, realloc, free};
> +@@ -519,6 +522,45 @@
> START_TEST(test_misc_stopparser_rejects_unstarted_parser) {
> + }
> + END_TEST
> +
> ++/* Adaptation of accumulate_characters that takes ExtHdlrData input to work
> with
> ++ * test_renter_loop_finite_content below */
> ++void XMLCALL
> ++accumulate_characters_ext_handler(void *userData, const XML_Char *s, int len)
> {
> ++  ExtHdlrData *const test_data = (ExtHdlrData *)userData;
> ++  CharData_AppendXMLChars(test_data->storage, s, len);
> ++}
> ++
> ++/* Test that internalEntityProcessor does not re-enter forever;
> ++ * based on files tests/xmlconf/xmltest/valid/ext-sa/012.{xml,ent} */
> ++START_TEST(test_renter_loop_finite_content) {
> ++  CharData storage;
> ++  CharData_Init(&storage);
> ++  const char *const text = "<!DOCTYPE doc [\n"
> ++                           "<!ENTITY e1 '&e2;'>\n"
> ++                           "<!ENTITY e2 '&e3;'>\n"
> ++                           "<!ENTITY e3 SYSTEM '012.ent'>\n"
> ++                           "<!ENTITY e4 '&e5;'>\n"
> ++                           "<!ENTITY e5 '(e5)'>\n"
> ++                           "<!ELEMENT doc (#PCDATA)>\n"
> ++                           "]>\n"
> ++                           "<doc>&e1;</doc>\n";
> ++  ExtHdlrData test_data = {"&e4;\n", external_entity_null_loader, &storage};
> ++  const XML_Char *const expected = XCS("(e5)\n");
> ++
> ++  XML_Parser parser = XML_ParserCreate(NULL);
> ++  assert_true(parser != NULL);
> ++  XML_SetUserData(parser, &test_data);
> ++  XML_SetExternalEntityRefHandler(parser, external_entity_oneshot_loader);
> ++  XML_SetCharacterDataHandler(parser, accumulate_characters_ext_handler);
> ++  if (_XML_Parse_SINGLE_BYTES(parser, text, (int)strlen(text), XML_TRUE)
> ++      == XML_STATUS_ERROR)
> ++    xml_failure(parser);
> ++
> ++  CharData_CheckXMLChars(&storage, expected);
> ++  XML_ParserFree(parser);
> ++}
> ++END_TEST
> ++
> + void
> + make_miscellaneous_test_case(Suite *s) {
> +   TCase *tc_misc = tcase_create("miscellaneous tests");
> +@@ -545,4 +587,5 @@ make_miscellaneous_test_case(Suite *s) {
> +   tcase_add_test(tc_misc, test_misc_char_handler_stop_without_leak);
> +   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);
> + }
> diff --git a/meta/recipes-core/expat/expat_2.6.4.bb b/meta/recipes-
> core/expat/expat_2.6.4.bb
> index f383792793..1da7141025 100644
> --- a/meta/recipes-core/expat/expat_2.6.4.bb
> +++ b/meta/recipes-core/expat/expat_2.6.4.bb
> @@ -10,6 +10,8 @@ VERSION_TAG = "${@d.getVar('PV').replace('.', '_')}"
> 
>  SRC_URI = "${GITHUB_BASE_URI}/download/R_${VERSION_TAG}/expat-
> ${PV}.tar.bz2  \
>             file://run-ptest \
> +           file://0001-tests-Cover-indirect-entity-recursion.patch;striplevel=2 \
> +           file://CVE-2024-8176.patch;striplevel=2 \
>             "
> 
>  GITHUB_BASE_URI = "https://github.com/libexpat/libexpat/releases/"
Richard Purdie March 20, 2025, 3:36 p.m. UTC | #2
On Thu, 2025-03-20 at 15:28 +0000, Peter Marko via
lists.openembedded.org wrote:
> Please drop this patch for now.
> The CVE fix causes a regression, see
> https://github.com/libexpat/libexpat/issues/980
> I'll submit a new patch when the regression is fixed.
> 
> Expat 2.7.0 was already merged in master.
> I think we can live with it and hope for a quick fix.
> I'll be observing the regression discussions.
> 

The expat maintainer did comment privately that this a complex fix and
not well suited to backporting. We may want to consider a version bump
for expat for this, once the issues are resolved.

Cheers,

Richard
Marko, Peter March 20, 2025, 5:50 p.m. UTC | #3
> -----Original Message-----
> From: Richard Purdie <richard.purdie@linuxfoundation.org>
> Sent: Thursday, March 20, 2025 16:37
> To: Marko, Peter (FT D EU SK BFS1) <Peter.Marko@siemens.com>;
> openembedded-core@lists.openembedded.org
> Subject: Re: [OE-core][scarthgap][PATCH] expat: patch CVE-2024-8176
> 
> On Thu, 2025-03-20 at 15:28 +0000, Peter Marko via
> lists.openembedded.org wrote:
> > Please drop this patch for now.
> > The CVE fix causes a regression, see
> > https://github.com/libexpat/libexpat/issues/980
> > I'll submit a new patch when the regression is fixed.
> >
> > Expat 2.7.0 was already merged in master.
> > I think we can live with it and hope for a quick fix.
> > I'll be observing the regression discussions.
> >
> 
> The expat maintainer did comment privately that this a complex fix and
> not well suited to backporting. We may want to consider a version bump
> for expat for this, once the issues are resolved.
> 
> Cheers,
> 
> Richard

2.7.0 is the first release after 2.6.4 and libexpat is not very active project anymore.
So this backport does not pose much risk as there were also no conflicts so I'd say breaking LTS rules is not needed (yet?).

Kirkstone is in much worse shape, and I didn’t plan to backport it there due to complexity anyway.
There already is also CVE-2023-52425 with the same HIGH score of 7.5 which was too complex to backport (there already were some tries).
So if we are open to upgrade, we should consider doing it in kirkstone to fix two high severity CVEs which cannot really be patched.

Peter
diff mbox series

Patch

diff --git a/meta/recipes-core/expat/expat/0001-tests-Cover-indirect-entity-recursion.patch b/meta/recipes-core/expat/expat/0001-tests-Cover-indirect-entity-recursion.patch
new file mode 100644
index 0000000000..802d762787
--- /dev/null
+++ b/meta/recipes-core/expat/expat/0001-tests-Cover-indirect-entity-recursion.patch
@@ -0,0 +1,103 @@ 
+From 3d5fdbb44e80ed789e4f6510542d77d6284fbd0e Mon Sep 17 00:00:00 2001
+From: Sebastian Pipping <sebastian@pipping.org>
+Date: Sat, 23 Nov 2024 14:20:21 +0100
+Subject: [PATCH] tests: Cover indirect entity recursion
+
+Upstream-Status: Backport [https://github.com/libexpat/libexpat/commit/3d5fdbb44e80ed789e4f6510542d77d6284fbd0e]
+Signed-off-by: Peter Marko <peter.marko@siemens.com>
+---
+ expat/tests/basic_tests.c | 74 +++++++++++++++++++++++++++++++++++++++
+ 1 file changed, 74 insertions(+)
+
+diff --git a/expat/tests/basic_tests.c b/expat/tests/basic_tests.c
+index d38b8fd1..d2306772 100644
+--- a/expat/tests/basic_tests.c
++++ b/expat/tests/basic_tests.c
+@@ -1202,6 +1202,79 @@ START_TEST(test_wfc_no_recursive_entity_refs) {
+ }
+ END_TEST
+ 
++START_TEST(test_no_indirectly_recursive_entity_refs) {
++  struct TestCase {
++    const char *doc;
++    bool usesParameterEntities;
++  };
++
++  const struct TestCase cases[] = {
++      // general entity + character data
++      {"<!DOCTYPE a [\n"
++       "  <!ENTITY e1 '&e2;'>\n"
++       "  <!ENTITY e2 '&e1;'>\n"
++       "]><a>&e2;</a>\n",
++       false},
++
++      // general entity + attribute value
++      {"<!DOCTYPE a [\n"
++       "  <!ENTITY e1 '&e2;'>\n"
++       "  <!ENTITY e2 '&e1;'>\n"
++       "]><a k1='&e2;' />\n",
++       false},
++
++      // parameter entity
++      {"<!DOCTYPE doc [\n"
++       "  <!ENTITY % p1 '&#37;p2;'>\n"
++       "  <!ENTITY % p2 '&#37;p1;'>\n"
++       "  <!ENTITY % define_g \"<!ENTITY g '&#37;p2;'>\">\n"
++       "  %define_g;\n"
++       "]>\n"
++       "<doc/>\n",
++       true},
++  };
++  for (size_t i = 0; i < sizeof(cases) / sizeof(cases[0]); i++) {
++    const char *const doc = cases[i].doc;
++    const bool usesParameterEntities = cases[i].usesParameterEntities;
++
++    set_subtest("[%i] %s", (int)i, doc);
++
++#ifdef XML_DTD // both GE and DTD
++    const bool rejection_expected = true;
++#elif XML_GE == 1 // GE but not DTD
++    const bool rejection_expected = ! usesParameterEntities;
++#else             // neither DTD nor GE
++    const bool rejection_expected = false;
++#endif
++
++    XML_Parser parser = XML_ParserCreate(NULL);
++
++#ifdef XML_DTD
++    if (usesParameterEntities) {
++      assert_true(
++          XML_SetParamEntityParsing(parser, XML_PARAM_ENTITY_PARSING_ALWAYS)
++          == 1);
++    }
++#else
++    UNUSED_P(usesParameterEntities);
++#endif // XML_DTD
++
++    const enum XML_Status status
++        = _XML_Parse_SINGLE_BYTES(parser, doc, (int)strlen(doc),
++                                  /*isFinal*/ XML_TRUE);
++
++    if (rejection_expected) {
++      assert_true(status == XML_STATUS_ERROR);
++      assert_true(XML_GetErrorCode(parser) == XML_ERROR_RECURSIVE_ENTITY_REF);
++    } else {
++      assert_true(status == XML_STATUS_OK);
++    }
++
++    XML_ParserFree(parser);
++  }
++}
++END_TEST
++
+ START_TEST(test_recursive_external_parameter_entity_2) {
+   struct TestCase {
+     const char *doc;
+@@ -5969,6 +6042,7 @@ make_basic_test_case(Suite *s) {
+   tcase_add_test(tc_basic, test_not_standalone_handler_reject);
+   tcase_add_test(tc_basic, test_not_standalone_handler_accept);
+   tcase_add_test__if_xml_ge(tc_basic, test_wfc_no_recursive_entity_refs);
++  tcase_add_test(tc_basic, test_no_indirectly_recursive_entity_refs);
+   tcase_add_test__ifdef_xml_dtd(tc_basic, test_ext_entity_invalid_parse);
+   tcase_add_test__if_xml_ge(tc_basic, test_dtd_default_handling);
+   tcase_add_test(tc_basic, test_dtd_attr_handling);
diff --git a/meta/recipes-core/expat/expat/CVE-2024-8176.patch b/meta/recipes-core/expat/expat/CVE-2024-8176.patch
new file mode 100644
index 0000000000..dc8a520161
--- /dev/null
+++ b/meta/recipes-core/expat/expat/CVE-2024-8176.patch
@@ -0,0 +1,1477 @@ 
+From 3f924a715cfa97e70df1c24334d2d728973d1020 Mon Sep 17 00:00:00 2001
+From: Peter Marko <peter.marko@siemens.com>
+Date: Mon, 17 Mar 2025 20:41:24 +0100
+Subject: [PATCH] [CVE-2024-8176] Resolve the recursion during entity
+ processing to prevent stack overflow (fixes #893)
+
+Fixes #893
+
+CVE: CVE-2024-8176
+Upstream-Status: Backport [https://github.com/libexpat/libexpat/pull/973]
+Signed-off-by: Peter Marko <peter.marko@siemens.com>
+---
+ expat/Changes             |  29 +-
+ expat/lib/xmlparse.c      | 564 ++++++++++++++++++++++++++++----------
+ expat/tests/alloc_tests.c |  27 ++
+ expat/tests/basic_tests.c | 247 +++++++++++++++--
+ expat/tests/handlers.c    |  14 +
+ expat/tests/handlers.h    |   5 +
+ expat/tests/misc_tests.c  |  43 +++
+ 7 files changed, 751 insertions(+), 178 deletions(-)
+
+diff --git a/expat/Changes b/expat/Changes
+index aa19f70a..8c5db88c 100644
+--- a/expat/Changes
++++ b/expat/Changes
+@@ -11,7 +11,6 @@
+ !! The following topics need *additional skilled C developers* to progress   !!
+ !! in a timely manner or at all (loosely ordered by descending priority):    !!
+ !!                                                                           !!
+-!! - <blink>fixing a complex non-public security issue</blink>,              !!
+ !! - teaming up on researching and fixing future security reports and        !!
+ !!   ClusterFuzz findings with few-days-max response times in communication  !!
+ !!   in order to (1) have a sound fix ready before the end of a 90 days      !!
+@@ -30,6 +29,34 @@
+ !! THANK YOU!                        Sebastian Pipping -- Berlin, 2024-03-09 !!
+ !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
+ 
++Patches:
++        Security fixes:
++       #893 #???  CVE-2024-8176 -- Fix crash from chaining a large number
++                    of entities caused by stack overflow by resolving use of
++                    recursion, for all three uses of entities:
++                    - general entities in character data ("<e>&g1;</e>")
++                    - general entities in attribute values ("<e k1='&g1;'/>")
++                    - parameter entities ("%p1;")
++                    Known impact is (reliable and easy) denial of service:
++                    CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H/E:H/RL:O/RC:C
++                    (Base Score: 7.5, Temporal Score: 7.2)
++                    Please note that a layer of compression around XML can
++                    significantly reduce the minimum attack payload size.
++
++         Special thanks to:
++            Alexander Gieringer
++            Berkay Eren Ürün
++            Jann Horn
++            Sebastian Andrzej Siewior
++            Snild Dolkow
++            Thomas Pröll
++            Tomas Korbar
++                 and
++            Google Project Zero
++            Linutronix
++            Red Hat
++            Siemens
++
+ Release 2.6.4 Wed November 6 2024
+         Security fixes:
+             #915  CVE-2024-50602 -- Fix crash within function XML_ResumeParser
+diff --git a/expat/lib/xmlparse.c b/expat/lib/xmlparse.c
+index a4e091e7..473c791d 100644
+--- a/expat/lib/xmlparse.c
++++ b/expat/lib/xmlparse.c
+@@ -39,7 +39,7 @@
+    Copyright (c) 2022      Sean McBride <sean@rogue-research.com>
+    Copyright (c) 2023      Owain Davies <owaind@bath.edu>
+    Copyright (c) 2023-2024 Sony Corporation / Snild Dolkow <snild@sony.com>
+-   Copyright (c) 2024      Berkay Eren Ürün <berkay.ueruen@siemens.com>
++   Copyright (c) 2024-2025 Berkay Eren Ürün <berkay.ueruen@siemens.com>
+    Copyright (c) 2024      Hanno Böck <hanno@gentoo.org>
+    Licensed under the MIT license:
+ 
+@@ -325,6 +325,10 @@ typedef struct {
+   const XML_Char *publicId;
+   const XML_Char *notation;
+   XML_Bool open;
++  XML_Bool hasMore; /* true if entity has not been completely processed */
++  /* An entity can be open while being already completely processed (hasMore ==
++    XML_FALSE). The reason is the delayed closing of entities until their inner
++    entities are processed and closed */
+   XML_Bool is_param;
+   XML_Bool is_internal; /* true if declared in internal subset outside PE */
+ } ENTITY;
+@@ -415,6 +419,12 @@ typedef struct {
+   int *scaffIndex;
+ } DTD;
+ 
++enum EntityType {
++  ENTITY_INTERNAL,
++  ENTITY_ATTRIBUTE,
++  ENTITY_VALUE,
++};
++
+ typedef struct open_internal_entity {
+   const char *internalEventPtr;
+   const char *internalEventEndPtr;
+@@ -422,6 +432,7 @@ typedef struct open_internal_entity {
+   ENTITY *entity;
+   int startTagLevel;
+   XML_Bool betweenDecl; /* WFC: PE Between Declarations */
++  enum EntityType type;
+ } OPEN_INTERNAL_ENTITY;
+ 
+ enum XML_Account {
+@@ -481,8 +492,8 @@ static enum XML_Error doProlog(XML_Parser parser, const ENCODING *enc,
+                                const char *next, const char **nextPtr,
+                                XML_Bool haveMore, XML_Bool allowClosingDoctype,
+                                enum XML_Account account);
+-static enum XML_Error processInternalEntity(XML_Parser parser, ENTITY *entity,
+-                                            XML_Bool betweenDecl);
++static enum XML_Error processEntity(XML_Parser parser, ENTITY *entity,
++                                    XML_Bool betweenDecl, enum EntityType type);
+ static enum XML_Error doContent(XML_Parser parser, int startTagLevel,
+                                 const ENCODING *enc, const char *start,
+                                 const char *end, const char **endPtr,
+@@ -513,18 +524,22 @@ static enum XML_Error storeAttributeValue(XML_Parser parser,
+                                           const char *ptr, const char *end,
+                                           STRING_POOL *pool,
+                                           enum XML_Account account);
+-static enum XML_Error appendAttributeValue(XML_Parser parser,
+-                                           const ENCODING *enc,
+-                                           XML_Bool isCdata, const char *ptr,
+-                                           const char *end, STRING_POOL *pool,
+-                                           enum XML_Account account);
++static enum XML_Error
++appendAttributeValue(XML_Parser parser, const ENCODING *enc, XML_Bool isCdata,
++                     const char *ptr, const char *end, STRING_POOL *pool,
++                     enum XML_Account account, const char **nextPtr);
+ static ATTRIBUTE_ID *getAttributeId(XML_Parser parser, const ENCODING *enc,
+                                     const char *start, const char *end);
+ static int setElementTypePrefix(XML_Parser parser, ELEMENT_TYPE *elementType);
+ #if XML_GE == 1
+ static enum XML_Error storeEntityValue(XML_Parser parser, const ENCODING *enc,
+                                        const char *start, const char *end,
+-                                       enum XML_Account account);
++                                       enum XML_Account account,
++                                       const char **nextPtr);
++static enum XML_Error callStoreEntityValue(XML_Parser parser,
++                                           const ENCODING *enc,
++                                           const char *start, const char *end,
++                                           enum XML_Account account);
+ #else
+ static enum XML_Error storeSelfEntityValue(XML_Parser parser, ENTITY *entity);
+ #endif
+@@ -709,6 +724,10 @@ struct XML_ParserStruct {
+   const char *m_positionPtr;
+   OPEN_INTERNAL_ENTITY *m_openInternalEntities;
+   OPEN_INTERNAL_ENTITY *m_freeInternalEntities;
++  OPEN_INTERNAL_ENTITY *m_openAttributeEntities;
++  OPEN_INTERNAL_ENTITY *m_freeAttributeEntities;
++  OPEN_INTERNAL_ENTITY *m_openValueEntities;
++  OPEN_INTERNAL_ENTITY *m_freeValueEntities;
+   XML_Bool m_defaultExpandInternalEntities;
+   int m_tagLevel;
+   ENTITY *m_declEntity;
+@@ -756,6 +775,7 @@ struct XML_ParserStruct {
+   ACCOUNTING m_accounting;
+   ENTITY_STATS m_entity_stats;
+ #endif
++  XML_Bool m_reenter;
+ };
+ 
+ #define MALLOC(parser, s) (parser->m_mem.malloc_fcn((s)))
+@@ -1028,7 +1048,29 @@ callProcessor(XML_Parser parser, const char *start, const char *end,
+ #if defined(XML_TESTING)
+   g_bytesScanned += (unsigned)have_now;
+ #endif
+-  const enum XML_Error ret = parser->m_processor(parser, start, end, endPtr);
++  // Run in a loop to eliminate dangerous recursion depths
++  enum XML_Error ret;
++  *endPtr = start;
++  while (1) {
++    // Use endPtr as the new start in each iteration, since it will
++    // be set to the next start point by m_processor.
++    ret = parser->m_processor(parser, *endPtr, end, endPtr);
++
++    // Make parsing status (and in particular XML_SUSPENDED) take
++    // precedence over re-enter flag when they disagree
++    if (parser->m_parsingStatus.parsing != XML_PARSING) {
++      parser->m_reenter = XML_FALSE;
++    }
++
++    if (! parser->m_reenter) {
++      break;
++    }
++
++    parser->m_reenter = XML_FALSE;
++    if (ret != XML_ERROR_NONE)
++      return ret;
++  }
++
+   if (ret == XML_ERROR_NONE) {
+     // if we consumed nothing, remember what we had on this parse attempt.
+     if (*endPtr == start) {
+@@ -1139,6 +1181,8 @@ parserCreate(const XML_Char *encodingName,
+   parser->m_freeBindingList = NULL;
+   parser->m_freeTagList = NULL;
+   parser->m_freeInternalEntities = NULL;
++  parser->m_freeAttributeEntities = NULL;
++  parser->m_freeValueEntities = NULL;
+ 
+   parser->m_groupSize = 0;
+   parser->m_groupConnector = NULL;
+@@ -1241,6 +1285,8 @@ parserInit(XML_Parser parser, const XML_Char *encodingName) {
+   parser->m_eventEndPtr = NULL;
+   parser->m_positionPtr = NULL;
+   parser->m_openInternalEntities = NULL;
++  parser->m_openAttributeEntities = NULL;
++  parser->m_openValueEntities = NULL;
+   parser->m_defaultExpandInternalEntities = XML_TRUE;
+   parser->m_tagLevel = 0;
+   parser->m_tagStack = NULL;
+@@ -1251,6 +1297,8 @@ parserInit(XML_Parser parser, const XML_Char *encodingName) {
+   parser->m_unknownEncodingData = NULL;
+   parser->m_parentParser = NULL;
+   parser->m_parsingStatus.parsing = XML_INITIALIZED;
++  // Reentry can only be triggered inside m_processor calls
++  parser->m_reenter = XML_FALSE;
+ #ifdef XML_DTD
+   parser->m_isParamEntity = XML_FALSE;
+   parser->m_useForeignDTD = XML_FALSE;
+@@ -1310,6 +1358,24 @@ XML_ParserReset(XML_Parser parser, const XML_Char *encodingName) {
+     openEntity->next = parser->m_freeInternalEntities;
+     parser->m_freeInternalEntities = openEntity;
+   }
++  /* move m_openAttributeEntities to m_freeAttributeEntities (i.e. same task but
++   * for attributes) */
++  openEntityList = parser->m_openAttributeEntities;
++  while (openEntityList) {
++    OPEN_INTERNAL_ENTITY *openEntity = openEntityList;
++    openEntityList = openEntity->next;
++    openEntity->next = parser->m_freeAttributeEntities;
++    parser->m_freeAttributeEntities = openEntity;
++  }
++  /* move m_openValueEntities to m_freeValueEntities (i.e. same task but
++   * for value entities) */
++  openEntityList = parser->m_openValueEntities;
++  while (openEntityList) {
++    OPEN_INTERNAL_ENTITY *openEntity = openEntityList;
++    openEntityList = openEntity->next;
++    openEntity->next = parser->m_freeValueEntities;
++    parser->m_freeValueEntities = openEntity;
++  }
+   moveToFreeBindingList(parser, parser->m_inheritedBindings);
+   FREE(parser, parser->m_unknownEncodingMem);
+   if (parser->m_unknownEncodingRelease)
+@@ -1323,6 +1389,19 @@ XML_ParserReset(XML_Parser parser, const XML_Char *encodingName) {
+   return XML_TRUE;
+ }
+ 
++static XML_Bool
++parserBusy(XML_Parser parser) {
++  switch (parser->m_parsingStatus.parsing) {
++  case XML_PARSING:
++  case XML_SUSPENDED:
++    return XML_TRUE;
++  case XML_INITIALIZED:
++  case XML_FINISHED:
++  default:
++    return XML_FALSE;
++  }
++}
++
+ enum XML_Status XMLCALL
+ XML_SetEncoding(XML_Parser parser, const XML_Char *encodingName) {
+   if (parser == NULL)
+@@ -1331,8 +1410,7 @@ XML_SetEncoding(XML_Parser parser, const XML_Char *encodingName) {
+      XXX There's no way for the caller to determine which of the
+      XXX possible error cases caused the XML_STATUS_ERROR return.
+   */
+-  if (parser->m_parsingStatus.parsing == XML_PARSING
+-      || parser->m_parsingStatus.parsing == XML_SUSPENDED)
++  if (parserBusy(parser))
+     return XML_STATUS_ERROR;
+ 
+   /* Get rid of any previous encoding name */
+@@ -1569,7 +1647,34 @@ XML_ParserFree(XML_Parser parser) {
+     entityList = entityList->next;
+     FREE(parser, openEntity);
+   }
+-
++  /* free m_openAttributeEntities and m_freeAttributeEntities */
++  entityList = parser->m_openAttributeEntities;
++  for (;;) {
++    OPEN_INTERNAL_ENTITY *openEntity;
++    if (entityList == NULL) {
++      if (parser->m_freeAttributeEntities == NULL)
++        break;
++      entityList = parser->m_freeAttributeEntities;
++      parser->m_freeAttributeEntities = NULL;
++    }
++    openEntity = entityList;
++    entityList = entityList->next;
++    FREE(parser, openEntity);
++  }
++  /* free m_openValueEntities and m_freeValueEntities */
++  entityList = parser->m_openValueEntities;
++  for (;;) {
++    OPEN_INTERNAL_ENTITY *openEntity;
++    if (entityList == NULL) {
++      if (parser->m_freeValueEntities == NULL)
++        break;
++      entityList = parser->m_freeValueEntities;
++      parser->m_freeValueEntities = NULL;
++    }
++    openEntity = entityList;
++    entityList = entityList->next;
++    FREE(parser, openEntity);
++  }
+   destroyBindings(parser->m_freeBindingList, parser);
+   destroyBindings(parser->m_inheritedBindings, parser);
+   poolDestroy(&parser->m_tempPool);
+@@ -1611,8 +1716,7 @@ XML_UseForeignDTD(XML_Parser parser, XML_Bool useDTD) {
+     return XML_ERROR_INVALID_ARGUMENT;
+ #ifdef XML_DTD
+   /* block after XML_Parse()/XML_ParseBuffer() has been called */
+-  if (parser->m_parsingStatus.parsing == XML_PARSING
+-      || parser->m_parsingStatus.parsing == XML_SUSPENDED)
++  if (parserBusy(parser))
+     return XML_ERROR_CANT_CHANGE_FEATURE_ONCE_PARSING;
+   parser->m_useForeignDTD = useDTD;
+   return XML_ERROR_NONE;
+@@ -1627,8 +1731,7 @@ XML_SetReturnNSTriplet(XML_Parser parser, int do_nst) {
+   if (parser == NULL)
+     return;
+   /* block after XML_Parse()/XML_ParseBuffer() has been called */
+-  if (parser->m_parsingStatus.parsing == XML_PARSING
+-      || parser->m_parsingStatus.parsing == XML_SUSPENDED)
++  if (parserBusy(parser))
+     return;
+   parser->m_ns_triplets = do_nst ? XML_TRUE : XML_FALSE;
+ }
+@@ -1897,8 +2000,7 @@ XML_SetParamEntityParsing(XML_Parser parser,
+   if (parser == NULL)
+     return 0;
+   /* block after XML_Parse()/XML_ParseBuffer() has been called */
+-  if (parser->m_parsingStatus.parsing == XML_PARSING
+-      || parser->m_parsingStatus.parsing == XML_SUSPENDED)
++  if (parserBusy(parser))
+     return 0;
+ #ifdef XML_DTD
+   parser->m_paramEntityParsing = peParsing;
+@@ -1915,8 +2017,7 @@ XML_SetHashSalt(XML_Parser parser, unsigned long hash_salt) {
+   if (parser->m_parentParser)
+     return XML_SetHashSalt(parser->m_parentParser, hash_salt);
+   /* block after XML_Parse()/XML_ParseBuffer() has been called */
+-  if (parser->m_parsingStatus.parsing == XML_PARSING
+-      || parser->m_parsingStatus.parsing == XML_SUSPENDED)
++  if (parserBusy(parser))
+     return 0;
+   parser->m_hash_secret_salt = hash_salt;
+   return 1;
+@@ -2230,6 +2331,11 @@ XML_GetBuffer(XML_Parser parser, int len) {
+   return parser->m_bufferEnd;
+ }
+ 
++static void
++triggerReenter(XML_Parser parser) {
++  parser->m_reenter = XML_TRUE;
++}
++
+ enum XML_Status XMLCALL
+ XML_StopParser(XML_Parser parser, XML_Bool resumable) {
+   if (parser == NULL)
+@@ -2704,8 +2810,9 @@ static enum XML_Error PTRCALL
+ contentProcessor(XML_Parser parser, const char *start, const char *end,
+                  const char **endPtr) {
+   enum XML_Error result = doContent(
+-      parser, 0, parser->m_encoding, start, end, endPtr,
+-      (XML_Bool)! parser->m_parsingStatus.finalBuffer, XML_ACCOUNT_DIRECT);
++      parser, parser->m_parentParser ? 1 : 0, parser->m_encoding, start, end,
++      endPtr, (XML_Bool)! parser->m_parsingStatus.finalBuffer,
++      XML_ACCOUNT_DIRECT);
+   if (result == XML_ERROR_NONE) {
+     if (! storeRawNames(parser))
+       return XML_ERROR_NO_MEMORY;
+@@ -2793,6 +2900,11 @@ externalEntityInitProcessor3(XML_Parser parser, const char *start,
+       return XML_ERROR_NONE;
+     case XML_FINISHED:
+       return XML_ERROR_ABORTED;
++    case XML_PARSING:
++      if (parser->m_reenter) {
++        return XML_ERROR_UNEXPECTED_STATE; // LCOV_EXCL_LINE
++      }
++      /* Fall through */
+     default:
+       start = next;
+     }
+@@ -2966,7 +3078,7 @@ doContent(XML_Parser parser, int startTagLevel, const ENCODING *enc,
+             reportDefault(parser, enc, s, next);
+           break;
+         }
+-        result = processInternalEntity(parser, entity, XML_FALSE);
++        result = processEntity(parser, entity, XML_FALSE, ENTITY_INTERNAL);
+         if (result != XML_ERROR_NONE)
+           return result;
+       } else if (parser->m_externalEntityRefHandler) {
+@@ -3092,7 +3204,9 @@ doContent(XML_Parser parser, int startTagLevel, const ENCODING *enc,
+     }
+       if ((parser->m_tagLevel == 0)
+           && (parser->m_parsingStatus.parsing != XML_FINISHED)) {
+-        if (parser->m_parsingStatus.parsing == XML_SUSPENDED)
++        if (parser->m_parsingStatus.parsing == XML_SUSPENDED
++            || (parser->m_parsingStatus.parsing == XML_PARSING
++                && parser->m_reenter))
+           parser->m_processor = epilogProcessor;
+         else
+           return epilogProcessor(parser, next, end, nextPtr);
+@@ -3153,7 +3267,9 @@ doContent(XML_Parser parser, int startTagLevel, const ENCODING *enc,
+         }
+         if ((parser->m_tagLevel == 0)
+             && (parser->m_parsingStatus.parsing != XML_FINISHED)) {
+-          if (parser->m_parsingStatus.parsing == XML_SUSPENDED)
++          if (parser->m_parsingStatus.parsing == XML_SUSPENDED
++              || (parser->m_parsingStatus.parsing == XML_PARSING
++                  && parser->m_reenter))
+             parser->m_processor = epilogProcessor;
+           else
+             return epilogProcessor(parser, next, end, nextPtr);
+@@ -3293,6 +3409,12 @@ doContent(XML_Parser parser, int startTagLevel, const ENCODING *enc,
+       return XML_ERROR_NONE;
+     case XML_FINISHED:
+       return XML_ERROR_ABORTED;
++    case XML_PARSING:
++      if (parser->m_reenter) {
++        *nextPtr = next;
++        return XML_ERROR_NONE;
++      }
++      /* Fall through */
+     default:;
+     }
+   }
+@@ -4217,6 +4339,11 @@ doCdataSection(XML_Parser parser, const ENCODING *enc, const char **startPtr,
+       return XML_ERROR_NONE;
+     case XML_FINISHED:
+       return XML_ERROR_ABORTED;
++    case XML_PARSING:
++      if (parser->m_reenter) {
++        return XML_ERROR_UNEXPECTED_STATE; // LCOV_EXCL_LINE
++      }
++      /* Fall through */
+     default:;
+     }
+   }
+@@ -4549,7 +4676,7 @@ entityValueInitProcessor(XML_Parser parser, const char *s, const char *end,
+       }
+       /* found end of entity value - can store it now */
+       return storeEntityValue(parser, parser->m_encoding, s, end,
+-                              XML_ACCOUNT_DIRECT);
++                              XML_ACCOUNT_DIRECT, NULL);
+     } else if (tok == XML_TOK_XML_DECL) {
+       enum XML_Error result;
+       result = processXmlDecl(parser, 0, start, next);
+@@ -4676,7 +4803,7 @@ entityValueProcessor(XML_Parser parser, const char *s, const char *end,
+         break;
+       }
+       /* found end of entity value - can store it now */
+-      return storeEntityValue(parser, enc, s, end, XML_ACCOUNT_DIRECT);
++      return storeEntityValue(parser, enc, s, end, XML_ACCOUNT_DIRECT, NULL);
+     }
+     start = next;
+   }
+@@ -5119,9 +5246,9 @@ doProlog(XML_Parser parser, const ENCODING *enc, const char *s, const char *end,
+ #if XML_GE == 1
+         // This will store the given replacement text in
+         // parser->m_declEntity->textPtr.
+-        enum XML_Error result
+-            = storeEntityValue(parser, enc, s + enc->minBytesPerChar,
+-                               next - enc->minBytesPerChar, XML_ACCOUNT_NONE);
++        enum XML_Error result = callStoreEntityValue(
++            parser, enc, s + enc->minBytesPerChar, next - enc->minBytesPerChar,
++            XML_ACCOUNT_NONE);
+         if (parser->m_declEntity) {
+           parser->m_declEntity->textPtr = poolStart(&dtd->entityValuePool);
+           parser->m_declEntity->textLen
+@@ -5546,7 +5673,7 @@ doProlog(XML_Parser parser, const ENCODING *enc, const char *s, const char *end,
+           enum XML_Error result;
+           XML_Bool betweenDecl
+               = (role == XML_ROLE_PARAM_ENTITY_REF ? XML_TRUE : XML_FALSE);
+-          result = processInternalEntity(parser, entity, betweenDecl);
++          result = processEntity(parser, entity, betweenDecl, ENTITY_INTERNAL);
+           if (result != XML_ERROR_NONE)
+             return result;
+           handleDefault = XML_FALSE;
+@@ -5751,6 +5878,12 @@ doProlog(XML_Parser parser, const ENCODING *enc, const char *s, const char *end,
+       return XML_ERROR_NONE;
+     case XML_FINISHED:
+       return XML_ERROR_ABORTED;
++    case XML_PARSING:
++      if (parser->m_reenter) {
++        *nextPtr = next;
++        return XML_ERROR_NONE;
++      }
++    /* Fall through */
+     default:
+       s = next;
+       tok = XmlPrologTok(enc, s, end, &next);
+@@ -5825,21 +5958,49 @@ epilogProcessor(XML_Parser parser, const char *s, const char *end,
+       return XML_ERROR_NONE;
+     case XML_FINISHED:
+       return XML_ERROR_ABORTED;
++    case XML_PARSING:
++      if (parser->m_reenter) {
++        return XML_ERROR_UNEXPECTED_STATE; // LCOV_EXCL_LINE
++      }
++    /* Fall through */
+     default:;
+     }
+   }
+ }
+ 
+ static enum XML_Error
+-processInternalEntity(XML_Parser parser, ENTITY *entity, XML_Bool betweenDecl) {
+-  const char *textStart, *textEnd;
+-  const char *next;
+-  enum XML_Error result;
+-  OPEN_INTERNAL_ENTITY *openEntity;
++processEntity(XML_Parser parser, ENTITY *entity, XML_Bool betweenDecl,
++              enum EntityType type) {
++  OPEN_INTERNAL_ENTITY *openEntity, **openEntityList, **freeEntityList;
++  switch (type) {
++  case ENTITY_INTERNAL:
++    parser->m_processor = internalEntityProcessor;
++    openEntityList = &parser->m_openInternalEntities;
++    freeEntityList = &parser->m_freeInternalEntities;
++    break;
++  case ENTITY_ATTRIBUTE:
++    openEntityList = &parser->m_openAttributeEntities;
++    freeEntityList = &parser->m_freeAttributeEntities;
++    break;
++  case ENTITY_VALUE:
++    openEntityList = &parser->m_openValueEntities;
++    freeEntityList = &parser->m_freeValueEntities;
++    break;
++    /* default case serves merely as a safety net in case of a
++     * wrong entityType. Therefore we exclude the following lines
++     * from the test coverage.
++     *
++     * LCOV_EXCL_START
++     */
++  default:
++    // Should not reach here
++    assert(0);
++    /* LCOV_EXCL_STOP */
++  }
+ 
+-  if (parser->m_freeInternalEntities) {
+-    openEntity = parser->m_freeInternalEntities;
+-    parser->m_freeInternalEntities = openEntity->next;
++  if (*freeEntityList) {
++    openEntity = *freeEntityList;
++    *freeEntityList = openEntity->next;
+   } else {
+     openEntity
+         = (OPEN_INTERNAL_ENTITY *)MALLOC(parser, sizeof(OPEN_INTERNAL_ENTITY));
+@@ -5847,55 +6008,34 @@ processInternalEntity(XML_Parser parser, ENTITY *entity, XML_Bool betweenDecl) {
+       return XML_ERROR_NO_MEMORY;
+   }
+   entity->open = XML_TRUE;
++  entity->hasMore = XML_TRUE;
+ #if XML_GE == 1
+   entityTrackingOnOpen(parser, entity, __LINE__);
+ #endif
+   entity->processed = 0;
+-  openEntity->next = parser->m_openInternalEntities;
+-  parser->m_openInternalEntities = openEntity;
++  openEntity->next = *openEntityList;
++  *openEntityList = openEntity;
+   openEntity->entity = entity;
++  openEntity->type = type;
+   openEntity->startTagLevel = parser->m_tagLevel;
+   openEntity->betweenDecl = betweenDecl;
+   openEntity->internalEventPtr = NULL;
+   openEntity->internalEventEndPtr = NULL;
+-  textStart = (const char *)entity->textPtr;
+-  textEnd = (const char *)(entity->textPtr + entity->textLen);
+-  /* Set a safe default value in case 'next' does not get set */
+-  next = textStart;
+ 
+-  if (entity->is_param) {
+-    int tok
+-        = XmlPrologTok(parser->m_internalEncoding, textStart, textEnd, &next);
+-    result = doProlog(parser, parser->m_internalEncoding, textStart, textEnd,
+-                      tok, next, &next, XML_FALSE, XML_FALSE,
+-                      XML_ACCOUNT_ENTITY_EXPANSION);
+-  } else {
+-    result = doContent(parser, parser->m_tagLevel, parser->m_internalEncoding,
+-                       textStart, textEnd, &next, XML_FALSE,
+-                       XML_ACCOUNT_ENTITY_EXPANSION);
++  // Only internal entities make use of the reenter flag
++  // therefore no need to set it for other entity types
++  if (type == ENTITY_INTERNAL) {
++    triggerReenter(parser);
+   }
+-
+-  if (result == XML_ERROR_NONE) {
+-    if (textEnd != next && parser->m_parsingStatus.parsing == XML_SUSPENDED) {
+-      entity->processed = (int)(next - textStart);
+-      parser->m_processor = internalEntityProcessor;
+-    } else if (parser->m_openInternalEntities->entity == entity) {
+-#if XML_GE == 1
+-      entityTrackingOnClose(parser, entity, __LINE__);
+-#endif /* XML_GE == 1 */
+-      entity->open = XML_FALSE;
+-      parser->m_openInternalEntities = openEntity->next;
+-      /* put openEntity back in list of free instances */
+-      openEntity->next = parser->m_freeInternalEntities;
+-      parser->m_freeInternalEntities = openEntity;
+-    }
+-  }
+-  return result;
++  return XML_ERROR_NONE;
+ }
+ 
+ static enum XML_Error PTRCALL
+ internalEntityProcessor(XML_Parser parser, const char *s, const char *end,
+                         const char **nextPtr) {
++  UNUSED_P(s);
++  UNUSED_P(end);
++  UNUSED_P(nextPtr);
+   ENTITY *entity;
+   const char *textStart, *textEnd;
+   const char *next;
+@@ -5905,68 +6045,67 @@ internalEntityProcessor(XML_Parser parser, const char *s, const char *end,
+     return XML_ERROR_UNEXPECTED_STATE;
+ 
+   entity = openEntity->entity;
+-  textStart = ((const char *)entity->textPtr) + entity->processed;
+-  textEnd = (const char *)(entity->textPtr + entity->textLen);
+-  /* Set a safe default value in case 'next' does not get set */
+-  next = textStart;
+ 
+-  if (entity->is_param) {
+-    int tok
+-        = XmlPrologTok(parser->m_internalEncoding, textStart, textEnd, &next);
+-    result = doProlog(parser, parser->m_internalEncoding, textStart, textEnd,
+-                      tok, next, &next, XML_FALSE, XML_TRUE,
+-                      XML_ACCOUNT_ENTITY_EXPANSION);
+-  } else {
+-    result = doContent(parser, openEntity->startTagLevel,
+-                       parser->m_internalEncoding, textStart, textEnd, &next,
+-                       XML_FALSE, XML_ACCOUNT_ENTITY_EXPANSION);
+-  }
++  // This will return early
++  if (entity->hasMore) {
++    textStart = ((const char *)entity->textPtr) + entity->processed;
++    textEnd = (const char *)(entity->textPtr + entity->textLen);
++    /* Set a safe default value in case 'next' does not get set */
++    next = textStart;
+ 
+-  if (result != XML_ERROR_NONE)
+-    return result;
++    if (entity->is_param) {
++      int tok
++          = XmlPrologTok(parser->m_internalEncoding, textStart, textEnd, &next);
++      result = doProlog(parser, parser->m_internalEncoding, textStart, textEnd,
++                        tok, next, &next, XML_FALSE, XML_FALSE,
++                        XML_ACCOUNT_ENTITY_EXPANSION);
++    } else {
++      result = doContent(parser, openEntity->startTagLevel,
++                         parser->m_internalEncoding, textStart, textEnd, &next,
++                         XML_FALSE, XML_ACCOUNT_ENTITY_EXPANSION);
++    }
++
++    if (result != XML_ERROR_NONE)
++      return result;
++    // Check if entity is complete, if not, mark down how much of it is
++    // processed
++    if (textEnd != next
++        && (parser->m_parsingStatus.parsing == XML_SUSPENDED
++            || (parser->m_parsingStatus.parsing == XML_PARSING
++                && parser->m_reenter))) {
++      entity->processed = (int)(next - (const char *)entity->textPtr);
++      return result;
++    }
+ 
+-  if (textEnd != next && parser->m_parsingStatus.parsing == XML_SUSPENDED) {
+-    entity->processed = (int)(next - (const char *)entity->textPtr);
++    // Entity is complete. We cannot close it here since we need to first
++    // process its possible inner entities (which are added to the
++    // m_openInternalEntities during doProlog or doContent calls above)
++    entity->hasMore = XML_FALSE;
++    triggerReenter(parser);
+     return result;
+-  }
++  } // End of entity processing, "if" block will return here
+ 
++  // Remove fully processed openEntity from open entity list.
+ #if XML_GE == 1
+   entityTrackingOnClose(parser, entity, __LINE__);
+ #endif
++  // openEntity is m_openInternalEntities' head, as we set it at the start of
++  // this function and we skipped doProlog and doContent calls with hasMore set
++  // to false. This means we can directly remove the head of
++  // m_openInternalEntities
++  assert(parser->m_openInternalEntities == openEntity);
+   entity->open = XML_FALSE;
+-  parser->m_openInternalEntities = openEntity->next;
++  parser->m_openInternalEntities = parser->m_openInternalEntities->next;
++
+   /* put openEntity back in list of free instances */
+   openEntity->next = parser->m_freeInternalEntities;
+   parser->m_freeInternalEntities = openEntity;
+ 
+-  // If there are more open entities we want to stop right here and have the
+-  // upcoming call to XML_ResumeParser continue with entity content, or it would
+-  // be ignored altogether.
+-  if (parser->m_openInternalEntities != NULL
+-      && parser->m_parsingStatus.parsing == XML_SUSPENDED) {
+-    return XML_ERROR_NONE;
+-  }
+-
+-  if (entity->is_param) {
+-    int tok;
+-    parser->m_processor = prologProcessor;
+-    tok = XmlPrologTok(parser->m_encoding, s, end, &next);
+-    return doProlog(parser, parser->m_encoding, s, end, tok, next, nextPtr,
+-                    (XML_Bool)! parser->m_parsingStatus.finalBuffer, XML_TRUE,
+-                    XML_ACCOUNT_DIRECT);
+-  } else {
+-    parser->m_processor = contentProcessor;
+-    /* see externalEntityContentProcessor vs contentProcessor */
+-    result = doContent(parser, parser->m_parentParser ? 1 : 0,
+-                       parser->m_encoding, s, end, nextPtr,
+-                       (XML_Bool)! parser->m_parsingStatus.finalBuffer,
+-                       XML_ACCOUNT_DIRECT);
+-    if (result == XML_ERROR_NONE) {
+-      if (! storeRawNames(parser))
+-        return XML_ERROR_NO_MEMORY;
+-    }
+-    return result;
++  if (parser->m_openInternalEntities == NULL) {
++    parser->m_processor = entity->is_param ? prologProcessor : contentProcessor;
+   }
++  triggerReenter(parser);
++  return XML_ERROR_NONE;
+ }
+ 
+ static enum XML_Error PTRCALL
+@@ -5982,8 +6121,70 @@ static enum XML_Error
+ storeAttributeValue(XML_Parser parser, const ENCODING *enc, XML_Bool isCdata,
+                     const char *ptr, const char *end, STRING_POOL *pool,
+                     enum XML_Account account) {
+-  enum XML_Error result
+-      = appendAttributeValue(parser, enc, isCdata, ptr, end, pool, account);
++  const char *next = ptr;
++  enum XML_Error result = XML_ERROR_NONE;
++
++  while (1) {
++    if (! parser->m_openAttributeEntities) {
++      result = appendAttributeValue(parser, enc, isCdata, next, end, pool,
++                                    account, &next);
++    } else {
++      OPEN_INTERNAL_ENTITY *const openEntity = parser->m_openAttributeEntities;
++      if (! openEntity)
++        return XML_ERROR_UNEXPECTED_STATE;
++
++      ENTITY *const entity = openEntity->entity;
++      const char *const textStart
++          = ((const char *)entity->textPtr) + entity->processed;
++      const char *const textEnd
++          = (const char *)(entity->textPtr + entity->textLen);
++      /* Set a safe default value in case 'next' does not get set */
++      const char *nextInEntity = textStart;
++      if (entity->hasMore) {
++        result = appendAttributeValue(
++            parser, parser->m_internalEncoding, isCdata, textStart, textEnd,
++            pool, XML_ACCOUNT_ENTITY_EXPANSION, &nextInEntity);
++        if (result != XML_ERROR_NONE)
++          break;
++        // Check if entity is complete, if not, mark down how much of it is
++        // processed. A XML_SUSPENDED check here is not required as
++        // appendAttributeValue will never suspend the parser.
++        if (textEnd != nextInEntity) {
++          entity->processed
++              = (int)(nextInEntity - (const char *)entity->textPtr);
++          continue;
++        }
++
++        // Entity is complete. We cannot close it here since we need to first
++        // process its possible inner entities (which are added to the
++        // m_openAttributeEntities during appendAttributeValue)
++        entity->hasMore = XML_FALSE;
++        continue;
++      } // End of entity processing, "if" block skips the rest
++
++      // Remove fully processed openEntity from open entity list.
++#if XML_GE == 1
++      entityTrackingOnClose(parser, entity, __LINE__);
++#endif
++      // openEntity is m_openAttributeEntities' head, since we set it at the
++      // start of this function and because we skipped appendAttributeValue call
++      // with hasMore set to false. This means we can directly remove the head
++      // of m_openAttributeEntities
++      assert(parser->m_openAttributeEntities == openEntity);
++      entity->open = XML_FALSE;
++      parser->m_openAttributeEntities = parser->m_openAttributeEntities->next;
++
++      /* put openEntity back in list of free instances */
++      openEntity->next = parser->m_freeAttributeEntities;
++      parser->m_freeAttributeEntities = openEntity;
++    }
++
++    // Break if an error occurred or there is nothing left to process
++    if (result || (parser->m_openAttributeEntities == NULL && end == next)) {
++      break;
++    }
++  }
++
+   if (result)
+     return result;
+   if (! isCdata && poolLength(pool) && poolLastChar(pool) == 0x20)
+@@ -5996,7 +6197,7 @@ storeAttributeValue(XML_Parser parser, const ENCODING *enc, XML_Bool isCdata,
+ static enum XML_Error
+ appendAttributeValue(XML_Parser parser, const ENCODING *enc, XML_Bool isCdata,
+                      const char *ptr, const char *end, STRING_POOL *pool,
+-                     enum XML_Account account) {
++                     enum XML_Account account, const char **nextPtr) {
+   DTD *const dtd = parser->m_dtd; /* save one level of indirection */
+ #ifndef XML_DTD
+   UNUSED_P(account);
+@@ -6014,6 +6215,9 @@ appendAttributeValue(XML_Parser parser, const ENCODING *enc, XML_Bool isCdata,
+ #endif
+     switch (tok) {
+     case XML_TOK_NONE:
++      if (nextPtr) {
++        *nextPtr = next;
++      }
+       return XML_ERROR_NONE;
+     case XML_TOK_INVALID:
+       if (enc == parser->m_encoding)
+@@ -6154,21 +6358,11 @@ appendAttributeValue(XML_Parser parser, const ENCODING *enc, XML_Bool isCdata,
+         return XML_ERROR_ATTRIBUTE_EXTERNAL_ENTITY_REF;
+       } else {
+         enum XML_Error result;
+-        const XML_Char *textEnd = entity->textPtr + entity->textLen;
+-        entity->open = XML_TRUE;
+-#if XML_GE == 1
+-        entityTrackingOnOpen(parser, entity, __LINE__);
+-#endif
+-        result = appendAttributeValue(parser, parser->m_internalEncoding,
+-                                      isCdata, (const char *)entity->textPtr,
+-                                      (const char *)textEnd, pool,
+-                                      XML_ACCOUNT_ENTITY_EXPANSION);
+-#if XML_GE == 1
+-        entityTrackingOnClose(parser, entity, __LINE__);
+-#endif
+-        entity->open = XML_FALSE;
+-        if (result)
+-          return result;
++        result = processEntity(parser, entity, XML_FALSE, ENTITY_ATTRIBUTE);
++        if ((result == XML_ERROR_NONE) && (nextPtr != NULL)) {
++          *nextPtr = next;
++        }
++        return result;
+       }
+     } break;
+     default:
+@@ -6197,7 +6391,7 @@ appendAttributeValue(XML_Parser parser, const ENCODING *enc, XML_Bool isCdata,
+ static enum XML_Error
+ storeEntityValue(XML_Parser parser, const ENCODING *enc,
+                  const char *entityTextPtr, const char *entityTextEnd,
+-                 enum XML_Account account) {
++                 enum XML_Account account, const char **nextPtr) {
+   DTD *const dtd = parser->m_dtd; /* save one level of indirection */
+   STRING_POOL *pool = &(dtd->entityValuePool);
+   enum XML_Error result = XML_ERROR_NONE;
+@@ -6215,8 +6409,9 @@ storeEntityValue(XML_Parser parser, const ENCODING *enc,
+       return XML_ERROR_NO_MEMORY;
+   }
+ 
++  const char *next;
+   for (;;) {
+-    const char *next
++    next
+         = entityTextPtr; /* XmlEntityValueTok doesn't always set the last arg */
+     int tok = XmlEntityValueTok(enc, entityTextPtr, entityTextEnd, &next);
+ 
+@@ -6278,16 +6473,8 @@ storeEntityValue(XML_Parser parser, const ENCODING *enc,
+           } else
+             dtd->keepProcessing = dtd->standalone;
+         } else {
+-          entity->open = XML_TRUE;
+-          entityTrackingOnOpen(parser, entity, __LINE__);
+-          result = storeEntityValue(
+-              parser, parser->m_internalEncoding, (const char *)entity->textPtr,
+-              (const char *)(entity->textPtr + entity->textLen),
+-              XML_ACCOUNT_ENTITY_EXPANSION);
+-          entityTrackingOnClose(parser, entity, __LINE__);
+-          entity->open = XML_FALSE;
+-          if (result)
+-            goto endEntityValue;
++          result = processEntity(parser, entity, XML_FALSE, ENTITY_VALUE);
++          goto endEntityValue;
+         }
+         break;
+       }
+@@ -6375,6 +6562,81 @@ endEntityValue:
+ #  ifdef XML_DTD
+   parser->m_prologState.inEntityValue = oldInEntityValue;
+ #  endif /* XML_DTD */
++  // If 'nextPtr' is given, it should be updated during the processing
++  if (nextPtr != NULL) {
++    *nextPtr = next;
++  }
++  return result;
++}
++
++static enum XML_Error
++callStoreEntityValue(XML_Parser parser, const ENCODING *enc,
++                     const char *entityTextPtr, const char *entityTextEnd,
++                     enum XML_Account account) {
++  const char *next = entityTextPtr;
++  enum XML_Error result = XML_ERROR_NONE;
++  while (1) {
++    if (! parser->m_openValueEntities) {
++      result
++          = storeEntityValue(parser, enc, next, entityTextEnd, account, &next);
++    } else {
++      OPEN_INTERNAL_ENTITY *const openEntity = parser->m_openValueEntities;
++      if (! openEntity)
++        return XML_ERROR_UNEXPECTED_STATE;
++
++      ENTITY *const entity = openEntity->entity;
++      const char *const textStart
++          = ((const char *)entity->textPtr) + entity->processed;
++      const char *const textEnd
++          = (const char *)(entity->textPtr + entity->textLen);
++      /* Set a safe default value in case 'next' does not get set */
++      const char *nextInEntity = textStart;
++      if (entity->hasMore) {
++        result = storeEntityValue(parser, parser->m_internalEncoding, textStart,
++                                  textEnd, XML_ACCOUNT_ENTITY_EXPANSION,
++                                  &nextInEntity);
++        if (result != XML_ERROR_NONE)
++          break;
++        // Check if entity is complete, if not, mark down how much of it is
++        // processed. A XML_SUSPENDED check here is not required as
++        // appendAttributeValue will never suspend the parser.
++        if (textEnd != nextInEntity) {
++          entity->processed
++              = (int)(nextInEntity - (const char *)entity->textPtr);
++          continue;
++        }
++
++        // Entity is complete. We cannot close it here since we need to first
++        // process its possible inner entities (which are added to the
++        // m_openValueEntities during storeEntityValue)
++        entity->hasMore = XML_FALSE;
++        continue;
++      } // End of entity processing, "if" block skips the rest
++
++      // Remove fully processed openEntity from open entity list.
++#  if XML_GE == 1
++      entityTrackingOnClose(parser, entity, __LINE__);
++#  endif
++      // openEntity is m_openValueEntities' head, since we set it at the
++      // start of this function and because we skipped storeEntityValue call
++      // with hasMore set to false. This means we can directly remove the head
++      // of m_openValueEntities
++      assert(parser->m_openValueEntities == openEntity);
++      entity->open = XML_FALSE;
++      parser->m_openValueEntities = parser->m_openValueEntities->next;
++
++      /* put openEntity back in list of free instances */
++      openEntity->next = parser->m_freeValueEntities;
++      parser->m_freeValueEntities = openEntity;
++    }
++
++    // Break if an error occurred or there is nothing left to process
++    if (result
++        || (parser->m_openValueEntities == NULL && entityTextEnd == next)) {
++      break;
++    }
++  }
++
+   return result;
+ }
+ 
+diff --git a/expat/tests/alloc_tests.c b/expat/tests/alloc_tests.c
+index e5d46ebe..12ea3b2a 100644
+--- a/expat/tests/alloc_tests.c
++++ b/expat/tests/alloc_tests.c
+@@ -19,6 +19,7 @@
+    Copyright (c) 2020      Tim Gates <tim.gates@iress.com>
+    Copyright (c) 2021      Donghee Na <donghee.na@python.org>
+    Copyright (c) 2023      Sony Corporation / Snild Dolkow <snild@sony.com>
++   Copyright (c) 2025      Berkay Eren Ürün <berkay.ueruen@siemens.com>
+    Licensed under the MIT license:
+ 
+    Permission is  hereby granted,  free of charge,  to any  person obtaining
+@@ -450,6 +451,31 @@ START_TEST(test_alloc_internal_entity) {
+ }
+ END_TEST
+ 
++START_TEST(test_alloc_parameter_entity) {
++  const char *text = "<!DOCTYPE foo ["
++                     "<!ENTITY % param1 \"<!ENTITY internal 'some_text'>\">"
++                     "%param1;"
++                     "]> <foo>&internal;content</foo>";
++  int i;
++  const int alloc_test_max_repeats = 30;
++
++  for (i = 0; i < alloc_test_max_repeats; i++) {
++    g_allocation_count = i;
++    XML_SetParamEntityParsing(g_parser, XML_PARAM_ENTITY_PARSING_ALWAYS);
++    if (_XML_Parse_SINGLE_BYTES(g_parser, text, (int)strlen(text), XML_TRUE)
++        != XML_STATUS_ERROR)
++      break;
++    alloc_teardown();
++    alloc_setup();
++  }
++  g_allocation_count = -1;
++  if (i == 0)
++    fail("Parameter entity processed despite duff allocator");
++  if (i == alloc_test_max_repeats)
++    fail("Parameter entity not processed at max allocation count");
++}
++END_TEST
++
+ /* Test the robustness against allocation failure of element handling
+  * Based on test_dtd_default_handling().
+  */
+@@ -2079,6 +2105,7 @@ make_alloc_test_case(Suite *s) {
+   tcase_add_test__ifdef_xml_dtd(tc_alloc, test_alloc_external_entity);
+   tcase_add_test__ifdef_xml_dtd(tc_alloc, test_alloc_ext_entity_set_encoding);
+   tcase_add_test__ifdef_xml_dtd(tc_alloc, test_alloc_internal_entity);
++  tcase_add_test__ifdef_xml_dtd(tc_alloc, test_alloc_parameter_entity);
+   tcase_add_test__ifdef_xml_dtd(tc_alloc, test_alloc_dtd_default_handling);
+   tcase_add_test(tc_alloc, test_alloc_explicit_encoding);
+   tcase_add_test(tc_alloc, test_alloc_set_base);
+diff --git a/expat/tests/basic_tests.c b/expat/tests/basic_tests.c
+index d2306772..29be32cf 100644
+--- a/expat/tests/basic_tests.c
++++ b/expat/tests/basic_tests.c
+@@ -10,7 +10,7 @@
+    Copyright (c) 2003      Greg Stein <gstein@users.sourceforge.net>
+    Copyright (c) 2005-2007 Steven Solie <steven@solie.ca>
+    Copyright (c) 2005-2012 Karl Waclawek <karl@waclawek.net>
+-   Copyright (c) 2016-2024 Sebastian Pipping <sebastian@pipping.org>
++   Copyright (c) 2016-2025 Sebastian Pipping <sebastian@pipping.org>
+    Copyright (c) 2017-2022 Rhodri James <rhodri@wildebeest.org.uk>
+    Copyright (c) 2017      Joe Orton <jorton@redhat.com>
+    Copyright (c) 2017      José Gutiérrez de la Concha <jose@zeroc.com>
+@@ -19,6 +19,7 @@
+    Copyright (c) 2020      Tim Gates <tim.gates@iress.com>
+    Copyright (c) 2021      Donghee Na <donghee.na@python.org>
+    Copyright (c) 2023-2024 Sony Corporation / Snild Dolkow <snild@sony.com>
++   Copyright (c) 2024-2025 Berkay Eren Ürün <berkay.ueruen@siemens.com>
+    Licensed under the MIT license:
+ 
+    Permission is  hereby granted,  free of charge,  to any  person obtaining
+@@ -1233,44 +1234,58 @@ START_TEST(test_no_indirectly_recursive_entity_refs) {
+        "<doc/>\n",
+        true},
+   };
++  const XML_Bool reset_or_not[] = {XML_TRUE, XML_FALSE};
++
+   for (size_t i = 0; i < sizeof(cases) / sizeof(cases[0]); i++) {
+-    const char *const doc = cases[i].doc;
+-    const bool usesParameterEntities = cases[i].usesParameterEntities;
++    for (size_t j = 0; j < sizeof(reset_or_not) / sizeof(reset_or_not[0]);
++         j++) {
++      const XML_Bool reset_wanted = reset_or_not[j];
++      const char *const doc = cases[i].doc;
++      const bool usesParameterEntities = cases[i].usesParameterEntities;
+ 
+-    set_subtest("[%i] %s", (int)i, doc);
++      set_subtest("[%i,reset=%i] %s", (int)i, (int)j, doc);
+ 
+ #ifdef XML_DTD // both GE and DTD
+-    const bool rejection_expected = true;
++      const bool rejection_expected = true;
+ #elif XML_GE == 1 // GE but not DTD
+-    const bool rejection_expected = ! usesParameterEntities;
++      const bool rejection_expected = ! usesParameterEntities;
+ #else             // neither DTD nor GE
+-    const bool rejection_expected = false;
++      const bool rejection_expected = false;
+ #endif
+ 
+-    XML_Parser parser = XML_ParserCreate(NULL);
++      XML_Parser parser = XML_ParserCreate(NULL);
+ 
+ #ifdef XML_DTD
+-    if (usesParameterEntities) {
+-      assert_true(
+-          XML_SetParamEntityParsing(parser, XML_PARAM_ENTITY_PARSING_ALWAYS)
+-          == 1);
+-    }
++      if (usesParameterEntities) {
++        assert_true(
++            XML_SetParamEntityParsing(parser, XML_PARAM_ENTITY_PARSING_ALWAYS)
++            == 1);
++      }
+ #else
+-    UNUSED_P(usesParameterEntities);
++      UNUSED_P(usesParameterEntities);
+ #endif // XML_DTD
+ 
+-    const enum XML_Status status
+-        = _XML_Parse_SINGLE_BYTES(parser, doc, (int)strlen(doc),
+-                                  /*isFinal*/ XML_TRUE);
++      const enum XML_Status status
++          = _XML_Parse_SINGLE_BYTES(parser, doc, (int)strlen(doc),
++                                    /*isFinal*/ XML_TRUE);
+ 
+-    if (rejection_expected) {
+-      assert_true(status == XML_STATUS_ERROR);
+-      assert_true(XML_GetErrorCode(parser) == XML_ERROR_RECURSIVE_ENTITY_REF);
+-    } else {
+-      assert_true(status == XML_STATUS_OK);
++      if (rejection_expected) {
++        assert_true(status == XML_STATUS_ERROR);
++        assert_true(XML_GetErrorCode(parser) == XML_ERROR_RECURSIVE_ENTITY_REF);
++      } else {
++        assert_true(status == XML_STATUS_OK);
++      }
++
++      if (reset_wanted) {
++        // This covers free'ing of (eventually) all three open entity lists by
++        // XML_ParserReset.
++        XML_ParserReset(parser, NULL);
++      }
++
++      // This covers free'ing of (eventually) all three open entity lists by
++      // XML_ParserFree (unless XML_ParserReset has already done that above).
++      XML_ParserFree(parser);
+     }
+-
+-    XML_ParserFree(parser);
+   }
+ }
+ END_TEST
+@@ -4033,7 +4048,7 @@ START_TEST(test_skipped_null_loaded_ext_entity) {
+       = {"<!ENTITY % pe1 SYSTEM 'http://example.org/two.ent'>\n"
+          "<!ENTITY % pe2 '%pe1;'>\n"
+          "%pe2;\n",
+-         external_entity_null_loader};
++         external_entity_null_loader, NULL};
+ 
+   XML_SetUserData(g_parser, &test_data);
+   XML_SetParamEntityParsing(g_parser, XML_PARAM_ENTITY_PARSING_ALWAYS);
+@@ -4051,7 +4066,7 @@ START_TEST(test_skipped_unloaded_ext_entity) {
+       = {"<!ENTITY % pe1 SYSTEM 'http://example.org/two.ent'>\n"
+          "<!ENTITY % pe2 '%pe1;'>\n"
+          "%pe2;\n",
+-         NULL};
++         NULL, NULL};
+ 
+   XML_SetUserData(g_parser, &test_data);
+   XML_SetParamEntityParsing(g_parser, XML_PARAM_ENTITY_PARSING_ALWAYS);
+@@ -5351,6 +5366,151 @@ START_TEST(test_pool_integrity_with_unfinished_attr) {
+ }
+ END_TEST
+ 
++/* Test a possible early return location in internalEntityProcessor */
++START_TEST(test_entity_ref_no_elements) {
++  const char *const text = "<!DOCTYPE foo [\n"
++                           "<!ENTITY e1 \"test\">\n"
++                           "]> <foo>&e1;"; // intentionally missing newline
++
++  XML_Parser parser = XML_ParserCreate(NULL);
++  assert_true(_XML_Parse_SINGLE_BYTES(parser, text, (int)strlen(text), XML_TRUE)
++              == XML_STATUS_ERROR);
++  assert_true(XML_GetErrorCode(parser) == XML_ERROR_NO_ELEMENTS);
++  XML_ParserFree(parser);
++}
++END_TEST
++
++/* Tests if chained entity references lead to unbounded recursion */
++START_TEST(test_deep_nested_entity) {
++  const size_t N_LINES = 60000;
++  const size_t SIZE_PER_LINE = 50;
++
++  char *const text = (char *)malloc((N_LINES + 4) * SIZE_PER_LINE);
++  if (text == NULL) {
++    fail("malloc failed");
++  }
++
++  char *textPtr = text;
++
++  // Create the XML
++  textPtr += snprintf(textPtr, SIZE_PER_LINE,
++                      "<!DOCTYPE foo [\n"
++                      "	<!ENTITY s0 'deepText'>\n");
++
++  for (size_t i = 1; i < N_LINES; ++i) {
++    textPtr += snprintf(textPtr, SIZE_PER_LINE, "  <!ENTITY s%lu '&s%lu;'>\n",
++                        (long unsigned)i, (long unsigned)(i - 1));
++  }
++
++  snprintf(textPtr, SIZE_PER_LINE, "]> <foo>&s%lu;</foo>\n",
++           (long unsigned)(N_LINES - 1));
++
++  const XML_Char *const expected = XCS("deepText");
++
++  CharData storage;
++  CharData_Init(&storage);
++
++  XML_Parser parser = XML_ParserCreate(NULL);
++
++  XML_SetCharacterDataHandler(parser, accumulate_characters);
++  XML_SetUserData(parser, &storage);
++
++  if (_XML_Parse_SINGLE_BYTES(parser, text, (int)strlen(text), XML_TRUE)
++      == XML_STATUS_ERROR)
++    xml_failure(parser);
++
++  CharData_CheckXMLChars(&storage, expected);
++  XML_ParserFree(parser);
++  free(text);
++}
++END_TEST
++
++/* Tests if chained entity references in attributes
++lead to unbounded recursion */
++START_TEST(test_deep_nested_attribute_entity) {
++  const size_t N_LINES = 60000;
++  const size_t SIZE_PER_LINE = 100;
++
++  char *const text = (char *)malloc((N_LINES + 4) * SIZE_PER_LINE);
++  if (text == NULL) {
++    fail("malloc failed");
++  }
++
++  char *textPtr = text;
++
++  // Create the XML
++  textPtr += snprintf(textPtr, SIZE_PER_LINE,
++                      "<!DOCTYPE foo [\n"
++                      "	<!ENTITY s0 'deepText'>\n");
++
++  for (size_t i = 1; i < N_LINES; ++i) {
++    textPtr += snprintf(textPtr, SIZE_PER_LINE, "  <!ENTITY s%lu '&s%lu;'>\n",
++                        (long unsigned)i, (long unsigned)(i - 1));
++  }
++
++  snprintf(textPtr, SIZE_PER_LINE, "]> <foo name='&s%lu;'>mainText</foo>\n",
++           (long unsigned)(N_LINES - 1));
++
++  AttrInfo doc_info[] = {{XCS("name"), XCS("deepText")}, {NULL, NULL}};
++  ElementInfo info[] = {{XCS("foo"), 1, NULL, NULL}, {NULL, 0, NULL, NULL}};
++  info[0].attributes = doc_info;
++
++  XML_Parser parser = XML_ParserCreate(NULL);
++  ParserAndElementInfo parserPlusElemenInfo = {parser, info};
++
++  XML_SetStartElementHandler(parser, counting_start_element_handler);
++  XML_SetUserData(parser, &parserPlusElemenInfo);
++
++  if (_XML_Parse_SINGLE_BYTES(parser, text, (int)strlen(text), XML_TRUE)
++      == XML_STATUS_ERROR)
++    xml_failure(parser);
++
++  XML_ParserFree(parser);
++  free(text);
++}
++END_TEST
++
++START_TEST(test_deep_nested_entity_delayed_interpretation) {
++  const size_t N_LINES = 70000;
++  const size_t SIZE_PER_LINE = 100;
++
++  char *const text = (char *)malloc((N_LINES + 4) * SIZE_PER_LINE);
++  if (text == NULL) {
++    fail("malloc failed");
++  }
++
++  char *textPtr = text;
++
++  // Create the XML
++  textPtr += snprintf(textPtr, SIZE_PER_LINE,
++                      "<!DOCTYPE foo [\n"
++                      "	<!ENTITY %% s0 'deepText'>\n");
++
++  for (size_t i = 1; i < N_LINES; ++i) {
++    textPtr += snprintf(textPtr, SIZE_PER_LINE,
++                        "  <!ENTITY %% s%lu '&#37;s%lu;'>\n", (long unsigned)i,
++                        (long unsigned)(i - 1));
++  }
++
++  snprintf(textPtr, SIZE_PER_LINE,
++           "  <!ENTITY %% define_g \"<!ENTITY g '&#37;s%lu;'>\">\n"
++           "  %%define_g;\n"
++           "]>\n"
++           "<foo/>\n",
++           (long unsigned)(N_LINES - 1));
++
++  XML_Parser parser = XML_ParserCreate(NULL);
++
++  XML_SetParamEntityParsing(parser, XML_PARAM_ENTITY_PARSING_ALWAYS);
++  if (_XML_Parse_SINGLE_BYTES(parser, text, (int)strlen(text), XML_TRUE)
++      == XML_STATUS_ERROR)
++    xml_failure(parser);
++
++  XML_ParserFree(parser);
++  free(text);
++}
++END_TEST
++
+ START_TEST(test_nested_entity_suspend) {
+   const char *const text = "<!DOCTYPE a [\n"
+                            "  <!ENTITY e1 '<!--e1-->'>\n"
+@@ -5381,6 +5541,35 @@ START_TEST(test_nested_entity_suspend) {
+ }
+ END_TEST
+ 
++START_TEST(test_nested_entity_suspend_2) {
++  const char *const text = "<!DOCTYPE doc [\n"
++                           "  <!ENTITY ge1 'head1Ztail1'>\n"
++                           "  <!ENTITY ge2 'head2&ge1;tail2'>\n"
++                           "  <!ENTITY ge3 'head3&ge2;tail3'>\n"
++                           "]>\n"
++                           "<doc>&ge3;</doc>";
++  const XML_Char *const expected = XCS("head3") XCS("head2") XCS("head1")
++      XCS("Z") XCS("tail1") XCS("tail2") XCS("tail3");
++  CharData storage;
++  CharData_Init(&storage);
++  XML_Parser parser = XML_ParserCreate(NULL);
++  ParserPlusStorage parserPlusStorage = {parser, &storage};
++
++  XML_SetCharacterDataHandler(parser, accumulate_char_data_and_suspend);
++  XML_SetUserData(parser, &parserPlusStorage);
++
++  enum XML_Status status = XML_Parse(parser, text, (int)strlen(text), XML_TRUE);
++  while (status == XML_STATUS_SUSPENDED) {
++    status = XML_ResumeParser(parser);
++  }
++  if (status != XML_STATUS_OK)
++    xml_failure(parser);
++
++  CharData_CheckXMLChars(&storage, expected);
++  XML_ParserFree(parser);
++}
++END_TEST
++
+ /* Regression test for quadratic parsing on large tokens */
+ START_TEST(test_big_tokens_scale_linearly) {
+   const struct {
+@@ -6221,7 +6410,13 @@ make_basic_test_case(Suite *s) {
+   tcase_add_test(tc_basic, test_empty_element_abort);
+   tcase_add_test__ifdef_xml_dtd(tc_basic,
+                                 test_pool_integrity_with_unfinished_attr);
++  tcase_add_test__if_xml_ge(tc_basic, test_entity_ref_no_elements);
++  tcase_add_test__if_xml_ge(tc_basic, test_deep_nested_entity);
++  tcase_add_test__if_xml_ge(tc_basic, test_deep_nested_attribute_entity);
++  tcase_add_test__if_xml_ge(tc_basic,
++                            test_deep_nested_entity_delayed_interpretation);
+   tcase_add_test__if_xml_ge(tc_basic, test_nested_entity_suspend);
++  tcase_add_test__if_xml_ge(tc_basic, test_nested_entity_suspend_2);
+   tcase_add_test(tc_basic, test_big_tokens_scale_linearly);
+   tcase_add_test(tc_basic, test_set_reparse_deferral);
+   tcase_add_test(tc_basic, test_reparse_deferral_is_inherited);
+diff --git a/expat/tests/handlers.c b/expat/tests/handlers.c
+index 0211985f..f15029e3 100644
+--- a/expat/tests/handlers.c
++++ b/expat/tests/handlers.c
+@@ -1882,6 +1882,20 @@ accumulate_entity_decl(void *userData, const XML_Char *entityName,
+   CharData_AppendXMLChars(storage, XCS("\n"), 1);
+ }
+ 
++void XMLCALL
++accumulate_char_data_and_suspend(void *userData, const XML_Char *s, int len) {
++  ParserPlusStorage *const parserPlusStorage = (ParserPlusStorage *)userData;
++
++  CharData_AppendXMLChars(parserPlusStorage->storage, s, len);
++
++  for (int i = 0; i < len; i++) {
++    if (s[i] == 'Z') {
++      XML_StopParser(parserPlusStorage->parser, /*resumable=*/XML_TRUE);
++      break;
++    }
++  }
++}
++
+ void XMLCALL
+ accumulate_start_element(void *userData, const XML_Char *name,
+                          const XML_Char **atts) {
+diff --git a/expat/tests/handlers.h b/expat/tests/handlers.h
+index 8850bb94..4d6a08d5 100644
+--- a/expat/tests/handlers.h
++++ b/expat/tests/handlers.h
+@@ -325,6 +325,7 @@ extern int XMLCALL external_entity_devaluer(XML_Parser parser,
+ typedef struct ext_hdlr_data {
+   const char *parse_text;
+   XML_ExternalEntityRefHandler handler;
++  CharData *storage;
+ } ExtHdlrData;
+ 
+ extern int XMLCALL external_entity_oneshot_loader(XML_Parser parser,
+@@ -569,6 +570,10 @@ extern void XMLCALL accumulate_entity_decl(
+     const XML_Char *systemId, const XML_Char *publicId,
+     const XML_Char *notationName);
+ 
++extern void XMLCALL accumulate_char_data_and_suspend(void *userData,
++                                                     const XML_Char *s,
++                                                     int len);
++
+ extern void XMLCALL accumulate_start_element(void *userData,
+                                              const XML_Char *name,
+                                              const XML_Char **atts);
+diff --git a/expat/tests/misc_tests.c b/expat/tests/misc_tests.c
+index 9afe0922..f9a78f66 100644
+--- a/expat/tests/misc_tests.c
++++ b/expat/tests/misc_tests.c
+@@ -59,6 +59,9 @@
+ #include "handlers.h"
+ #include "misc_tests.h"
+ 
++void XMLCALL accumulate_characters_ext_handler(void *userData,
++                                               const XML_Char *s, int len);
++
+ /* Test that a failure to allocate the parser structure fails gracefully */
+ START_TEST(test_misc_alloc_create_parser) {
+   XML_Memory_Handling_Suite memsuite = {duff_allocator, realloc, free};
+@@ -519,6 +522,45 @@ START_TEST(test_misc_stopparser_rejects_unstarted_parser) {
+ }
+ END_TEST
+ 
++/* Adaptation of accumulate_characters that takes ExtHdlrData input to work with
++ * test_renter_loop_finite_content below */
++void XMLCALL
++accumulate_characters_ext_handler(void *userData, const XML_Char *s, int len) {
++  ExtHdlrData *const test_data = (ExtHdlrData *)userData;
++  CharData_AppendXMLChars(test_data->storage, s, len);
++}
++
++/* Test that internalEntityProcessor does not re-enter forever;
++ * based on files tests/xmlconf/xmltest/valid/ext-sa/012.{xml,ent} */
++START_TEST(test_renter_loop_finite_content) {
++  CharData storage;
++  CharData_Init(&storage);
++  const char *const text = "<!DOCTYPE doc [\n"
++                           "<!ENTITY e1 '&e2;'>\n"
++                           "<!ENTITY e2 '&e3;'>\n"
++                           "<!ENTITY e3 SYSTEM '012.ent'>\n"
++                           "<!ENTITY e4 '&e5;'>\n"
++                           "<!ENTITY e5 '(e5)'>\n"
++                           "<!ELEMENT doc (#PCDATA)>\n"
++                           "]>\n"
++                           "<doc>&e1;</doc>\n";
++  ExtHdlrData test_data = {"&e4;\n", external_entity_null_loader, &storage};
++  const XML_Char *const expected = XCS("(e5)\n");
++
++  XML_Parser parser = XML_ParserCreate(NULL);
++  assert_true(parser != NULL);
++  XML_SetUserData(parser, &test_data);
++  XML_SetExternalEntityRefHandler(parser, external_entity_oneshot_loader);
++  XML_SetCharacterDataHandler(parser, accumulate_characters_ext_handler);
++  if (_XML_Parse_SINGLE_BYTES(parser, text, (int)strlen(text), XML_TRUE)
++      == XML_STATUS_ERROR)
++    xml_failure(parser);
++
++  CharData_CheckXMLChars(&storage, expected);
++  XML_ParserFree(parser);
++}
++END_TEST
++
+ void
+ make_miscellaneous_test_case(Suite *s) {
+   TCase *tc_misc = tcase_create("miscellaneous tests");
+@@ -545,4 +587,5 @@ make_miscellaneous_test_case(Suite *s) {
+   tcase_add_test(tc_misc, test_misc_char_handler_stop_without_leak);
+   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);
+ }
diff --git a/meta/recipes-core/expat/expat_2.6.4.bb b/meta/recipes-core/expat/expat_2.6.4.bb
index f383792793..1da7141025 100644
--- a/meta/recipes-core/expat/expat_2.6.4.bb
+++ b/meta/recipes-core/expat/expat_2.6.4.bb
@@ -10,6 +10,8 @@  VERSION_TAG = "${@d.getVar('PV').replace('.', '_')}"
 
 SRC_URI = "${GITHUB_BASE_URI}/download/R_${VERSION_TAG}/expat-${PV}.tar.bz2  \
            file://run-ptest \
+           file://0001-tests-Cover-indirect-entity-recursion.patch;striplevel=2 \
+           file://CVE-2024-8176.patch;striplevel=2 \
            "
 
 GITHUB_BASE_URI = "https://github.com/libexpat/libexpat/releases/"