CVE-2023-52426: Improper Restriction of Recursive Entity References in DTDs ('XML Entity Expansion') (CWE-776)
value: libexpat through 2.5.0 allows recursive XML Entity Expansion if XML_DTD is undefined at compile time.
CWE description: The product uses XML documents and allows their structure to be defined with a Document Type Definition (DTD), but it does not properly control the number of recursive definitions of entities.
CVSS v3 base score: 5.5
Published: 2024-02-04T20:15Z
Repository: https://github.com/libexpat/libexpat
Language: C
Files changed: expat/CMakeLists.txt, expat/lib/expat.h, expat/lib/internal.h, expat/lib/libexpat.def.cmake, expat/lib/xmlparse.c, expat/xmlwf/xmlwf.c
Commit date: 2023-11-06 20:43:09 +0100
Commit message: lib|xmlwf|cmake: Extend scope of billion laughs attack protection

.. from "defined(XML_DTD)" to "defined(XML_DTD) || XML_GE==1".

Diff: diff --git a/expat/CMakeLists.txt b/expat/CMakeLists.txt
index 38805ebb..5f59da71 100644
--- a/expat/CMakeLists.txt
+++ b/expat/CMakeLists.txt
@@ -401,11 +401,17 @@ if(EXPAT_SHARED_LIBS)
             else()
                 set(${target_var} ";")  # i.e. commented out
             endif()
         endmacro()
 
-        _expat_def_file_toggle(EXPAT_DTD _EXPAT_COMMENT_DTD)
+        if(EXPAT_DTD OR EXPAT_GE)
+            set(_EXPAT_DTD_OR_GE TRUE)
+        else()
+            set(_EXPAT_DTD_OR_GE FALSE)
+        endif()
+
+        _expat_def_file_toggle(_EXPAT_DTD_OR_GE _EXPAT_COMMENT_DTD_OR_GE)
         _expat_def_file_toggle(EXPAT_ATTR_INFO _EXPAT_COMMENT_ATTR_INFO)
 
         configure_file("${CMAKE_CURRENT_SOURCE_DIR}/lib/libexpat.def.cmake" "${CMAKE_CURRENT_BINARY_DIR}/lib/libexpat.def")
         set(_EXPAT_EXTRA_SOURCES ${_EXPAT_EXTRA_SOURCES} "${CMAKE_CURRENT_BINARY_DIR}/lib/libexpat.def")
 
diff --git a/expat/lib/expat.h b/expat/lib/expat.h
index f5f2025e..99113247 100644
--- a/expat/lib/expat.h
+++ b/expat/lib/expat.h
@@ -1036,17 +1036,19 @@ typedef struct {
 } XML_Feature;
 
 XMLPARSEAPI(const XML_Feature *)
 XML_GetFeatureList(void);
 
-#ifdef XML_DTD
-/* Added in Expat 2.4.0. */
+#if defined(XML_DTD) || XML_GE == 1
+/* Added in Expat 2.4.0 for XML_DTD defined and
+ * added in Expat 2.6.0 for XML_GE == 1. */
 XMLPARSEAPI(XML_Bool)
 XML_SetBillionLaughsAttackProtectionMaximumAmplification(
     XML_Parser parser, float maximumAmplificationFactor);
 
-/* Added in Expat 2.4.0. */
+/* Added in Expat 2.4.0 for XML_DTD defined and
+ * added in Expat 2.6.0 for XML_GE == 1. */
 XMLPARSEAPI(XML_Bool)
 XML_SetBillionLaughsAttackProtectionActivationThreshold(
     XML_Parser parser, unsigned long long activationThresholdBytes);
 #endif
 
diff --git a/expat/lib/internal.h b/expat/lib/internal.h
index e09f533b..1851925c 100644
--- a/expat/lib/internal.h
+++ b/expat/lib/internal.h
@@ -152,11 +152,11 @@ extern "C" {
 #endif
 
 void _INTERNAL_trim_to_complete_utf8_characters(const char *from,
                                                 const char **fromLimRef);
 
-#if defined(XML_DTD)
+#if defined(XML_DTD) || XML_GE == 1
 unsigned long long testingAccountingGetCountBytesDirect(XML_Parser parser);
 unsigned long long testingAccountingGetCountBytesIndirect(XML_Parser parser);
 const char *unsignedCharToPrintable(unsigned char c);
 #endif
 
diff --git a/expat/lib/libexpat.def.cmake b/expat/lib/libexpat.def.cmake
index cf434a22..61a4f006 100644
--- a/expat/lib/libexpat.def.cmake
+++ b/expat/lib/libexpat.def.cmake
@@ -73,7 +73,7 @@ EXPORTS
 ; added with version 2.1.1
 @_EXPAT_COMMENT_ATTR_INFO@ XML_GetAttributeInfo @66
   XML_SetHashSalt @67
 ; internal @68 removed with version 2.3.1
 ; added with version 2.4.0
-@_EXPAT_COMMENT_DTD@ XML_SetBillionLaughsAttackProtectionActivationThreshold @69
-@_EXPAT_COMMENT_DTD@ XML_SetBillionLaughsAttackProtectionMaximumAmplification @70
+@_EXPAT_COMMENT_DTD_OR_GE@ XML_SetBillionLaughsAttackProtectionActivationThreshold @69
+@_EXPAT_COMMENT_DTD_OR_GE@ XML_SetBillionLaughsAttackProtectionMaximumAmplification @70
diff --git a/expat/lib/xmlparse.c b/expat/lib/xmlparse.c
index 3650b6d6..19d6c235 100644
--- a/expat/lib/xmlparse.c
+++ b/expat/lib/xmlparse.c
@@ -414,11 +414,11 @@ enum XML_Account {
   XML_ACCOUNT_ENTITY_EXPANSION, /* intermediate bytes produced during entity
                                    expansion */
   XML_ACCOUNT_NONE              /* i.e. do not account, was accounted already */
 };
 
-#ifdef XML_DTD
+#if defined(XML_DTD) || XML_GE == 1
 typedef unsigned long long XmlBigCount;
 typedef struct accounting {
   XmlBigCount countBytesDirect;
   XmlBigCount countBytesIndirect;
   unsigned long debugLevel;
@@ -430,11 +430,11 @@ typedef struct entity_stats {
   unsigned int countEverOpened;
   unsigned int currentDepth;
   unsigned int maximumDepthSeen;
   unsigned long debugLevel;
 } ENTITY_STATS;
-#endif /* XML_DTD */
+#endif /* defined(XML_DTD) || XML_GE == 1 */
 
 typedef enum XML_Error PTRCALL Processor(XML_Parser parser, const char *start,
                                          const char *end, const char **endPtr);
 
 static Processor prologProcessor;
@@ -568,11 +568,11 @@ static XML_Parser parserCreate(const XML_Char *encodingName,
                                const XML_Memory_Handling_Suite *memsuite,
                                const XML_Char *nameSep, DTD *dtd);
 
 static void parserInit(XML_Parser parser, const XML_Char *encodingName);
 
-#ifdef XML_DTD
+#if defined(XML_DTD) || XML_GE == 1
 static float accountingGetCurrentAmplification(XML_Parser rootParser);
 static void accountingReportStats(XML_Parser originParser, const char *epilog);
 static void accountingOnAbort(XML_Parser originParser);
 static void accountingReportDiff(XML_Parser rootParser,
                                  unsigned int levelsAwayFromRootParser,
@@ -591,11 +591,11 @@ static void entityTrackingOnOpen(XML_Parser parser, ENTITY *entity,
 static void entityTrackingOnClose(XML_Parser parser, ENTITY *entity,
                                   int sourceLine);
 
 static XML_Parser getRootParserOf(XML_Parser parser,
                                   unsigned int *outLevelDiff);
-#endif /* XML_DTD */
+#endif /* defined(XML_DTD) || XML_GE == 1 */
 
 static unsigned long getDebugLevel(const char *variableName,
                                    unsigned long defaultDebugLevel);
 
 #define poolStart(pool) ((pool)->start)
@@ -716,11 +716,11 @@ struct XML_ParserStruct {
   XML_Bool m_isParamEntity;
   XML_Bool m_useForeignDTD;
   enum XML_ParamEntityParsing m_paramEntityParsing;
 #endif
   unsigned long m_hash_secret_salt;
-#ifdef XML_DTD
+#if defined(XML_DTD) || XML_GE == 1
   ACCOUNTING m_accounting;
   ENTITY_STATS m_entity_stats;
 #endif
 };
 
@@ -1176,11 +1176,11 @@ parserInit(XML_Parser parser, const XML_Char *encodingName) {
   parser->m_useForeignDTD = XML_FALSE;
   parser->m_paramEntityParsing = XML_PARAM_ENTITY_PARSING_NEVER;
 #endif
   parser->m_hash_secret_salt = 0;
 
-#ifdef XML_DTD
+#if defined(XML_DTD) || XML_GE == 1
   memset(&parser->m_accounting, 0, sizeof(ACCOUNTING));
   parser->m_accounting.debugLevel = getDebugLevel("EXPAT_ACCOUNTING_DEBUG", 0u);
   parser->m_accounting.maximumAmplificationFactor
       = EXPAT_BILLION_LAUGHS_ATTACK_PROTECTION_MAXIMUM_AMPLIFICATION_DEFAULT;
   parser->m_accounting.activationThresholdBytes
@@ -2532,12 +2532,13 @@ XML_GetFeatureList(void) {
     {XML_FEATURE_LARGE_SIZE, XML_L("XML_LARGE_SIZE"), 0},
 #endif
 #ifdef XML_ATTR_INFO
     {XML_FEATURE_ATTR_INFO, XML_L("XML_ATTR_INFO"), 0},
 #endif
-#ifdef XML_DTD
-    /* Added in Expat 2.4.0. */
+#if defined(XML_DTD) || XML_GE == 1
+    /* Added in Expat 2.4.0 for XML_DTD defined and
+     * added in Expat 2.6.0 for XML_GE == 1. */
     {XML_FEATURE_BILLION_LAUGHS_ATTACK_PROTECTION_MAXIMUM_AMPLIFICATION_DEFAULT,
      XML_L("XML_BLAP_MAX_AMP"),
      (long int)
          EXPAT_BILLION_LAUGHS_ATTACK_PROTECTION_MAXIMUM_AMPLIFICATION_DEFAULT},
     {XML_FEATURE_BILLION_LAUGHS_ATTACK_PROTECTION_ACTIVATION_THRESHOLD_DEFAULT,
@@ -2548,11 +2549,11 @@ XML_GetFeatureList(void) {
   };
 
   return features;
 }
 
-#ifdef XML_DTD
+#if defined(XML_DTD) || XML_GE == 1
 XML_Bool XMLCALL
 XML_SetBillionLaughsAttackProtectionMaximumAmplification(
     XML_Parser parser, float maximumAmplificationFactor) {
   if ((parser == NULL) || (parser->m_parentParser != NULL)
       || isnan(maximumAmplificationFactor)
@@ -2570,11 +2571,11 @@ XML_SetBillionLaughsAttackProtectionActivationThreshold(
     return XML_FALSE;
   }
   parser->m_accounting.activationThresholdBytes = activationThresholdBytes;
   return XML_TRUE;
 }
-#endif /* XML_DTD */
+#endif /* defined(XML_DTD) || XML_GE == 1 */
 
 /* Initially tag->rawName always points into the parse buffer;
    for those TAG instances opened while the current parse buffer was
    processed, and not yet closed, we need to store tag->rawName in a more
    permanent location, since the parse buffer is about to be discarded.
@@ -2656,17 +2657,17 @@ externalEntityInitProcessor2(XML_Parser parser, const char *start,
                              const char *end, const char **endPtr) {
   const char *next = start; /* XmlContentTok doesn't always set the last arg */
   int tok = XmlContentTok(parser->m_encoding, start, end, &next);
   switch (tok) {
   case XML_TOK_BOM:
-#ifdef XML_DTD
+#if defined(XML_DTD) || XML_GE == 1
     if (! accountingDiffTolerated(parser, tok, start, next, __LINE__,
                                   XML_ACCOUNT_DIRECT)) {
       accountingOnAbort(parser);
       return XML_ERROR_AMPLIFICATION_LIMIT_BREACH;
     }
-#endif /* XML_DTD */
+#endif /* defined(XML_DTD) || XML_GE == 1 */
 
     /* If we are at the end of the buffer, this would cause the next stage,
        i.e. externalEntityInitProcessor3, to pass control directly to
        doContent (by detecting XML_TOK_NONE) without processing any xml text
        declaration - causing the error XML_ERROR_MISPLACED_XML_PI in doContent.
@@ -2776,11 +2777,11 @@ doContent(XML_Parser parser, int startTagLevel, const ENCODING *enc,
   *eventPP = s;
 
   for (;;) {
     const char *next = s; /* XmlContentTok doesn't always set the last arg */
     int tok = XmlContentTok(enc, s, end, &next);
-#ifdef XML_DTD
+#if defined(XML_DTD) || XML_GE == 1
     const char *accountAfter
         = ((tok == XML_TOK_TRAILING_RSQB) || (tok == XML_TOK_TRAILING_CR))
               ? (haveMore ? s /* i.e. 0 bytes */ : end)
               : next;
     if (! accountingDiffTolerated(parser, tok, s, accountAfter, __LINE__,
@@ -2842,18 +2843,18 @@ doContent(XML_Parser parser, int startTagLevel, const ENCODING *enc,
       const XML_Char *name;
       ENTITY *entity;
       XML_Char ch = (XML_Char)XmlPredefinedEntityName(
           enc, s + enc->minBytesPerChar, next - enc->minBytesPerChar);
       if (ch) {
-#ifdef XML_DTD
+#if defined(XML_DTD) || XML_GE == 1
         /* NOTE: We are replacing 4-6 characters original input for 1 character
          *       so there is no amplification and hence recording without
          *       protection. */
         accountingDiffTolerated(parser, tok, (char *)&ch,
                                 ((char *)&ch) + sizeof(XML_Char), __LINE__,
                                 XML_ACCOUNT_ENTITY_EXPANSION);
-#endif /* XML_DTD */
+#endif /* defined(XML_DTD) || XML_GE == 1 */
         if (parser->m_characterDataHandler)
           parser->m_characterDataHandler(parser->m_handlerArg, &ch, 1);
         else if (parser->m_defaultHandler)
           reportDefault(parser, enc, s, next);
         break;
@@ -4051,11 +4052,11 @@ doCdataSection(XML_Parser parser, const ENCODING *enc, const char **startPtr,
   *startPtr = NULL;
 
   for (;;) {
     const char *next = s; /* in case of XML_TOK_NONE or XML_TOK_PARTIAL */
     int tok = XmlCdataSectionTok(enc, s, end, &next);
-#ifdef XML_DTD
+#if defined(XML_DTD) || XML_GE == 1
     if (! accountingDiffTolerated(parser, tok, s, next, __LINE__, account)) {
       accountingOnAbort(parser);
       return XML_ERROR_AMPLIFICATION_LIMIT_BREACH;
     }
 #else
@@ -4203,11 +4204,11 @@ doIgnoreSection(XML_Parser parser, const ENCODING *enc, const char **startPtr,
     /* LCOV_EXCL_STOP */
   }
   *eventPP = s;
   *startPtr = NULL;
   tok = XmlIgnoreSectionTok(enc, s, end, &next);
-#  ifdef XML_DTD
+#  if defined(XML_DTD) || XML_GE == 1
   if (! accountingDiffTolerated(parser, tok, s, next, __LINE__,
                                 XML_ACCOUNT_DIRECT)) {
     accountingOnAbort(parser);
     return XML_ERROR_AMPLIFICATION_LIMIT_BREACH;
   }
@@ -4295,11 +4296,11 @@ processXmlDecl(XML_Parser parser, int isGeneralTextEntity, const char *s,
   const char *version = NULL;
   const char *versionend = NULL;
   const XML_Char *storedversion = NULL;
   int standalone = -1;
 
-#ifdef XML_DTD
+#if defined(XML_DTD) || XML_GE == 1
   if (! accountingDiffTolerated(parser, XML_TOK_XML_DECL, s, next, __LINE__,
                                 XML_ACCOUNT_DIRECT)) {
     accountingOnAbort(parser);
     return XML_ERROR_AMPLIFICATION_LIMIT_BREACH;
   }
@@ -4502,11 +4503,11 @@ entityValueInitProcessor(XML_Parser parser, const char *s, const char *end,
        would leave the BOM in the buffer and return. On the next call to this
        function, our XmlPrologTok call would return XML_TOK_INVALID, since it
        is not valid to have multiple BOMs.
     */
     else if (tok == XML_TOK_BOM) {
-#  ifdef XML_DTD
+#  if defined(XML_DTD) || XML_GE == 1
       if (! accountingDiffTolerated(parser, tok, s, next, __LINE__,
                                     XML_ACCOUNT_DIRECT)) {
         accountingOnAbort(parser);
         return XML_ERROR_AMPLIFICATION_LIMIT_BREACH;
       }
@@ -4718,15 +4719,17 @@ doProlog(XML_Parser parser, const ENCODING *enc, const char *s, const char *end,
         next = end;
         break;
       }
     }
     role = XmlTokenRole(&parser->m_prologState, tok, s, next, enc);
-#ifdef XML_DTD
+#if defined(XML_DTD) || XML_GE == 1
     switch (role) {
     case XML_ROLE_INSTANCE_START: // bytes accounted in contentProcessor
     case XML_ROLE_XML_DECL:       // bytes accounted in processXmlDecl
-    case XML_ROLE_TEXT_DECL:      // bytes accounted in processXmlDecl
+#  ifdef XML_DTD
+    case XML_ROLE_TEXT_DECL: // bytes accounted in processXmlDecl
+#  endif
       break;
     default:
       if (! accountingDiffTolerated(parser, tok, s, next, __LINE__, account)) {
         accountingOnAbort(parser);
         return XML_ERROR_AMPLIFICATION_LIMIT_BREACH;
@@ -5659,11 +5662,11 @@ epilogProcessor(XML_Parser parser, const char *s, const char *end,
   parser->m_processor = epilogProcessor;
   parser->m_eventPtr = s;
   for (;;) {
     const char *next = NULL;
     int tok = XmlPrologTok(parser->m_encoding, s, end, &next);
-#ifdef XML_DTD
+#if defined(XML_DTD) || XML_GE == 1
     if (! accountingDiffTolerated(parser, tok, s, next, __LINE__,
                                   XML_ACCOUNT_DIRECT)) {
       accountingOnAbort(parser);
       return XML_ERROR_AMPLIFICATION_LIMIT_BREACH;
     }
@@ -5739,11 +5742,11 @@ processInternalEntity(XML_Parser parser, ENTITY *entity, XML_Bool betweenDecl) {
         = (OPEN_INTERNAL_ENTITY *)MALLOC(parser, sizeof(OPEN_INTERNAL_ENTITY));
     if (! openEntity)
       return XML_ERROR_NO_MEMORY;
   }
   entity->open = XML_TRUE;
-#ifdef XML_DTD
+#if defined(XML_DTD) || XML_GE == 1
   entityTrackingOnOpen(parser, entity, __LINE__);
 #endif
   entity->processed = 0;
   openEntity->next = parser->m_openInternalEntities;
   parser->m_openInternalEntities = openEntity;
@@ -5773,13 +5776,13 @@ processInternalEntity(XML_Parser parser, ENTITY *entity, XML_Bool betweenDecl) {
   if (result == XML_ERROR_NONE) {
     if (textEnd != next && parser->m_parsingStatus.parsing == XML_SUSPENDED) {
       entity->processed = (int)(next - textStart);
       parser->m_processor = internalEntityProcessor;
     } else {
-#ifdef XML_DTD
+#if defined(XML_DTD) || XML_GE == 1
       entityTrackingOnClose(parser, entity, __LINE__);
-#endif /* XML_DTD */
+#endif /* defined(XML_DTD) || 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;
@@ -5824,11 +5827,11 @@ internalEntityProcessor(XML_Parser parser, const char *s, const char *end,
   if (textEnd != next && parser->m_parsingStatus.parsing == XML_SUSPENDED) {
     entity->processed = (int)(next - (const char *)entity->textPtr);
     return result;
   }
 
-#ifdef XML_DTD
+#if defined(XML_DTD) || XML_GE == 1
   entityTrackingOnClose(parser, entity, __LINE__);
 #endif
   entity->open = XML_FALSE;
   parser->m_openInternalEntities = openEntity->next;
   /* put openEntity back in list of free instances */
@@ -5903,11 +5906,11 @@ appendAttributeValue(XML_Parser parser, const ENCODING *enc, XML_Bool isCdata,
 
   for (;;) {
     const char *next
         = ptr; /* XmlAttributeValueTok doesn't always set the last arg */
     int tok = XmlAttributeValueTok(enc, ptr, end, &next);
-#ifdef XML_DTD
+#if defined(XML_DTD) || XML_GE == 1
     if (! accountingDiffTolerated(parser, tok, ptr, next, __LINE__, account)) {
       accountingOnAbort(parser);
       return XML_ERROR_AMPLIFICATION_LIMIT_BREACH;
     }
 #endif
@@ -5968,18 +5971,18 @@ appendAttributeValue(XML_Parser parser, const ENCODING *enc, XML_Bool isCdata,
       ENTITY *entity;
       char checkEntityDecl;
       XML_Char ch = (XML_Char)XmlPredefinedEntityName(
           enc, ptr + enc->minBytesPerChar, next - enc->minBytesPerChar);
       if (ch) {
-#ifdef XML_DTD
+#if defined(XML_DTD) || XML_GE == 1
         /* NOTE: We are replacing 4-6 characters original input for 1 character
          *       so there is no amplification and hence recording without
          *       protection. */
         accountingDiffTolerated(parser, tok, (char *)&ch,
                                 ((char *)&ch) + sizeof(XML_Char), __LINE__,
                                 XML_ACCOUNT_ENTITY_EXPANSION);
-#endif /* XML_DTD */
+#endif /* defined(XML_DTD) || XML_GE == 1 */
         if (! poolAppendChar(pool, ch))
           return XML_ERROR_NO_MEMORY;
         break;
       }
       name = poolStoreString(&parser->m_temp2Pool, enc,
@@ -6053,18 +6056,18 @@ 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;
-#ifdef XML_DTD
+#if defined(XML_DTD) || 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);
-#ifdef XML_DTD
+#if defined(XML_DTD) || XML_GE == 1
         entityTrackingOnClose(parser, entity, __LINE__);
 #endif
         entity->open = XML_FALSE;
         if (result)
           return result;
@@ -6116,11 +6119,11 @@ storeEntityValue(XML_Parser parser, const ENCODING *enc,
   for (;;) {
     const char *next
         = entityTextPtr; /* XmlEntityValueTok doesn't always set the last arg */
     int tok = XmlEntityValueTok(enc, entityTextPtr, entityTextEnd, &next);
 
-#ifdef XML_DTD
+#if defined(XML_DTD) || XML_GE == 1
     if (! accountingDiffTolerated(parser, tok, entityTextPtr, next, __LINE__,
                                   account)) {
       accountingOnAbort(parser);
       result = XML_ERROR_AMPLIFICATION_LIMIT_BREACH;
       goto endEntityValue;
@@ -7667,11 +7670,11 @@ copyString(const XML_Char *s, const XML_Memory_Handling_Suite *memsuite) {
   /* Copy the original into place */
   memcpy(result, s, charsRequired * sizeof(XML_Char));
   return result;
 }
 
-#ifdef XML_DTD
+#if defined(XML_DTD) || XML_GE == 1
 
 static float
 accountingGetCurrentAmplification(XML_Parser rootParser) {
   const XmlBigCount countBytesOutput
       = rootParser->m_accounting.countBytesDirect
@@ -8398,11 +8401,11 @@ unsignedCharToPrintable(unsigned char c) {
     return "dead code";
   }
   assert(0); /* never gets here */
 }
 
-#endif /* XML_DTD */
+#endif /* defined(XML_DTD) || XML_GE == 1 */
 
 static unsigned long
 getDebugLevel(const char *variableName, unsigned long defaultDebugLevel) {
   const char *const valueOrNull = getenv(variableName);
   if (valueOrNull == NULL) {
diff --git a/expat/xmlwf/xmlwf.c b/expat/xmlwf/xmlwf.c
index d5157ef5..de3ac40c 100644
--- a/expat/xmlwf/xmlwf.c
+++ b/expat/xmlwf/xmlwf.c
@@ -1094,13 +1094,14 @@ tmain(int argc, XML_Char **argv) {
         errno = ERANGE;
         tperror(T("invalid amplification limit") T(
             " (needs a floating point number greater or equal than 1.0)"));
         exit(XMLWF_EXIT_USAGE_ERROR);
       }
-#ifndef XML_DTD
-      ftprintf(stderr, T("Warning: Given amplification limit ignored") T(
-                           ", xmlwf has been compiled without DTD support.\n"));
+#if ! defined(XML_DTD) && XML_GE == 0
+      ftprintf(stderr,
+               T("Warning: Given amplification limit ignored")
+                   T(", xmlwf has been compiled without DTD/GE support.\n"));
 #endif
       break;
     }
     case T('b'): {
       const XML_Char *valueText = NULL;
@@ -1115,13 +1116,14 @@ tmain(int argc, XML_Char **argv) {
         tperror(T("invalid ignore threshold")
                     T(" (needs an integer from 0 to 2^64-1)"));
         exit(XMLWF_EXIT_USAGE_ERROR);
       }
       attackThresholdGiven = XML_TRUE;
-#ifndef XML_DTD
-      ftprintf(stderr, T("Warning: Given attack threshold ignored") T(
-                           ", xmlwf has been compiled without DTD support.\n"));
+#if ! defined(XML_DTD) && XML_GE == 0
+      ftprintf(stderr,
+               T("Warning: Given attack threshold ignored")
+                   T(", xmlwf has been compiled without DTD/GE support.\n"));
 #endif
       break;
     }
     case T('\0'):
       if (j > 1) {
@@ -1153,17 +1155,17 @@ tmain(int argc, XML_Char **argv) {
       tperror(T("Could not instantiate parser"));
       exit(XMLWF_EXIT_INTERNAL_ERROR);
     }
 
     if (attackMaximumAmplification != -1.0f) {
-#ifdef XML_DTD
+#if defined(XML_DTD) || XML_GE == 1
       XML_SetBillionLaughsAttackProtectionMaximumAmplification(
           parser, attackMaximumAmplification);
 #endif
     }
     if (attackThresholdGiven) {
-#ifdef XML_DTD
+#if defined(XML_DTD) || XML_GE == 1
       XML_SetBillionLaughsAttackProtectionActivationThreshold(
           parser, attackThresholdBytes);
 #else
       (void)attackThresholdBytes; // silence -Wunused-but-set-variable
 #endif

Vulnerable code:         _expat_def_file_toggle(EXPAT_DTD _EXPAT_COMMENT_DTD)
#ifdef XML_DTD
/* Added in Expat 2.4.0. */
/* Added in Expat 2.4.0. */
#if defined(XML_DTD)
@_EXPAT_COMMENT_DTD@ XML_SetBillionLaughsAttackProtectionActivationThreshold @69
@_EXPAT_COMMENT_DTD@ XML_SetBillionLaughsAttackProtectionMaximumAmplification @70
#ifdef XML_DTD
#endif /* XML_DTD */
#ifdef XML_DTD
#endif /* XML_DTD */
#ifdef XML_DTD
#ifdef XML_DTD
#ifdef XML_DTD
    /* Added in Expat 2.4.0. */
#ifdef XML_DTD
#endif /* XML_DTD */
#ifdef XML_DTD
#endif /* XML_DTD */
#ifdef XML_DTD
#ifdef XML_DTD
#endif /* XML_DTD */
#ifdef XML_DTD
#  ifdef XML_DTD
#ifdef XML_DTD
#  ifdef XML_DTD
#ifdef XML_DTD
    case XML_ROLE_TEXT_DECL:      // bytes accounted in processXmlDecl
#ifdef XML_DTD
#ifdef XML_DTD
#ifdef XML_DTD
#endif /* XML_DTD */
#ifdef XML_DTD
#ifdef XML_DTD
#ifdef XML_DTD
#endif /* XML_DTD */
#ifdef XML_DTD
#ifdef XML_DTD
#ifdef XML_DTD
#ifdef XML_DTD
#endif /* XML_DTD */
#ifndef XML_DTD
      ftprintf(stderr, T("Warning: Given amplification limit ignored") T(
                           ", xmlwf has been compiled without DTD support.\n"));
#ifndef XML_DTD
      ftprintf(stderr, T("Warning: Given attack threshold ignored") T(
                           ", xmlwf has been compiled without DTD support.\n"));
#ifdef XML_DTD
#ifdef XML_DTD
Fixed code:         if(EXPAT_DTD OR EXPAT_GE)
            set(_EXPAT_DTD_OR_GE TRUE)
        else()
            set(_EXPAT_DTD_OR_GE FALSE)
        endif()

        _expat_def_file_toggle(_EXPAT_DTD_OR_GE _EXPAT_COMMENT_DTD_OR_GE)
#if defined(XML_DTD) || XML_GE == 1
/* Added in Expat 2.4.0 for XML_DTD defined and
 * added in Expat 2.6.0 for XML_GE == 1. */
/* Added in Expat 2.4.0 for XML_DTD defined and
 * added in Expat 2.6.0 for XML_GE == 1. */
#if defined(XML_DTD) || XML_GE == 1
@_EXPAT_COMMENT_DTD_OR_GE@ XML_SetBillionLaughsAttackProtectionActivationThreshold @69
@_EXPAT_COMMENT_DTD_OR_GE@ XML_SetBillionLaughsAttackProtectionMaximumAmplification @70
#if defined(XML_DTD) || XML_GE == 1
#endif /* defined(XML_DTD) || XML_GE == 1 */
#if defined(XML_DTD) || XML_GE == 1
#endif /* defined(XML_DTD) || XML_GE == 1 */
#if defined(XML_DTD) || XML_GE == 1
#if defined(XML_DTD) || XML_GE == 1
#if defined(XML_DTD) || XML_GE == 1
    /* Added in Expat 2.4.0 for XML_DTD defined and
     * added in Expat 2.6.0 for XML_GE == 1. */
#if defined(XML_DTD) || XML_GE == 1
#endif /* defined(XML_DTD) || XML_GE == 1 */
#if defined(XML_DTD) || XML_GE == 1
#endif /* defined(XML_DTD) || XML_GE == 1 */
#if defined(XML_DTD) || XML_GE == 1
#if defined(XML_DTD) || XML_GE == 1
#endif /* defined(XML_DTD) || XML_GE == 1 */
#if defined(XML_DTD) || XML_GE == 1
#  if defined(XML_DTD) || XML_GE == 1
#if defined(XML_DTD) || XML_GE == 1
#  if defined(XML_DTD) || XML_GE == 1
#if defined(XML_DTD) || XML_GE == 1
#  ifdef XML_DTD
    case XML_ROLE_TEXT_DECL: // bytes accounted in processXmlDecl
#  endif
#if defined(XML_DTD) || XML_GE == 1
#if defined(XML_DTD) || XML_GE == 1
#if defined(XML_DTD) || XML_GE == 1
#endif /* defined(XML_DTD) || XML_GE == 1 */
#if defined(XML_DTD) || XML_GE == 1
#if defined(XML_DTD) || XML_GE == 1
#if defined(XML_DTD) || XML_GE == 1
#endif /* defined(XML_DTD) || XML_GE == 1 */
#if defined(XML_DTD) || XML_GE == 1
#if defined(XML_DTD) || XML_GE == 1
#if defined(XML_DTD) || XML_GE == 1
#if defined(XML_DTD) || XML_GE == 1
#endif /* defined(XML_DTD) || XML_GE == 1 */
#if ! defined(XML_DTD) && XML_GE == 0
      ftprintf(stderr,
               T("Warning: Given amplification limit ignored")
                   T(", xmlwf has been compiled without DTD/GE support.\n"));
#if ! defined(XML_DTD) && XML_GE == 0
      ftprintf(stderr,
               T("Warning: Given attack threshold ignored")
                   T(", xmlwf has been compiled without DTD/GE support.\n"));
#if defined(XML_DTD) || XML_GE == 1
#if defined(XML_DTD) || XML_GE == 1