diff --git a/backport-Autotools-Give-test-suite-access-to-internal-symbols.patch b/backport-Autotools-Give-test-suite-access-to-internal-symbols.patch new file mode 100644 index 0000000000000000000000000000000000000000..8b08f7de39343f85ffeadeebac4a0c82bd5bd9ea --- /dev/null +++ b/backport-Autotools-Give-test-suite-access-to-internal-symbols.patch @@ -0,0 +1,58 @@ +From f01a61402cd44bb0cb59db43e70309c01acc50d1 Mon Sep 17 00:00:00 2001 +From: Sebastian Pipping +Date: Mon, 5 Apr 2021 17:23:26 +0200 +Subject: [PATCH] Autotools: Give test suite access to internal symbols + +--- + lib/Makefile.am | 10 +++++++++- + tests/Makefile.am | 4 ++-- + 2 files changed, 11 insertions(+), 3 deletions(-) + +diff --git a/lib/Makefile.am b/lib/Makefile.am +index 05343e2..f35a2e1 100644 +--- a/lib/Makefile.am ++++ b/lib/Makefile.am +@@ -34,16 +34,24 @@ include_HEADERS = \ + expat_external.h + + lib_LTLIBRARIES = libexpat.la ++noinst_LTLIBRARIES = libexpatinternal.la + + libexpat_la_LDFLAGS = \ + -no-undefined \ + -version-info @LIBCURRENT@:@LIBREVISION@:@LIBAGE@ + +-libexpat_la_SOURCES = \ ++libexpat_la_SOURCES = ++ ++# This layer of indirection allows ++# the test suite to access internal symbols ++# despite compiling with -fvisibility=hidden ++libexpatinternal_la_SOURCES = \ + xmlparse.c \ + xmltok.c \ + xmlrole.c + ++libexpat_la_LIBADD = libexpatinternal.la ++ + doc_DATA = \ + ../AUTHORS \ + ../Changes +diff --git a/tests/Makefile.am b/tests/Makefile.am +index e19fc1a..9724717 100644 +--- a/tests/Makefile.am ++++ b/tests/Makefile.am +@@ -52,8 +52,8 @@ runtests_SOURCES = \ + runtestspp_SOURCES = \ + runtestspp.cpp + +-runtests_LDADD = libruntests.a ../lib/libexpat.la +-runtestspp_LDADD = libruntests.a ../lib/libexpat.la ++runtests_LDADD = libruntests.a ../lib/libexpatinternal.la ++runtestspp_LDADD = libruntests.a ../lib/libexpatinternal.la + + EXTRA_DIST = \ + chardata.h \ +-- +1.8.3.1 + diff --git a/backport-CVE-2013-0340-Autotools-CMake-Suppress-Wpedantic-ms-format-false-p.patch b/backport-CVE-2013-0340-Autotools-CMake-Suppress-Wpedantic-ms-format-false-p.patch new file mode 100644 index 0000000000000000000000000000000000000000..8b22fb1bc593dfa58bc23364a0692653332c4296 --- /dev/null +++ b/backport-CVE-2013-0340-Autotools-CMake-Suppress-Wpedantic-ms-format-false-p.patch @@ -0,0 +1,50 @@ +From 1e053c698b7ff8f7b4cc3c7c6e9945b72c3d5286 Mon Sep 17 00:00:00 2001 +From: Sebastian Pipping +Date: Tue, 20 Apr 2021 19:01:30 +0200 +Subject: [PATCH] Autotools|CMake: Suppress -Wpedantic-ms-format false + positives +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit + +Addresses warning: +ISO C does not support the ‘I64’ ms_printf length modifier. + +It seems correct and relevant with __USE_MINGW_ANSI_STDIO, only. +And -Werror doesn't tolerate false positives... +--- + CMakeLists.txt | 4 ++++ + configure.ac | 2 +- + 2 files changed, 5 insertions(+), 1 deletion(-) + +diff --git a/CMakeLists.txt b/CMakeLists.txt +index 951df0e..96ac5da 100644 +--- a/CMakeLists.txt ++++ b/CMakeLists.txt +@@ -248,6 +248,10 @@ if(FLAG_VISIBILITY) + add_definitions(-DXML_ENABLE_VISIBILITY=1) + set(EXTRA_COMPILE_FLAGS "${EXTRA_COMPILE_FLAGS} -fvisibility=hidden") + endif(FLAG_VISIBILITY) ++if(MINGW) ++ # Without __USE_MINGW_ANSI_STDIO the compiler produces a false positive ++ set(EXTRA_COMPILE_FLAGS "${EXTRA_COMPILE_FLAGS} -Wno-pedantic-ms-format") ++endif() + if (EXPAT_WARNINGS_AS_ERRORS) + if(MSVC) + add_definitions(/WX) +diff --git a/configure.ac b/configure.ac +index f26ce6c..7d60a2c 100644 +--- a/configure.ac ++++ b/configure.ac +@@ -111,7 +111,7 @@ AS_IF([test "$GCC" = yes], + AX_APPEND_COMPILE_FLAGS([-fno-strict-aliasing -Wmissing-prototypes -Wstrict-prototypes], [CFLAGS]) + AX_APPEND_COMPILE_FLAGS([-pedantic -Wduplicated-cond -Wduplicated-branches -Wlogical-op], [CFLAGS]) + AX_APPEND_COMPILE_FLAGS([-Wrestrict -Wnull-dereference -Wjump-misses-init -Wdouble-promotion], [CFLAGS]) +- AX_APPEND_COMPILE_FLAGS([-Wshadow -Wformat=2 -Wmisleading-indentation], [CFLAGS])]) ++ AX_APPEND_COMPILE_FLAGS([-Wshadow -Wformat=2 -Wno-pedantic-ms-format -Wmisleading-indentation], [CFLAGS])]) + + AC_LANG_PUSH([C++]) + AC_PROG_CXX +-- +1.8.3.1 + diff --git a/backport-CVE-2013-0340-Changes-Document-protection-against-billion-laughs-a.patch b/backport-CVE-2013-0340-Changes-Document-protection-against-billion-laughs-a.patch new file mode 100644 index 0000000000000000000000000000000000000000..0de92a612e668e9bb817f4d3c57b8eb9c1859897 --- /dev/null +++ b/backport-CVE-2013-0340-Changes-Document-protection-against-billion-laughs-a.patch @@ -0,0 +1,68 @@ +From 3f2f8786623cc3e89a1f4384715b3ad178c5ee2c Mon Sep 17 00:00:00 2001 +From: Sebastian Pipping +Date: Mon, 19 Apr 2021 15:08:17 +0200 +Subject: [PATCH] Changes: Document protection against billion laughs attacks + +--- + Changes | 34 ++++++++++++++++++++++++++++++++++ + 1 file changed, 34 insertions(+) + +diff --git a/Changes b/Changes +index 2ecc8a0..a435999 100644 +--- a/Changes ++++ b/Changes +@@ -3,10 +3,39 @@ NOTE: We are looking for help with a few things: + If you can help, please get in touch. Thanks! + + Release 2.2.9 Wed Septemper 25 2019 ++ Security fixes: ++ #34 #466 CVE-2013-0340/CWE-776 -- Protect against billion laughs attacks ++ (denial-of-service; flavors targeting CPU time or RAM or both, ++ leveraging general entities or parameter entities or both) ++ by tracking and limiting the input amplification factor ++ ( := ( + ) / ). ++ By conservative default, amplification up to a factor of 100.0 ++ is tolerated and rejection only starts after 8 MiB of output bytes ++ (= + ) have been processed. ++ A new error code XML_ERROR_AMPLIFICATION_LIMIT_BREACH signals ++ this condition. ++ + Bug fixes: + #390 #395 Fix undefined behavior during parsing when compiled with + -DXML_UNICODE that was introduced with Expat 2.0.1 + ++ New features: ++ #34 #466 Add two new API functions to further tighten billion laughs ++ protection parameters when desired. ++ - XML_SetBillionLaughsAttackProtectionMaximumAmplification ++ - XML_SetBillionLaughsAttackProtectionActivationThreshold ++ Please see file "doc/reference.html" for more details. ++ If you ever need to increase the defaults for non-attack XML ++ payload, please file a bug report with libexpat. ++ #34 #466 Introduce environment switches EXPAT_ACCOUNTING_DEBUG=(0|1|2|3) ++ and EXPAT_ENTITY_DEBUG=(0|1) for runtime debugging of accounting ++ and entity processing; specific behavior of these values may ++ change in the future. ++ #34 #466 xmlwf: Add arguments "-a FACTOR" and "-b BYTES" to further tighten ++ billion laughs protection parameters when desired. ++ If you ever need to increase the defaults for non-attack XML ++ payload, please file a bug report with libexpat. ++ + Other changes: + examples: Drop executable bits from elements.c + #349 Windows: Change the name of the Windows DLLs from expat*.dll +@@ -20,6 +49,11 @@ Release 2.2.9 Wed Septemper 25 2019 + + Special thanks to: + Ben Wagner ++ Nick Wellnhofer ++ Yury Gribov ++ and ++ Clang LeakSan ++ JetBrains + + Release 2.2.8 Fri Septemper 13 2019 + Security fixes: +-- +1.8.3.1 + diff --git a/backport-CVE-2013-0340-doc-reference.html-Document-billion-laughs-attack-pr.patch b/backport-CVE-2013-0340-doc-reference.html-Document-billion-laughs-attack-pr.patch new file mode 100644 index 0000000000000000000000000000000000000000..009e92b7853fe2f07ddc4ed94a42bf2126a9b2b5 --- /dev/null +++ b/backport-CVE-2013-0340-doc-reference.html-Document-billion-laughs-attack-pr.patch @@ -0,0 +1,130 @@ +From 899c00e613800ef973a93ce8f83b3514992f1afa Mon Sep 17 00:00:00 2001 +From: Sebastian Pipping +Date: Sun, 25 Apr 2021 20:57:17 +0200 +Subject: [PATCH] doc/reference.html: Document billion laughs attack protection + API + +--- + doc/reference.html | 99 ++++++++++++++++++++++++++++++++++++++++++++++++ + 1 file changed, 99 insertions(+) + +diff --git a/doc/reference.html b/doc/reference.html +index 1b37071..06a70e2 100644 +--- a/doc/reference.html ++++ b/doc/reference.html +@@ -149,6 +149,13 @@ interface.

+
  • XML_GetInputContext
  • + + ++
  • ++ Billion Laughs Attack Protection ++ ++
  • +
  • Miscellaneous Functions +
      +
    • XML_SetUserData
    • +@@ -2074,6 +2081,98 @@ parse position may be before the beginning of the buffer.

      + return NULL.

      + + ++

      Billion Laughs Attack Protection

      ++ ++

      The functions in this section configure the built-in ++ protection against various forms of ++ billion laughs attacks.

      ++ ++

      XML_SetBillionLaughsAttackProtectionMaximumAmplification

      ++
      ++/* Added in Expat 2.4.0. */
      ++XML_Bool XMLCALL
      ++XML_SetBillionLaughsAttackProtectionMaximumAmplification(XML_Parser p,
      ++                                                         float maximumAmplificationFactor);
      ++
      ++
      ++

      ++ Sets the maximum tolerated amplification factor ++ for protection against ++ billion laughs attacks ++ (default: 100.0) ++ of parser p to maximumAmplificationFactor, and ++ returns XML_TRUE upon success and XML_TRUE upon error. ++

      ++ ++ The amplification factor is calculated as .. ++
      ++    amplification := (direct + indirect) / direct
      ++  
      ++ .. while parsing, whereas ++ direct is the number of bytes read from the primary document in parsing and ++ indirect is the number of bytes added by expanding entities and reading of external DTD files, combined. ++ ++

      For a call to XML_SetBillionLaughsAttackProtectionMaximumAmplification to succeed:

      ++
        ++
      • parser p must be a non-NULL root parser (without any parent parsers) and
      • ++
      • maximumAmplificationFactor must be non-NaN and greater than or equal to 1.0.
      • ++
      ++ ++

      ++ Note: ++ If you ever need to increase this value for non-attack payload, ++ please file a bug report. ++

      ++ ++

      ++ Note: ++ Peak amplifications ++ of factor 15,000 for the entire payload and ++ of factor 30,000 in the middle of parsing ++ have been observed with small benign files in practice. ++ ++ So if you do reduce the maximum allowed amplification, ++ please make sure that the activation threshold is still big enough ++ to not end up with undesired false positives (i.e. benign files being rejected). ++

      ++
      ++ ++

      XML_SetBillionLaughsAttackProtectionActivationThreshold

      ++
      ++/* Added in Expat 2.4.0. */
      ++XML_Bool XMLCALL
      ++XML_SetBillionLaughsAttackProtectionActivationThreshold(XML_Parser p,
      ++                                                        unsigned long long activationThresholdBytes);
      ++
      ++
      ++

      ++ Sets number of output bytes (including amplification from entity expansion and reading DTD files) ++ needed to activate protection against ++ billion laughs attacks ++ (default: 8 MiB) ++ of parser p to activationThresholdBytes, and ++ returns XML_TRUE upon success and XML_TRUE upon error. ++

      ++ ++

      For a call to XML_SetBillionLaughsAttackProtectionActivationThreshold to succeed:

      ++
        ++
      • parser p must be a non-NULL root parser (without any parent parsers).
      • ++
      ++ ++

      ++ Note: ++ If you ever need to increase this value for non-attack payload, ++ please file a bug report. ++

      ++ ++

      ++ Note: ++ Activation thresholds below 4 MiB are known to break support for ++ DITA 1.3 payload ++ and are hence not recommended. ++

      ++
      ++ +

      Miscellaneous functions

      + +

      The functions in this section either obtain state information from +-- +1.8.3.1 + diff --git a/backport-CVE-2013-0340-lib-Add-prefix-expat-to-EXPAT_ENTROPY_DEBUG-1-stderr.patch b/backport-CVE-2013-0340-lib-Add-prefix-expat-to-EXPAT_ENTROPY_DEBUG-1-stderr.patch new file mode 100644 index 0000000000000000000000000000000000000000..22172eed603214fef7eda433fb6a9865722162cd --- /dev/null +++ b/backport-CVE-2013-0340-lib-Add-prefix-expat-to-EXPAT_ENTROPY_DEBUG-1-stderr.patch @@ -0,0 +1,26 @@ +From 857fdc4c3bf47eb3fedcd15d3763f62727476df0 Mon Sep 17 00:00:00 2001 +From: Sebastian Pipping +Date: Wed, 14 Apr 2021 17:30:22 +0200 +Subject: [PATCH] lib: Add prefix "expat: " to EXPAT_ENTROPY_DEBUG=1 stderr + output + +--- + lib/xmlparse.c | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/lib/xmlparse.c b/lib/xmlparse.c +index fce7447..641f005 100644 +--- a/lib/xmlparse.c ++++ b/lib/xmlparse.c +@@ -889,7 +889,7 @@ static unsigned long + ENTROPY_DEBUG(const char *label, unsigned long entropy) { + const char *const EXPAT_ENTROPY_DEBUG = getenv("EXPAT_ENTROPY_DEBUG"); + if (EXPAT_ENTROPY_DEBUG && ! strcmp(EXPAT_ENTROPY_DEBUG, "1")) { +- fprintf(stderr, "Entropy: %s --> 0x%0*lx (%lu bytes)\n", label, ++ fprintf(stderr, "expat: Entropy: %s --> 0x%0*lx (%lu bytes)\n", label, + (int)sizeof(entropy) * 2, entropy, (unsigned long)sizeof(entropy)); + } + return entropy; +-- +1.8.3.1 + diff --git a/backport-CVE-2013-0340-lib-Address-Cppcheck-2.4.1-warning-uninitvar.patch b/backport-CVE-2013-0340-lib-Address-Cppcheck-2.4.1-warning-uninitvar.patch new file mode 100644 index 0000000000000000000000000000000000000000..87b90045c2ef66b9f3ba306d3c7bf0819a38881b --- /dev/null +++ b/backport-CVE-2013-0340-lib-Address-Cppcheck-2.4.1-warning-uninitvar.patch @@ -0,0 +1,36 @@ +From fcd0e14c3e35a56eb7ec42142a12e984fbe1b3c0 Mon Sep 17 00:00:00 2001 +From: Sebastian Pipping +Date: Tue, 20 Apr 2021 14:01:27 +0200 +Subject: [PATCH] lib: Address Cppcheck 2.4.1 warning "uninitvar" + +--- + lib/xmlparse.c | 6 ++++-- + 1 file changed, 4 insertions(+), 2 deletions(-) + +diff --git a/lib/xmlparse.c b/lib/xmlparse.c +index 97e7980..4628def 100644 +--- a/lib/xmlparse.c ++++ b/lib/xmlparse.c +@@ -5581,7 +5581,8 @@ appendAttributeValue(XML_Parser parser, const ENCODING *enc, XML_Bool isCdata, + #endif + + for (;;) { +- const char *next; ++ const char *next ++ = ptr; /* XmlAttributeValueTok doesn't always set the last arg */ + int tok = XmlAttributeValueTok(enc, ptr, end, &next); + #ifdef XML_DTD + if (! accountingDiffTolerated(parser, tok, ptr, next, __LINE__, account)) { +@@ -5792,7 +5793,8 @@ storeEntityValue(XML_Parser parser, const ENCODING *enc, + } + + for (;;) { +- const char *next; ++ const char *next ++ = entityTextPtr; /* XmlEntityValueTok doesn't always set the last arg */ + int tok = XmlEntityValueTok(enc, entityTextPtr, entityTextEnd, &next); + + #ifdef XML_DTD +-- +1.8.3.1 + diff --git a/backport-CVE-2013-0340-lib-Allow-test-suite-to-access-raw-accounting-values.patch b/backport-CVE-2013-0340-lib-Allow-test-suite-to-access-raw-accounting-values.patch new file mode 100644 index 0000000000000000000000000000000000000000..8021394371dcf2d6e657918cb43cabab2df6aab2 --- /dev/null +++ b/backport-CVE-2013-0340-lib-Allow-test-suite-to-access-raw-accounting-values.patch @@ -0,0 +1,63 @@ +From 8af7d22ff029796484c78a16d4c6d44a47cb5729 Mon Sep 17 00:00:00 2001 +From: Sebastian Pipping +Date: Mon, 19 Apr 2021 21:44:38 +0200 +Subject: [PATCH] lib: Allow test suite to access raw accounting values + +--- + lib/internal.h | 7 +++++++ + lib/xmlparse.c | 14 ++++++++++++++ + 2 files changed, 21 insertions(+) + +diff --git a/lib/internal.h b/lib/internal.h +index 40c5033..ce6d27a 100644 +--- a/lib/internal.h ++++ b/lib/internal.h +@@ -139,6 +139,8 @@ + 8388608 // 8 MiB, 2^23 + /* NOTE END */ + ++#include "expat.h" // so we can use type XML_Parser below ++ + #ifdef __cplusplus + extern "C" { + #endif +@@ -146,6 +148,11 @@ extern "C" { + _INTERNAL_trim_to_complete_utf8_characters(const char *from, + const char **fromLimRef); + ++#if defined(XML_DTD) ++unsigned long long testingAccountingGetCountBytesDirect(XML_Parser parser); ++unsigned long long testingAccountingGetCountBytesIndirect(XML_Parser parser); ++#endif ++ + #ifdef __cplusplus + } + #endif +diff --git a/lib/xmlparse.c b/lib/xmlparse.c +index 4628def..fce7447 100644 +--- a/lib/xmlparse.c ++++ b/lib/xmlparse.c +@@ -7357,6 +7357,20 @@ accountingDiffTolerated(XML_Parser originParser, int tok, const char *before, + return tolerated; + } + ++unsigned long long ++testingAccountingGetCountBytesDirect(XML_Parser parser) { ++ if (! parser) ++ return 0; ++ return parser->m_accounting.countBytesDirect; ++} ++ ++unsigned long long ++testingAccountingGetCountBytesIndirect(XML_Parser parser) { ++ if (! parser) ++ return 0; ++ return parser->m_accounting.countBytesIndirect; ++} ++ + static void + entityTrackingReportStats(XML_Parser rootParser, ENTITY *entity, + const char *action, int sourceLine) { +-- +1.8.3.1 + diff --git a/backport-CVE-2013-0340-lib-Fix-accounting-of-CDATA-sections-inside.patch b/backport-CVE-2013-0340-lib-Fix-accounting-of-CDATA-sections-inside.patch new file mode 100644 index 0000000000000000000000000000000000000000..14553917502fcb645b7f0ee026a9af4af1837ede --- /dev/null +++ b/backport-CVE-2013-0340-lib-Fix-accounting-of-CDATA-sections-inside.patch @@ -0,0 +1,119 @@ +From 60959f2b491876199879d97c8ed956eabb0c2e73 Mon Sep 17 00:00:00 2001 +From: Sebastian Pipping +Date: Fri, 14 May 2021 20:09:22 +0200 +Subject: [PATCH] lib: Fix accounting of CDATA sections inside of general + entities + +--- + Changes | 9 +++++---- + lib/xmlparse.c | 20 ++++++++++++-------- + 2 files changed, 17 insertions(+), 12 deletions(-) + +diff --git a/Changes b/Changes +index a435999..e62814b 100644 +--- a/Changes ++++ b/Changes +@@ -4,7 +4,7 @@ NOTE: We are looking for help with a few things: + + Release 2.2.9 Wed Septemper 25 2019 + Security fixes: +- #34 #466 CVE-2013-0340/CWE-776 -- Protect against billion laughs attacks ++ #34 #466 #484 CVE-2013-0340/CWE-776 -- Protect against billion laughs attacks + (denial-of-service; flavors targeting CPU time or RAM or both, + leveraging general entities or parameter entities or both) + by tracking and limiting the input amplification factor +@@ -20,18 +20,18 @@ Release 2.2.9 Wed Septemper 25 2019 + -DXML_UNICODE that was introduced with Expat 2.0.1 + + New features: +- #34 #466 Add two new API functions to further tighten billion laughs ++ #34 #466 #484 Add two new API functions to further tighten billion laughs + protection parameters when desired. + - XML_SetBillionLaughsAttackProtectionMaximumAmplification + - XML_SetBillionLaughsAttackProtectionActivationThreshold + Please see file "doc/reference.html" for more details. + If you ever need to increase the defaults for non-attack XML + payload, please file a bug report with libexpat. +- #34 #466 Introduce environment switches EXPAT_ACCOUNTING_DEBUG=(0|1|2|3) ++ #34 #466 #484 Introduce environment switches EXPAT_ACCOUNTING_DEBUG=(0|1|2|3) + and EXPAT_ENTITY_DEBUG=(0|1) for runtime debugging of accounting + and entity processing; specific behavior of these values may + change in the future. +- #34 #466 xmlwf: Add arguments "-a FACTOR" and "-b BYTES" to further tighten ++ #34 #466 #484 xmlwf: Add arguments "-a FACTOR" and "-b BYTES" to further tighten + billion laughs protection parameters when desired. + If you ever need to increase the defaults for non-attack XML + payload, please file a bug report with libexpat. +@@ -54,6 +54,7 @@ Release 2.2.9 Wed Septemper 25 2019 + and + Clang LeakSan + JetBrains ++ OSS-Fuzz + + Release 2.2.8 Fri Septemper 13 2019 + Security fixes: +diff --git a/lib/xmlparse.c b/lib/xmlparse.c +index 3870dfa..64d5dc3 100644 +--- a/lib/xmlparse.c ++++ b/lib/xmlparse.c +@@ -448,7 +448,8 @@ static enum XML_Error doContent(XML_Parser parser, int startTagLevel, + XML_Bool haveMore, enum XML_Account account); + static enum XML_Error doCdataSection(XML_Parser parser, const ENCODING *, + const char **startPtr, const char *end, +- const char **nextPtr, XML_Bool haveMore); ++ const char **nextPtr, XML_Bool haveMore, ++ enum XML_Account account); + #ifdef XML_DTD + static enum XML_Error doIgnoreSection(XML_Parser parser, const ENCODING *, + const char **startPtr, const char *end, +@@ -3062,7 +3063,8 @@ doContent(XML_Parser parser, int startTagLevel, const ENCODING *enc, + /* END disabled code */ + else if (parser->m_defaultHandler) + reportDefault(parser, enc, s, next); +- result = doCdataSection(parser, enc, &next, end, nextPtr, haveMore); ++ result ++ = doCdataSection(parser, enc, &next, end, nextPtr, haveMore, account); + if (result != XML_ERROR_NONE) + return result; + else if (! next) { +@@ -3691,9 +3693,9 @@ addBinding(XML_Parser parser, PREFIX *prefix, const ATTRIBUTE_ID *attId, + static enum XML_Error PTRCALL + cdataSectionProcessor(XML_Parser parser, const char *start, const char *end, + const char **endPtr) { +- enum XML_Error result +- = doCdataSection(parser, parser->m_encoding, &start, end, endPtr, +- (XML_Bool)! parser->m_parsingStatus.finalBuffer); ++ enum XML_Error result = doCdataSection( ++ parser, parser->m_encoding, &start, end, endPtr, ++ (XML_Bool)! parser->m_parsingStatus.finalBuffer, XML_ACCOUNT_DIRECT); + if (result != XML_ERROR_NONE) + return result; + if (start) { +@@ -3713,7 +3715,8 @@ cdataSectionProcessor(XML_Parser parser, const char *start, const char *end, + */ + static enum XML_Error + doCdataSection(XML_Parser parser, const ENCODING *enc, const char **startPtr, +- const char *end, const char **nextPtr, XML_Bool haveMore) { ++ const char *end, const char **nextPtr, XML_Bool haveMore, ++ enum XML_Account account) { + const char *s = *startPtr; + const char **eventPP; + const char **eventEndPP; +@@ -3732,11 +3735,12 @@ doCdataSection(XML_Parser parser, const ENCODING *enc, const char **startPtr, + const char *next; + int tok = XmlCdataSectionTok(enc, s, end, &next); + #ifdef XML_DTD +- if (! accountingDiffTolerated(parser, tok, s, next, __LINE__, +- XML_ACCOUNT_DIRECT)) { ++ if (! accountingDiffTolerated(parser, tok, s, next, __LINE__, account)) { + accountingOnAbort(parser); + return XML_ERROR_AMPLIFICATION_LIMIT_BREACH; + } ++#else ++ UNUSED_P(account); + #endif + *eventEndPP = next; + switch (tok) { +-- +1.8.3.1 + diff --git a/backport-CVE-2013-0340-lib-Make-EXPAT_ENTROPY_DEBUG-consistent-with-other-E.patch b/backport-CVE-2013-0340-lib-Make-EXPAT_ENTROPY_DEBUG-consistent-with-other-E.patch new file mode 100644 index 0000000000000000000000000000000000000000..b2a7f60c7bfddc7d5b1996d5d073d6efe8265f7c --- /dev/null +++ b/backport-CVE-2013-0340-lib-Make-EXPAT_ENTROPY_DEBUG-consistent-with-other-E.patch @@ -0,0 +1,27 @@ +From 29c3748788ff5ba0e4b14b02dfa15080177a3c8c Mon Sep 17 00:00:00 2001 +From: Sebastian Pipping +Date: Mon, 19 Apr 2021 20:39:42 +0200 +Subject: [PATCH] lib: Make EXPAT_ENTROPY_DEBUG consistent with other + EXPAT_*_DEBUG variables + +--- + lib/xmlparse.c | 3 +-- + 1 file changed, 1 insertion(+), 2 deletions(-) + +diff --git a/lib/xmlparse.c b/lib/xmlparse.c +index 641f005..adaab23 100644 +--- a/lib/xmlparse.c ++++ b/lib/xmlparse.c +@@ -887,8 +887,7 @@ gather_time_entropy(void) { + + static unsigned long + ENTROPY_DEBUG(const char *label, unsigned long entropy) { +- const char *const EXPAT_ENTROPY_DEBUG = getenv("EXPAT_ENTROPY_DEBUG"); +- if (EXPAT_ENTROPY_DEBUG && ! strcmp(EXPAT_ENTROPY_DEBUG, "1")) { ++ if (getDebugLevel("EXPAT_ENTROPY_DEBUG", 0) >= 1u) { + fprintf(stderr, "expat: Entropy: %s --> 0x%0*lx (%lu bytes)\n", label, + (int)sizeof(entropy) * 2, entropy, (unsigned long)sizeof(entropy)); + } +-- +1.8.3.1 + diff --git a/backport-CVE-2013-0340-lib-Protect-against-billion-laughs-attacks-approach-.patch b/backport-CVE-2013-0340-lib-Protect-against-billion-laughs-attacks-approach-.patch new file mode 100644 index 0000000000000000000000000000000000000000..13e359ee1f1e0e7ea23bd602f71f4e71349d4d4b --- /dev/null +++ b/backport-CVE-2013-0340-lib-Protect-against-billion-laughs-attacks-approach-.patch @@ -0,0 +1,1705 @@ +From b1d039607d3d8a042bf0466bfcc1c0f104e353c8 Mon Sep 17 00:00:00 2001 +From: Sebastian Pipping +Date: Mon, 19 Apr 2021 21:42:51 +0200 +Subject: [PATCH] lib: Protect against billion laughs attacks (approach 3.0.21) + +--- + lib/expat.h | 21 +- + lib/internal.h | 30 ++ + lib/libexpat.def | 3 + + lib/libexpatw.def | 3 + + lib/xmlparse.c | 1126 ++++++++++++++++++++++++++++++++++++++++++++++++++--- + 5 files changed, 1130 insertions(+), 53 deletions(-) + +diff --git a/lib/expat.h b/lib/expat.h +index 48a6e2a..0fb70d9 100644 +--- a/lib/expat.h ++++ b/lib/expat.h +@@ -115,7 +115,9 @@ enum XML_Error { + XML_ERROR_RESERVED_PREFIX_XMLNS, + XML_ERROR_RESERVED_NAMESPACE_URI, + /* Added in 2.2.1. */ +- XML_ERROR_INVALID_ARGUMENT ++ XML_ERROR_INVALID_ARGUMENT, ++ /* Added in 2.2.9. */ ++ XML_ERROR_AMPLIFICATION_LIMIT_BREACH + }; + + enum XML_Content_Type { +@@ -997,7 +999,10 @@ enum XML_FeatureEnum { + XML_FEATURE_SIZEOF_XML_LCHAR, + XML_FEATURE_NS, + XML_FEATURE_LARGE_SIZE, +- XML_FEATURE_ATTR_INFO ++ XML_FEATURE_ATTR_INFO, ++ /* Added in Expat 2.2.9. */ ++ XML_FEATURE_BILLION_LAUGHS_ATTACK_PROTECTION_MAXIMUM_AMPLIFICATION_DEFAULT, ++ XML_FEATURE_BILLION_LAUGHS_ATTACK_PROTECTION_ACTIVATION_THRESHOLD_DEFAULT + /* Additional features must be added to the end of this enum. */ + }; + +@@ -1010,6 +1015,18 @@ typedef struct { + XMLPARSEAPI(const XML_Feature *) + XML_GetFeatureList(void); + ++#ifdef XML_DTD ++/* Added in Expat 2.2.9. */ ++XMLPARSEAPI(XML_Bool) ++XML_SetBillionLaughsAttackProtectionMaximumAmplification( ++ XML_Parser parser, float maximumAmplificationFactor); ++ ++/* Added in Expat 2.2.9. */ ++XMLPARSEAPI(XML_Bool) ++XML_SetBillionLaughsAttackProtectionActivationThreshold( ++ XML_Parser parser, unsigned long long activationThresholdBytes); ++#endif ++ + /* Expat follows the semantic versioning convention. + See http://semver.org. + */ +diff --git a/lib/internal.h b/lib/internal.h +index 60913da..d8b31fa 100644 +--- a/lib/internal.h ++++ b/lib/internal.h +@@ -101,10 +101,40 @@ + # endif + #endif + ++#include // ULONG_MAX ++ ++#if defined(_WIN32) && ! defined(__USE_MINGW_ANSI_STDIO) ++# define EXPAT_FMT_ULL(midpart) "%" midpart "I64u" ++# if defined(_WIN64) // Note: modifier "td" does not work for MinGW ++# define EXPAT_FMT_PTRDIFF_T(midpart) "%" midpart "I64d" ++# else ++# define EXPAT_FMT_PTRDIFF_T(midpart) "%" midpart "d" ++# endif ++#else ++# define EXPAT_FMT_ULL(midpart) "%" midpart "llu" ++# if ! defined(ULONG_MAX) ++# error Compiler did not define ULONG_MAX for us ++# elif ULONG_MAX == 18446744073709551615u // 2^64-1 ++# define EXPAT_FMT_PTRDIFF_T(midpart) "%" midpart "ld" ++# else ++# define EXPAT_FMT_PTRDIFF_T(midpart) "%" midpart "d" ++# endif ++#endif ++ + #ifndef UNUSED_P + # define UNUSED_P(p) (void)p + #endif + ++/* NOTE BEGIN If you ever patch these defaults to greater values ++ for non-attack XML payload in your environment, ++ please file a bug report with libexpat. Thank you! ++*/ ++#define EXPAT_BILLION_LAUGHS_ATTACK_PROTECTION_MAXIMUM_AMPLIFICATION_DEFAULT \ ++ 100.0f ++#define EXPAT_BILLION_LAUGHS_ATTACK_PROTECTION_ACTIVATION_THRESHOLD_DEFAULT \ ++ 8388608 // 8 MiB, 2^23 ++/* NOTE END */ ++ + #ifdef __cplusplus + extern "C" { + #endif +diff --git a/lib/libexpat.def b/lib/libexpat.def +index 16faf59..5aefa6d 100644 +--- a/lib/libexpat.def ++++ b/lib/libexpat.def +@@ -76,3 +76,6 @@ EXPORTS + XML_SetHashSalt @67 + ; added with version 2.2.5 + _INTERNAL_trim_to_complete_utf8_characters @68 ++; added with version 2.2.9 ++ XML_SetBillionLaughsAttackProtectionActivationThreshold @69 ++ XML_SetBillionLaughsAttackProtectionMaximumAmplification @70 +diff --git a/lib/libexpatw.def b/lib/libexpatw.def +index 16faf59..5aefa6d 100644 +--- a/lib/libexpatw.def ++++ b/lib/libexpatw.def +@@ -76,3 +76,6 @@ EXPORTS + XML_SetHashSalt @67 + ; added with version 2.2.5 + _INTERNAL_trim_to_complete_utf8_characters @68 ++; added with version 2.2.9 ++ XML_SetBillionLaughsAttackProtectionActivationThreshold @69 ++ XML_SetBillionLaughsAttackProtectionMaximumAmplification @70 +diff --git a/lib/xmlparse.c b/lib/xmlparse.c +index 005e675..98ac5c3 100644 +--- a/lib/xmlparse.c ++++ b/lib/xmlparse.c +@@ -47,6 +47,7 @@ + #include /* UINT_MAX */ + #include /* fprintf */ + #include /* getenv, rand_s */ ++#include /* isnan */ + + #if defined(_WIN32) && defined(_MSC_VER) && (_MSC_VER < 1600) + /* vs2008/9.0 and earlier lack stdint.h; _MSC_VER 1600 is vs2010/10.0 */ +@@ -382,6 +383,31 @@ typedef struct open_internal_entity { + XML_Bool betweenDecl; /* WFC: PE Between Declarations */ + } OPEN_INTERNAL_ENTITY; + ++enum XML_Account { ++ XML_ACCOUNT_DIRECT, /* bytes directly passed to the Expat parser */ ++ XML_ACCOUNT_ENTITY_EXPANSION, /* intermediate bytes produced during entity ++ expansion */ ++ XML_ACCOUNT_NONE /* i.e. do not account, was accounted already */ ++}; ++ ++#ifdef XML_DTD ++typedef unsigned long long XmlBigCount; ++typedef struct accounting { ++ XmlBigCount countBytesDirect; ++ XmlBigCount countBytesIndirect; ++ int debugLevel; ++ float maximumAmplificationFactor; // >=1.0 ++ unsigned long long activationThresholdBytes; ++} ACCOUNTING; ++ ++typedef struct entity_stats { ++ unsigned int countEverOpened; ++ unsigned int currentDepth; ++ unsigned int maximumDepthSeen; ++ int debugLevel; ++} ENTITY_STATS; ++#endif /* XML_DTD */ ++ + typedef enum XML_Error PTRCALL Processor(XML_Parser parser, const char *start, + const char *end, const char **endPtr); + +@@ -412,13 +438,14 @@ static enum XML_Error initializeEncoding(XML_Parser parser); + static enum XML_Error doProlog(XML_Parser parser, const ENCODING *enc, + const char *s, const char *end, int tok, + const char *next, const char **nextPtr, +- XML_Bool haveMore, XML_Bool allowClosingDoctype); ++ 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 doContent(XML_Parser parser, int startTagLevel, + const ENCODING *enc, const char *start, + const char *end, const char **endPtr, +- XML_Bool haveMore); ++ XML_Bool haveMore, enum XML_Account account); + static enum XML_Error doCdataSection(XML_Parser parser, const ENCODING *, + const char **startPtr, const char *end, + const char **nextPtr, XML_Bool haveMore); +@@ -431,7 +458,8 @@ static enum XML_Error doIgnoreSection(XML_Parser parser, const ENCODING *, + static void freeBindings(XML_Parser parser, BINDING *bindings); + static enum XML_Error storeAtts(XML_Parser parser, const ENCODING *, + const char *s, TAG_NAME *tagNamePtr, +- BINDING **bindingsPtr); ++ BINDING **bindingsPtr, ++ enum XML_Account account); + static enum XML_Error addBinding(XML_Parser parser, PREFIX *prefix, + const ATTRIBUTE_ID *attId, const XML_Char *uri, + BINDING **bindingsPtr); +@@ -440,15 +468,18 @@ static int defineAttribute(ELEMENT_TYPE *type, ATTRIBUTE_ID *, XML_Bool isCdata, + XML_Parser parser); + static enum XML_Error storeAttributeValue(XML_Parser parser, const ENCODING *, + XML_Bool isCdata, const char *, +- const char *, STRING_POOL *); ++ const char *, STRING_POOL *, ++ enum XML_Account account); + static enum XML_Error appendAttributeValue(XML_Parser parser, const ENCODING *, + XML_Bool isCdata, const char *, +- const char *, STRING_POOL *); ++ const char *, STRING_POOL *, ++ enum XML_Account account); + static ATTRIBUTE_ID *getAttributeId(XML_Parser parser, const ENCODING *enc, + const char *start, const char *end); + static int setElementTypePrefix(XML_Parser parser, ELEMENT_TYPE *); + static enum XML_Error storeEntityValue(XML_Parser parser, const ENCODING *enc, +- const char *start, const char *end); ++ const char *start, const char *end, ++ enum XML_Account account); + static int reportProcessingInstruction(XML_Parser parser, const ENCODING *enc, + const char *start, const char *end); + static int reportComment(XML_Parser parser, const ENCODING *enc, +@@ -512,6 +543,35 @@ static XML_Parser parserCreate(const XML_Char *encodingName, + + static void parserInit(XML_Parser parser, const XML_Char *encodingName); + ++#ifdef XML_DTD ++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, ++ const char *before, const char *after, ++ ptrdiff_t bytesMore, int source_line, ++ enum XML_Account account); ++static XML_Bool accountingDiffTolerated(XML_Parser originParser, int tok, ++ const char *before, const char *after, ++ int source_line, ++ enum XML_Account account); ++ ++static void entityTrackingReportStats(XML_Parser parser, ENTITY *entity, ++ const char *action, int sourceLine); ++static void entityTrackingOnOpen(XML_Parser parser, ENTITY *entity, ++ int sourceLine); ++static void entityTrackingOnClose(XML_Parser parser, ENTITY *entity, ++ int sourceLine); ++ ++static XML_Parser getRootParserOf(XML_Parser parser, ++ unsigned int *outLevelDiff); ++static const char *unsignedCharToPrintable(unsigned char c); ++#endif /* XML_DTD */ ++ ++static unsigned long getDebugLevel(const char *variableName, ++ unsigned long defaultDebugLevel); ++ + #define poolStart(pool) ((pool)->start) + #define poolEnd(pool) ((pool)->ptr) + #define poolLength(pool) ((pool)->ptr - (pool)->start) +@@ -625,6 +685,10 @@ struct XML_ParserStruct { + enum XML_ParamEntityParsing m_paramEntityParsing; + #endif + unsigned long m_hash_secret_salt; ++#ifdef XML_DTD ++ ACCOUNTING m_accounting; ++ ENTITY_STATS m_entity_stats; ++#endif + }; + + #define MALLOC(parser, s) (parser->m_mem.malloc_fcn((s))) +@@ -1064,6 +1128,18 @@ parserInit(XML_Parser parser, const XML_Char *encodingName) { + parser->m_paramEntityParsing = XML_PARAM_ENTITY_PARSING_NEVER; + #endif + parser->m_hash_secret_salt = 0; ++ ++#ifdef XML_DTD ++ 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 ++ = EXPAT_BILLION_LAUGHS_ATTACK_PROTECTION_ACTIVATION_THRESHOLD_DEFAULT; ++ ++ memset(&parser->m_entity_stats, 0, sizeof(ENTITY_STATS)); ++ parser->m_entity_stats.debugLevel = getDebugLevel("EXPAT_ENTITY_DEBUG", 0u); ++#endif + } + + /* moves list of bindings to m_freeBindingList */ +@@ -2327,6 +2403,10 @@ XML_ErrorString(enum XML_Error code) { + /* Added in 2.2.5. */ + case XML_ERROR_INVALID_ARGUMENT: /* Constant added in 2.2.1, already */ + return XML_L("invalid argument"); ++ /* Added in 2.2.9. */ ++ case XML_ERROR_AMPLIFICATION_LIMIT_BREACH: ++ return XML_L( ++ "limit on input amplification factor (from DTD and entities) breached"); + } + return NULL; + } +@@ -2363,41 +2443,75 @@ XML_ExpatVersionInfo(void) { + + const XML_Feature *XMLCALL + XML_GetFeatureList(void) { +- static const XML_Feature features[] +- = {{XML_FEATURE_SIZEOF_XML_CHAR, XML_L("sizeof(XML_Char)"), +- sizeof(XML_Char)}, +- {XML_FEATURE_SIZEOF_XML_LCHAR, XML_L("sizeof(XML_LChar)"), +- sizeof(XML_LChar)}, ++ static const XML_Feature features[] = { ++ {XML_FEATURE_SIZEOF_XML_CHAR, XML_L("sizeof(XML_Char)"), ++ sizeof(XML_Char)}, ++ {XML_FEATURE_SIZEOF_XML_LCHAR, XML_L("sizeof(XML_LChar)"), ++ sizeof(XML_LChar)}, + #ifdef XML_UNICODE +- {XML_FEATURE_UNICODE, XML_L("XML_UNICODE"), 0}, ++ {XML_FEATURE_UNICODE, XML_L("XML_UNICODE"), 0}, + #endif + #ifdef XML_UNICODE_WCHAR_T +- {XML_FEATURE_UNICODE_WCHAR_T, XML_L("XML_UNICODE_WCHAR_T"), 0}, ++ {XML_FEATURE_UNICODE_WCHAR_T, XML_L("XML_UNICODE_WCHAR_T"), 0}, + #endif + #ifdef XML_DTD +- {XML_FEATURE_DTD, XML_L("XML_DTD"), 0}, ++ {XML_FEATURE_DTD, XML_L("XML_DTD"), 0}, + #endif + #ifdef XML_CONTEXT_BYTES +- {XML_FEATURE_CONTEXT_BYTES, XML_L("XML_CONTEXT_BYTES"), +- XML_CONTEXT_BYTES}, ++ {XML_FEATURE_CONTEXT_BYTES, XML_L("XML_CONTEXT_BYTES"), ++ XML_CONTEXT_BYTES}, + #endif + #ifdef XML_MIN_SIZE +- {XML_FEATURE_MIN_SIZE, XML_L("XML_MIN_SIZE"), 0}, ++ {XML_FEATURE_MIN_SIZE, XML_L("XML_MIN_SIZE"), 0}, + #endif + #ifdef XML_NS +- {XML_FEATURE_NS, XML_L("XML_NS"), 0}, ++ {XML_FEATURE_NS, XML_L("XML_NS"), 0}, + #endif + #ifdef XML_LARGE_SIZE +- {XML_FEATURE_LARGE_SIZE, XML_L("XML_LARGE_SIZE"), 0}, ++ {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}, ++ {XML_FEATURE_ATTR_INFO, XML_L("XML_ATTR_INFO"), 0}, + #endif +- {XML_FEATURE_END, NULL, 0}}; ++#ifdef XML_DTD ++ /* Added in Expat 2.2.9. */ ++ {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, ++ XML_L("XML_BLAP_ACT_THRES"), ++ EXPAT_BILLION_LAUGHS_ATTACK_PROTECTION_ACTIVATION_THRESHOLD_DEFAULT}, ++#endif ++ {XML_FEATURE_END, NULL, 0}}; + + return features; + } + ++#ifdef XML_DTD ++XML_Bool XMLCALL ++XML_SetBillionLaughsAttackProtectionMaximumAmplification( ++ XML_Parser parser, float maximumAmplificationFactor) { ++ if ((parser == NULL) || (parser->m_parentParser != NULL) ++ || isnan(maximumAmplificationFactor) ++ || (maximumAmplificationFactor < 1.0f)) { ++ return XML_FALSE; ++ } ++ parser->m_accounting.maximumAmplificationFactor = maximumAmplificationFactor; ++ return XML_TRUE; ++} ++ ++XML_Bool XMLCALL ++XML_SetBillionLaughsAttackProtectionActivationThreshold( ++ XML_Parser parser, unsigned long long activationThresholdBytes) { ++ if ((parser == NULL) || (parser->m_parentParser != NULL)) { ++ return XML_FALSE; ++ } ++ parser->m_accounting.activationThresholdBytes = activationThresholdBytes; ++ return XML_TRUE; ++} ++#endif /* XML_DTD */ ++ + /* 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 +@@ -2450,9 +2564,9 @@ storeRawNames(XML_Parser parser) { + 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); ++ enum XML_Error result = doContent( ++ parser, 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; +@@ -2477,6 +2591,14 @@ externalEntityInitProcessor2(XML_Parser parser, const char *start, + int tok = XmlContentTok(parser->m_encoding, start, end, &next); + switch (tok) { + case XML_TOK_BOM: ++#ifdef XML_DTD ++ if (! accountingDiffTolerated(parser, tok, start, next, __LINE__, ++ XML_ACCOUNT_DIRECT)) { ++ accountingOnAbort(parser); ++ return XML_ERROR_AMPLIFICATION_LIMIT_BREACH; ++ } ++#endif /* XML_DTD */ ++ + /* 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 +@@ -2514,6 +2636,10 @@ externalEntityInitProcessor3(XML_Parser parser, const char *start, + const char *next = start; /* XmlContentTok doesn't always set the last arg */ + parser->m_eventPtr = start; + tok = XmlContentTok(parser->m_encoding, start, end, &next); ++ /* Note: These bytes are accounted later in: ++ - processXmlDecl ++ - externalEntityContentProcessor ++ */ + parser->m_eventEndPtr = next; + + switch (tok) { +@@ -2555,7 +2681,8 @@ externalEntityContentProcessor(XML_Parser parser, const char *start, + const char *end, const char **endPtr) { + enum XML_Error result + = doContent(parser, 1, parser->m_encoding, start, end, endPtr, +- (XML_Bool)! parser->m_parsingStatus.finalBuffer); ++ (XML_Bool)! parser->m_parsingStatus.finalBuffer, ++ XML_ACCOUNT_ENTITY_EXPANSION); + if (result == XML_ERROR_NONE) { + if (! storeRawNames(parser)) + return XML_ERROR_NO_MEMORY; +@@ -2566,7 +2693,7 @@ externalEntityContentProcessor(XML_Parser parser, const char *start, + static enum XML_Error + doContent(XML_Parser parser, int startTagLevel, const ENCODING *enc, + const char *s, const char *end, const char **nextPtr, +- XML_Bool haveMore) { ++ XML_Bool haveMore, enum XML_Account account) { + /* save one level of indirection */ + DTD *const dtd = parser->m_dtd; + +@@ -2584,6 +2711,17 @@ doContent(XML_Parser parser, int startTagLevel, const ENCODING *enc, + for (;;) { + const char *next = s; /* XmlContentTok doesn't always set the last arg */ + int tok = XmlContentTok(enc, s, end, &next); ++#ifdef XML_DTD ++ 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__, ++ account)) { ++ accountingOnAbort(parser); ++ return XML_ERROR_AMPLIFICATION_LIMIT_BREACH; ++ } ++#endif + *eventEndPP = next; + switch (tok) { + case XML_TOK_TRAILING_CR: +@@ -2639,6 +2777,14 @@ doContent(XML_Parser parser, int startTagLevel, const ENCODING *enc, + XML_Char ch = (XML_Char)XmlPredefinedEntityName( + enc, s + enc->minBytesPerChar, next - enc->minBytesPerChar); + if (ch) { ++#ifdef XML_DTD ++ /* 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 */ + if (parser->m_characterDataHandler) + parser->m_characterDataHandler(parser->m_handlerArg, &ch, 1); + else if (parser->m_defaultHandler) +@@ -2757,7 +2903,8 @@ doContent(XML_Parser parser, int startTagLevel, const ENCODING *enc, + } + tag->name.str = (XML_Char *)tag->buf; + *toPtr = XML_T('\0'); +- result = storeAtts(parser, enc, s, &(tag->name), &(tag->bindings)); ++ result ++ = storeAtts(parser, enc, s, &(tag->name), &(tag->bindings), account); + if (result) + return result; + if (parser->m_startElementHandler) +@@ -2781,7 +2928,8 @@ doContent(XML_Parser parser, int startTagLevel, const ENCODING *enc, + if (! name.str) + return XML_ERROR_NO_MEMORY; + poolFinish(&parser->m_tempPool); +- result = storeAtts(parser, enc, s, &name, &bindings); ++ result = storeAtts(parser, enc, s, &name, &bindings, ++ XML_ACCOUNT_NONE /* token spans whole start tag */); + if (result != XML_ERROR_NONE) { + freeBindings(parser, bindings); + return result; +@@ -3045,7 +3193,8 @@ freeBindings(XML_Parser parser, BINDING *bindings) { + */ + static enum XML_Error + storeAtts(XML_Parser parser, const ENCODING *enc, const char *attStr, +- TAG_NAME *tagNamePtr, BINDING **bindingsPtr) { ++ TAG_NAME *tagNamePtr, BINDING **bindingsPtr, ++ enum XML_Account account) { + DTD *const dtd = parser->m_dtd; /* save one level of indirection */ + ELEMENT_TYPE *elementType; + int nDefaultAtts; +@@ -3155,7 +3304,7 @@ storeAtts(XML_Parser parser, const ENCODING *enc, const char *attStr, + /* normalize the attribute value */ + result = storeAttributeValue( + parser, enc, isCdata, parser->m_atts[i].valuePtr, +- parser->m_atts[i].valueEnd, &parser->m_tempPool); ++ parser->m_atts[i].valueEnd, &parser->m_tempPool, account); + if (result) + return result; + appAtts[attIndex] = poolStart(&parser->m_tempPool); +@@ -3584,6 +3733,13 @@ doCdataSection(XML_Parser parser, const ENCODING *enc, const char **startPtr, + for (;;) { + const char *next; + int tok = XmlCdataSectionTok(enc, s, end, &next); ++#ifdef XML_DTD ++ if (! accountingDiffTolerated(parser, tok, s, next, __LINE__, ++ XML_ACCOUNT_DIRECT)) { ++ accountingOnAbort(parser); ++ return XML_ERROR_AMPLIFICATION_LIMIT_BREACH; ++ } ++#endif + *eventEndPP = next; + switch (tok) { + case XML_TOK_CDATA_SECT_CLOSE: +@@ -3728,6 +3884,13 @@ doIgnoreSection(XML_Parser parser, const ENCODING *enc, const char **startPtr, + *eventPP = s; + *startPtr = NULL; + tok = XmlIgnoreSectionTok(enc, s, end, &next); ++# ifdef XML_DTD ++ if (! accountingDiffTolerated(parser, tok, s, next, __LINE__, ++ XML_ACCOUNT_DIRECT)) { ++ accountingOnAbort(parser); ++ return XML_ERROR_AMPLIFICATION_LIMIT_BREACH; ++ } ++# endif + *eventEndPP = next; + switch (tok) { + case XML_TOK_IGNORE_SECT: +@@ -3812,6 +3975,15 @@ processXmlDecl(XML_Parser parser, int isGeneralTextEntity, const char *s, + const char *versionend; + const XML_Char *storedversion = NULL; + int standalone = -1; ++ ++#ifdef XML_DTD ++ if (! accountingDiffTolerated(parser, XML_TOK_XML_DECL, s, next, __LINE__, ++ XML_ACCOUNT_DIRECT)) { ++ accountingOnAbort(parser); ++ return XML_ERROR_AMPLIFICATION_LIMIT_BREACH; ++ } ++#endif ++ + if (! (parser->m_ns ? XmlParseXmlDeclNS : XmlParseXmlDecl)( + isGeneralTextEntity, parser->m_encoding, s, next, &parser->m_eventPtr, + &version, &versionend, &encodingName, &newEncoding, &standalone)) { +@@ -3961,6 +4133,10 @@ entityValueInitProcessor(XML_Parser parser, const char *s, const char *end, + + for (;;) { + tok = XmlPrologTok(parser->m_encoding, start, end, &next); ++ /* Note: Except for XML_TOK_BOM below, these bytes are accounted later in: ++ - storeEntityValue ++ - processXmlDecl ++ */ + parser->m_eventEndPtr = next; + if (tok <= 0) { + if (! parser->m_parsingStatus.finalBuffer && tok != XML_TOK_INVALID) { +@@ -3979,7 +4155,8 @@ entityValueInitProcessor(XML_Parser parser, const char *s, const char *end, + break; + } + /* found end of entity value - can store it now */ +- return storeEntityValue(parser, parser->m_encoding, s, end); ++ return storeEntityValue(parser, parser->m_encoding, s, end, ++ XML_ACCOUNT_DIRECT); + } else if (tok == XML_TOK_XML_DECL) { + enum XML_Error result; + result = processXmlDecl(parser, 0, start, next); +@@ -4006,6 +4183,14 @@ entityValueInitProcessor(XML_Parser parser, const char *s, const char *end, + */ + else if (tok == XML_TOK_BOM && next == end + && ! parser->m_parsingStatus.finalBuffer) { ++# ifdef XML_DTD ++ if (! accountingDiffTolerated(parser, tok, s, next, __LINE__, ++ XML_ACCOUNT_DIRECT)) { ++ accountingOnAbort(parser); ++ return XML_ERROR_AMPLIFICATION_LIMIT_BREACH; ++ } ++# endif ++ + *nextPtr = next; + return XML_ERROR_NONE; + } +@@ -4048,16 +4233,24 @@ externalParEntProcessor(XML_Parser parser, const char *s, const char *end, + } + /* This would cause the next stage, i.e. doProlog to be passed XML_TOK_BOM. + However, when parsing an external subset, doProlog will not accept a BOM +- as valid, and report a syntax error, so we have to skip the BOM ++ as valid, and report a syntax error, so we have to skip the BOM, and ++ account for the BOM bytes. + */ + else if (tok == XML_TOK_BOM) { ++ if (! accountingDiffTolerated(parser, tok, s, next, __LINE__, ++ XML_ACCOUNT_DIRECT)) { ++ accountingOnAbort(parser); ++ return XML_ERROR_AMPLIFICATION_LIMIT_BREACH; ++ } ++ + s = next; + tok = XmlPrologTok(parser->m_encoding, s, end, &next); + } + + parser->m_processor = prologProcessor; + return doProlog(parser, parser->m_encoding, s, end, tok, next, nextPtr, +- (XML_Bool)! parser->m_parsingStatus.finalBuffer, XML_TRUE); ++ (XML_Bool)! parser->m_parsingStatus.finalBuffer, XML_TRUE, ++ XML_ACCOUNT_DIRECT); + } + + static enum XML_Error PTRCALL +@@ -4070,6 +4263,9 @@ entityValueProcessor(XML_Parser parser, const char *s, const char *end, + + for (;;) { + tok = XmlPrologTok(enc, start, end, &next); ++ /* Note: These bytes are accounted later in: ++ - storeEntityValue ++ */ + if (tok <= 0) { + if (! parser->m_parsingStatus.finalBuffer && tok != XML_TOK_INVALID) { + *nextPtr = s; +@@ -4087,7 +4283,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); ++ return storeEntityValue(parser, enc, s, end, XML_ACCOUNT_DIRECT); + } + start = next; + } +@@ -4101,13 +4297,14 @@ prologProcessor(XML_Parser parser, const char *s, const char *end, + const char *next = s; + int 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_Bool)! parser->m_parsingStatus.finalBuffer, XML_TRUE, ++ XML_ACCOUNT_DIRECT); + } + + static enum XML_Error + doProlog(XML_Parser parser, const ENCODING *enc, const char *s, const char *end, + int tok, const char *next, const char **nextPtr, XML_Bool haveMore, +- XML_Bool allowClosingDoctype) { ++ XML_Bool allowClosingDoctype, enum XML_Account account) { + #ifdef XML_DTD + static const XML_Char externalSubsetName[] = {ASCII_HASH, '\0'}; + #endif /* XML_DTD */ +@@ -4134,6 +4331,10 @@ doProlog(XML_Parser parser, const ENCODING *enc, const char *s, const char *end, + static const XML_Char enumValueSep[] = {ASCII_PIPE, '\0'}; + static const XML_Char enumValueStart[] = {ASCII_LPAREN, '\0'}; + ++#ifndef XML_DTD ++ UNUSED_P(account); ++#endif ++ + /* save one level of indirection */ + DTD *const dtd = parser->m_dtd; + +@@ -4198,6 +4399,19 @@ doProlog(XML_Parser parser, const ENCODING *enc, const char *s, const char *end, + } + } + role = XmlTokenRole(&parser->m_prologState, tok, s, next, enc); ++#ifdef XML_DTD ++ 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 ++ break; ++ default: ++ if (! accountingDiffTolerated(parser, tok, s, next, __LINE__, account)) { ++ accountingOnAbort(parser); ++ return XML_ERROR_AMPLIFICATION_LIMIT_BREACH; ++ } ++ } ++#endif + switch (role) { + case XML_ROLE_XML_DECL: { + enum XML_Error result = processXmlDecl(parser, 0, s, next); +@@ -4473,7 +4687,8 @@ doProlog(XML_Parser parser, const ENCODING *enc, const char *s, const char *end, + const XML_Char *attVal; + enum XML_Error result = storeAttributeValue( + parser, enc, parser->m_declAttributeIsCdata, +- s + enc->minBytesPerChar, next - enc->minBytesPerChar, &dtd->pool); ++ s + enc->minBytesPerChar, next - enc->minBytesPerChar, &dtd->pool, ++ XML_ACCOUNT_NONE); + if (result) + return result; + attVal = poolStart(&dtd->pool); +@@ -4506,8 +4721,9 @@ doProlog(XML_Parser parser, const ENCODING *enc, const char *s, const char *end, + break; + case XML_ROLE_ENTITY_VALUE: + if (dtd->keepProcessing) { +- enum XML_Error result = storeEntityValue( +- parser, enc, s + enc->minBytesPerChar, next - enc->minBytesPerChar); ++ enum XML_Error result ++ = storeEntityValue(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 +@@ -4897,12 +5113,15 @@ doProlog(XML_Parser parser, const ENCODING *enc, const char *s, const char *end, + if (parser->m_externalEntityRefHandler) { + dtd->paramEntityRead = XML_FALSE; + entity->open = XML_TRUE; ++ entityTrackingOnOpen(parser, entity, __LINE__); + if (! parser->m_externalEntityRefHandler( + parser->m_externalEntityRefHandlerArg, 0, entity->base, + entity->systemId, entity->publicId)) { ++ entityTrackingOnClose(parser, entity, __LINE__); + entity->open = XML_FALSE; + return XML_ERROR_EXTERNAL_ENTITY_HANDLING; + } ++ entityTrackingOnClose(parser, entity, __LINE__); + entity->open = XML_FALSE; + handleDefault = XML_FALSE; + if (! dtd->paramEntityRead) { +@@ -5100,6 +5319,13 @@ epilogProcessor(XML_Parser parser, const char *s, const char *end, + for (;;) { + const char *next = NULL; + int tok = XmlPrologTok(parser->m_encoding, s, end, &next); ++#ifdef XML_DTD ++ if (! accountingDiffTolerated(parser, tok, s, next, __LINE__, ++ XML_ACCOUNT_DIRECT)) { ++ accountingOnAbort(parser); ++ return XML_ERROR_AMPLIFICATION_LIMIT_BREACH; ++ } ++#endif + parser->m_eventEndPtr = next; + switch (tok) { + /* report partial linebreak - it might be the last token */ +@@ -5173,6 +5399,9 @@ processInternalEntity(XML_Parser parser, ENTITY *entity, XML_Bool betweenDecl) { + return XML_ERROR_NO_MEMORY; + } + entity->open = XML_TRUE; ++#ifdef XML_DTD ++ entityTrackingOnOpen(parser, entity, __LINE__); ++#endif + entity->processed = 0; + openEntity->next = parser->m_openInternalEntities; + parser->m_openInternalEntities = openEntity; +@@ -5191,17 +5420,22 @@ processInternalEntity(XML_Parser parser, ENTITY *entity, XML_Bool betweenDecl) { + int tok + = XmlPrologTok(parser->m_internalEncoding, textStart, textEnd, &next); + result = doProlog(parser, parser->m_internalEncoding, textStart, textEnd, +- tok, next, &next, XML_FALSE, XML_FALSE); ++ tok, next, &next, XML_FALSE, XML_FALSE, ++ XML_ACCOUNT_ENTITY_EXPANSION); + } else + #endif /* XML_DTD */ + result = doContent(parser, parser->m_tagLevel, parser->m_internalEncoding, +- textStart, textEnd, &next, XML_FALSE); ++ textStart, textEnd, &next, XML_FALSE, ++ XML_ACCOUNT_ENTITY_EXPANSION); + + 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 ++ entityTrackingOnClose(parser, entity, __LINE__); ++#endif /* XML_DTD */ + entity->open = XML_FALSE; + parser->m_openInternalEntities = openEntity->next; + /* put openEntity back in list of free instances */ +@@ -5234,12 +5468,13 @@ internalEntityProcessor(XML_Parser parser, const char *s, const char *end, + int tok + = XmlPrologTok(parser->m_internalEncoding, textStart, textEnd, &next); + result = doProlog(parser, parser->m_internalEncoding, textStart, textEnd, +- tok, next, &next, XML_FALSE, XML_TRUE); ++ tok, next, &next, XML_FALSE, XML_TRUE, ++ XML_ACCOUNT_ENTITY_EXPANSION); + } else + #endif /* XML_DTD */ + result = doContent(parser, openEntity->startTagLevel, + parser->m_internalEncoding, textStart, textEnd, &next, +- XML_FALSE); ++ XML_FALSE, XML_ACCOUNT_ENTITY_EXPANSION); + + if (result != XML_ERROR_NONE) + return result; +@@ -5248,6 +5483,9 @@ internalEntityProcessor(XML_Parser parser, const char *s, const char *end, + entity->processed = (int)(next - (char *)entity->textPtr); + return result; + } else { ++#ifdef XML_DTD ++ entityTrackingOnClose(parser, entity, __LINE__); ++#endif + entity->open = XML_FALSE; + parser->m_openInternalEntities = openEntity->next; + /* put openEntity back in list of free instances */ +@@ -5261,7 +5499,8 @@ internalEntityProcessor(XML_Parser parser, const char *s, const char *end, + 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_Bool)! parser->m_parsingStatus.finalBuffer, XML_TRUE, ++ XML_ACCOUNT_DIRECT); + } else + #endif /* XML_DTD */ + { +@@ -5269,7 +5508,8 @@ internalEntityProcessor(XML_Parser parser, const char *s, const char *end, + /* see externalEntityContentProcessor vs contentProcessor */ + return doContent(parser, parser->m_parentParser ? 1 : 0, parser->m_encoding, + s, end, nextPtr, +- (XML_Bool)! parser->m_parsingStatus.finalBuffer); ++ (XML_Bool)! parser->m_parsingStatus.finalBuffer, ++ XML_ACCOUNT_DIRECT); + } + } + +@@ -5284,9 +5524,10 @@ errorProcessor(XML_Parser parser, const char *s, const char *end, + + static enum XML_Error + storeAttributeValue(XML_Parser parser, const ENCODING *enc, XML_Bool isCdata, +- const char *ptr, const char *end, STRING_POOL *pool) { ++ const char *ptr, const char *end, STRING_POOL *pool, ++ enum XML_Account account) { + enum XML_Error result +- = appendAttributeValue(parser, enc, isCdata, ptr, end, pool); ++ = appendAttributeValue(parser, enc, isCdata, ptr, end, pool, account); + if (result) + return result; + if (! isCdata && poolLength(pool) && poolLastChar(pool) == 0x20) +@@ -5298,11 +5539,22 @@ 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) { ++ const char *ptr, const char *end, STRING_POOL *pool, ++ enum XML_Account account) { + DTD *const dtd = parser->m_dtd; /* save one level of indirection */ ++#ifndef XML_DTD ++ UNUSED_P(account); ++#endif ++ + for (;;) { + const char *next; + int tok = XmlAttributeValueTok(enc, ptr, end, &next); ++#ifdef XML_DTD ++ if (! accountingDiffTolerated(parser, tok, ptr, next, __LINE__, account)) { ++ accountingOnAbort(parser); ++ return XML_ERROR_AMPLIFICATION_LIMIT_BREACH; ++ } ++#endif + switch (tok) { + case XML_TOK_NONE: + return XML_ERROR_NONE; +@@ -5362,6 +5614,14 @@ appendAttributeValue(XML_Parser parser, const ENCODING *enc, XML_Bool isCdata, + XML_Char ch = (XML_Char)XmlPredefinedEntityName( + enc, ptr + enc->minBytesPerChar, next - enc->minBytesPerChar); + if (ch) { ++#ifdef XML_DTD ++ /* 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 */ + if (! poolAppendChar(pool, ch)) + return XML_ERROR_NO_MEMORY; + break; +@@ -5439,9 +5699,16 @@ appendAttributeValue(XML_Parser parser, const ENCODING *enc, XML_Bool isCdata, + enum XML_Error result; + const XML_Char *textEnd = entity->textPtr + entity->textLen; + entity->open = XML_TRUE; ++#ifdef XML_DTDded to activate ( ++ entityTrackingOnOpen(parser, entity, __LINE__);ded to activate ( ++#endif + result = appendAttributeValue(parser, parser->m_internalEncoding, + isCdata, (char *)entity->textPtr, +- (char *)textEnd, pool); ++ (char *)textEnd, pool, ++ XML_ACCOUNT_ENTITY_EXPANSION); ++#ifdef XML_DTD ++ entityTrackingOnClose(parser, entity, __LINE__); ++#endif + entity->open = XML_FALSE; + if (result) + return result; +@@ -5471,13 +5738,16 @@ 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) { ++ const char *entityTextPtr, const char *entityTextEnd, ++ enum XML_Account account) { + DTD *const dtd = parser->m_dtd; /* save one level of indirection */ + STRING_POOL *pool = &(dtd->entityValuePool); + enum XML_Error result = XML_ERROR_NONE; + #ifdef XML_DTD + int oldInEntityValue = parser->m_prologState.inEntityValue; + parser->m_prologState.inEntityValue = 1; ++#else ++ UNUSED_P(account); + #endif /* XML_DTD */ + /* never return Null for the value argument in EntityDeclHandler, + since this would indicate an external entity; therefore we +@@ -5490,6 +5760,16 @@ storeEntityValue(XML_Parser parser, const ENCODING *enc, + for (;;) { + const char *next; + int tok = XmlEntityValueTok(enc, entityTextPtr, entityTextEnd, &next); ++ ++#ifdef XML_DTD ++ if (! accountingDiffTolerated(parser, tok, entityTextPtr, next, __LINE__, ++ account)) { ++ accountingOnAbort(parser); ++ result = XML_ERROR_AMPLIFICATION_LIMIT_BREACH; ++ goto endEntityValue; ++ } ++#endif ++ + switch (tok) { + case XML_TOK_PARAM_ENTITY_REF: + #ifdef XML_DTD +@@ -5525,13 +5805,16 @@ storeEntityValue(XML_Parser parser, const ENCODING *enc, + if (parser->m_externalEntityRefHandler) { + dtd->paramEntityRead = XML_FALSE; + entity->open = XML_TRUE; ++ entityTrackingOnOpen(parser, entity, __LINE__); + if (! parser->m_externalEntityRefHandler( + parser->m_externalEntityRefHandlerArg, 0, entity->base, + entity->systemId, entity->publicId)) { ++ entityTrackingOnClose(parser, entity, __LINE__); + entity->open = XML_FALSE; + result = XML_ERROR_EXTERNAL_ENTITY_HANDLING; + goto endEntityValue; + } ++ entityTrackingOnClose(parser, entity, __LINE__); + entity->open = XML_FALSE; + if (! dtd->paramEntityRead) + dtd->keepProcessing = dtd->standalone; +@@ -5539,9 +5822,12 @@ storeEntityValue(XML_Parser parser, const ENCODING *enc, + dtd->keepProcessing = dtd->standalone; + } else { + entity->open = XML_TRUE; ++ entityTrackingOnOpen(parser, entity, __LINE__); + result = storeEntityValue( + parser, parser->m_internalEncoding, (char *)entity->textPtr, +- (char *)(entity->textPtr + entity->textLen)); ++ (char *)(entity->textPtr + entity->textLen), ++ XML_ACCOUNT_ENTITY_EXPANSION); ++ entityTrackingOnClose(parser, entity, __LINE__); + entity->open = XML_FALSE; + if (result) + goto endEntityValue; +@@ -6902,3 +7188,741 @@ copyString(const XML_Char *s, const XML_Memory_Handling_Suite *memsuite) { + memcpy(result, s, charsRequired * sizeof(XML_Char)); + return result; + } ++ ++#ifdef XML_DTD ++ ++static float ++accountingGetCurrentAmplification(XML_Parser rootParser) { ++ const XmlBigCount countBytesOutput ++ = rootParser->m_accounting.countBytesDirect ++ + rootParser->m_accounting.countBytesIndirect; ++ const float amplificationFactor ++ = rootParser->m_accounting.countBytesDirect ++ ? (countBytesOutput ++ / (float)(rootParser->m_accounting.countBytesDirect)) ++ : 1.0f; ++ assert(! rootParser->m_parentParser); ++ return amplificationFactor; ++} ++ ++static void ++accountingReportStats(XML_Parser originParser, const char *epilog) { ++ const XML_Parser rootParser = getRootParserOf(originParser, NULL); ++ assert(! rootParser->m_parentParser); ++ ++ if (rootParser->m_accounting.debugLevel < 1) { ++ return; ++ } ++ ++ const float amplificationFactor ++ = accountingGetCurrentAmplification(rootParser); ++ fprintf(stderr, ++ "expat: Accounting(%p): Direct " EXPAT_FMT_ULL( ++ "10") ", indirect " EXPAT_FMT_ULL("10") ", amplification %8.2f%s", ++ (void *)rootParser, rootParser->m_accounting.countBytesDirect, ++ rootParser->m_accounting.countBytesIndirect, ++ (double)amplificationFactor, epilog); ++} ++ ++static void ++accountingOnAbort(XML_Parser originParser) { ++ accountingReportStats(originParser, " ABORTING\n"); ++} ++ ++static void ++accountingReportDiff(XML_Parser rootParser, ++ unsigned int levelsAwayFromRootParser, const char *before, ++ const char *after, ptrdiff_t bytesMore, int source_line, ++ enum XML_Account account) { ++ assert(! rootParser->m_parentParser); ++ ++ fprintf(stderr, ++ " (+" EXPAT_FMT_PTRDIFF_T("6") " bytes %s|%d, xmlparse.c:%d) %*s\"", ++ bytesMore, (account == XML_ACCOUNT_DIRECT) ? "DIR" : "EXP", ++ levelsAwayFromRootParser, source_line, 10, ""); ++ ++ const char ellipis[] = "[..]"; ++ const size_t ellipsisLength = sizeof(ellipis) /* because compile-time */ - 1; ++ const unsigned int contextLength = 10; ++ ++ /* Note: Performance is of no concern here */ ++ const char *walker = before; ++ if ((rootParser->m_accounting.debugLevel >= 3) ++ || (after - before) ++ <= (ptrdiff_t)(contextLength + ellipsisLength + contextLength)) { ++ for (; walker < after; walker++) { ++ fprintf(stderr, "%s", unsignedCharToPrintable(walker[0])); ++ } ++ } else { ++ for (; walker < before + contextLength; walker++) { ++ fprintf(stderr, "%s", unsignedCharToPrintable(walker[0])); ++ } ++ fprintf(stderr, ellipis); ++ walker = after - contextLength; ++ for (; walker < after; walker++) { ++ fprintf(stderr, "%s", unsignedCharToPrintable(walker[0])); ++ } ++ } ++ fprintf(stderr, "\"\n"); ++} ++ ++static XML_Bool ++accountingDiffTolerated(XML_Parser originParser, int tok, const char *before, ++ const char *after, int source_line, ++ enum XML_Account account) { ++ /* Note: We need to check the token type *first* to be sure that ++ * we can even access variable , safely. ++ * E.g. for XML_TOK_NONE may hold an invalid pointer. */ ++ switch (tok) { ++ case XML_TOK_INVALID: ++ case XML_TOK_PARTIAL: ++ case XML_TOK_PARTIAL_CHAR: ++ case XML_TOK_NONE: ++ return XML_TRUE; ++ } ++ ++ if (account == XML_ACCOUNT_NONE) ++ return XML_TRUE; /* because these bytes have been accounted for, already */ ++ ++ unsigned int levelsAwayFromRootParser; ++ const XML_Parser rootParser ++ = getRootParserOf(originParser, &levelsAwayFromRootParser); ++ assert(! rootParser->m_parentParser); ++ ++ const int isDirect ++ = (account == XML_ACCOUNT_DIRECT) && (originParser == rootParser); ++ const ptrdiff_t bytesMore = after - before; ++ ++ XmlBigCount *const additionTarget ++ = isDirect ? &rootParser->m_accounting.countBytesDirect ++ : &rootParser->m_accounting.countBytesIndirect; ++ ++ /* Detect and avoid integer overflow */ ++ if (*additionTarget > (XmlBigCount)(-1) - (XmlBigCount)bytesMore) ++ return XML_FALSE; ++ *additionTarget += bytesMore; ++ ++ const XmlBigCount countBytesOutput ++ = rootParser->m_accounting.countBytesDirect ++ + rootParser->m_accounting.countBytesIndirect; ++ const float amplificationFactor ++ = accountingGetCurrentAmplification(rootParser); ++ const XML_Bool tolerated ++ = (countBytesOutput < rootParser->m_accounting.activationThresholdBytes) ++ || (amplificationFactor ++ <= rootParser->m_accounting.maximumAmplificationFactor); ++ ++ if (rootParser->m_accounting.debugLevel >= 2) { ++ accountingReportStats(rootParser, ""); ++ accountingReportDiff(rootParser, levelsAwayFromRootParser, before, after, ++ bytesMore, source_line, account); ++ } ++ ++ return tolerated; ++} ++ ++static void ++entityTrackingReportStats(XML_Parser rootParser, ENTITY *entity, ++ const char *action, int sourceLine) { ++ assert(! rootParser->m_parentParser); ++ if (rootParser->m_entity_stats.debugLevel < 1) ++ return; ++ ++# if defined(XML_UNICODE) ++ const char *const entityName = "[..]"; ++# else ++ const char *const entityName = entity->name; ++# endif ++ ++ fprintf( ++ stderr, ++ "expat: Entities(%p): Count %9d, depth %2d/%2d %*s%s%s; %s length %d (xmlparse.c:%d)\n", ++ (void *)rootParser, rootParser->m_entity_stats.countEverOpened, ++ rootParser->m_entity_stats.currentDepth, ++ rootParser->m_entity_stats.maximumDepthSeen, ++ (rootParser->m_entity_stats.currentDepth - 1) * 2, "", ++ entity->is_param ? "%" : "&", entityName, action, entity->textLen, ++ sourceLine); ++} ++ ++static void ++entityTrackingOnOpen(XML_Parser originParser, ENTITY *entity, int sourceLine) { ++ const XML_Parser rootParser = getRootParserOf(originParser, NULL); ++ assert(! rootParser->m_parentParser); ++ ++ rootParser->m_entity_stats.countEverOpened++; ++ rootParser->m_entity_stats.currentDepth++; ++ if (rootParser->m_entity_stats.currentDepth ++ > rootParser->m_entity_stats.maximumDepthSeen) { ++ rootParser->m_entity_stats.maximumDepthSeen++; ++ } ++ ++ entityTrackingReportStats(rootParser, entity, "OPEN ", sourceLine); ++} ++ ++static void ++entityTrackingOnClose(XML_Parser originParser, ENTITY *entity, int sourceLine) { ++ const XML_Parser rootParser = getRootParserOf(originParser, NULL); ++ assert(! rootParser->m_parentParser); ++ ++ entityTrackingReportStats(rootParser, entity, "CLOSE", sourceLine); ++ rootParser->m_entity_stats.currentDepth--; ++} ++ ++static XML_Parser ++getRootParserOf(XML_Parser parser, unsigned int *outLevelDiff) { ++ XML_Parser rootParser = parser; ++ unsigned int stepsTakenUpwards = 0; ++ while (rootParser->m_parentParser) { ++ rootParser = rootParser->m_parentParser; ++ stepsTakenUpwards++; ++ } ++ assert(! rootParser->m_parentParser); ++ if (outLevelDiff != NULL) { ++ *outLevelDiff = stepsTakenUpwards; ++ } ++ return rootParser; ++} ++ ++static const char * ++unsignedCharToPrintable(unsigned char c) { ++ switch (c) { ++ case 0: ++ return "\\0"; ++ case 1: ++ return "\\x1"; ++ case 2: ++ return "\\x2"; ++ case 3: ++ return "\\x3"; ++ case 4: ++ return "\\x4"; ++ case 5: ++ return "\\x5"; ++ case 6: ++ return "\\x6"; ++ case 7: ++ return "\\x7"; ++ case 8: ++ return "\\x8"; ++ case 9: ++ return "\\t"; ++ case 10: ++ return "\\n"; ++ case 11: ++ return "\\xB"; ++ case 12: ++ return "\\xC"; ++ case 13: ++ return "\\r"; ++ case 14: ++ return "\\xE"; ++ case 15: ++ return "\\xF"; ++ case 16: ++ return "\\x10"; ++ case 17: ++ return "\\x11"; ++ case 18: ++ return "\\x12"; ++ case 19: ++ return "\\x13"; ++ case 20: ++ return "\\x14"; ++ case 21: ++ return "\\x15"; ++ case 22: ++ return "\\x16"; ++ case 23: ++ return "\\x17"; ++ case 24: ++ return "\\x18"; ++ case 25: ++ return "\\x19"; ++ case 26: ++ return "\\x1A"; ++ case 27: ++ return "\\x1B"; ++ case 28: ++ return "\\x1C"; ++ case 29: ++ return "\\x1D"; ++ case 30: ++ return "\\x1E"; ++ case 31: ++ return "\\x1F"; ++ case 32: ++ return " "; ++ case 33: ++ return "!"; ++ case 34: ++ return "\\\""; ++ case 35: ++ return "#"; ++ case 36: ++ return "$"; ++ case 37: ++ return "%"; ++ case 38: ++ return "&"; ++ case 39: ++ return "'"; ++ case 40: ++ return "("; ++ case 41: ++ return ")"; ++ case 42: ++ return "*"; ++ case 43: ++ return "+"; ++ case 44: ++ return ","; ++ case 45: ++ return "-"; ++ case 46: ++ return "."; ++ case 47: ++ return "/"; ++ case 48: ++ return "0"; ++ case 49: ++ return "1"; ++ case 50: ++ return "2"; ++ case 51: ++ return "3"; ++ case 52: ++ return "4"; ++ case 53: ++ return "5"; ++ case 54: ++ return "6"; ++ case 55: ++ return "7"; ++ case 56: ++ return "8"; ++ case 57: ++ return "9"; ++ case 58: ++ return ":"; ++ case 59: ++ return ";"; ++ case 60: ++ return "<"; ++ case 61: ++ return "="; ++ case 62: ++ return ">"; ++ case 63: ++ return "?"; ++ case 64: ++ return "@"; ++ case 65: ++ return "A"; ++ case 66: ++ return "B"; ++ case 67: ++ return "C"; ++ case 68: ++ return "D"; ++ case 69: ++ return "E"; ++ case 70: ++ return "F"; ++ case 71: ++ return "G"; ++ case 72: ++ return "H"; ++ case 73: ++ return "I"; ++ case 74: ++ return "J"; ++ case 75: ++ return "K"; ++ case 76: ++ return "L"; ++ case 77: ++ return "M"; ++ case 78: ++ return "N"; ++ case 79: ++ return "O"; ++ case 80: ++ return "P"; ++ case 81: ++ return "Q"; ++ case 82: ++ return "R"; ++ case 83: ++ return "S"; ++ case 84: ++ return "T"; ++ case 85: ++ return "U"; ++ case 86: ++ return "V"; ++ case 87: ++ return "W"; ++ case 88: ++ return "X"; ++ case 89: ++ return "Y"; ++ case 90: ++ return "Z"; ++ case 91: ++ return "["; ++ case 92: ++ return "\\\\"; ++ case 93: ++ return "]"; ++ case 94: ++ return "^"; ++ case 95: ++ return "_"; ++ case 96: ++ return "`"; ++ case 97: ++ return "a"; ++ case 98: ++ return "b"; ++ case 99: ++ return "c"; ++ case 100: ++ return "d"; ++ case 101: ++ return "e"; ++ case 102: ++ return "f"; ++ case 103: ++ return "g"; ++ case 104: ++ return "h"; ++ case 105: ++ return "i"; ++ case 106: ++ return "j"; ++ case 107: ++ return "k"; ++ case 108: ++ return "l"; ++ case 109: ++ return "m"; ++ case 110: ++ return "n"; ++ case 111: ++ return "o"; ++ case 112: ++ return "p"; ++ case 113: ++ return "q"; ++ case 114: ++ return "r"; ++ case 115: ++ return "s"; ++ case 116: ++ return "t"; ++ case 117: ++ return "u"; ++ case 118: ++ return "v"; ++ case 119: ++ return "w"; ++ case 120: ++ return "x"; ++ case 121: ++ return "y"; ++ case 122: ++ return "z"; ++ case 123: ++ return "{"; ++ case 124: ++ return "|"; ++ case 125: ++ return "}"; ++ case 126: ++ return "~"; ++ case 127: ++ return "\\x7F"; ++ case 128: ++ return "\\x80"; ++ case 129: ++ return "\\x81"; ++ case 130: ++ return "\\x82"; ++ case 131: ++ return "\\x83"; ++ case 132: ++ return "\\x84"; ++ case 133: ++ return "\\x85"; ++ case 134: ++ return "\\x86"; ++ case 135: ++ return "\\x87"; ++ case 136: ++ return "\\x88"; ++ case 137: ++ return "\\x89"; ++ case 138: ++ return "\\x8A"; ++ case 139: ++ return "\\x8B"; ++ case 140: ++ return "\\x8C"; ++ case 141: ++ return "\\x8D"; ++ case 142: ++ return "\\x8E"; ++ case 143: ++ return "\\x8F"; ++ case 144: ++ return "\\x90"; ++ case 145: ++ return "\\x91"; ++ case 146: ++ return "\\x92"; ++ case 147: ++ return "\\x93"; ++ case 148: ++ return "\\x94"; ++ case 149: ++ return "\\x95"; ++ case 150: ++ return "\\x96"; ++ case 151: ++ return "\\x97"; ++ case 152: ++ return "\\x98"; ++ case 153: ++ return "\\x99"; ++ case 154: ++ return "\\x9A"; ++ case 155: ++ return "\\x9B"; ++ case 156: ++ return "\\x9C"; ++ case 157: ++ return "\\x9D"; ++ case 158: ++ return "\\x9E"; ++ case 159: ++ return "\\x9F"; ++ case 160: ++ return "\\xA0"; ++ case 161: ++ return "\\xA1"; ++ case 162: ++ return "\\xA2"; ++ case 163: ++ return "\\xA3"; ++ case 164: ++ return "\\xA4"; ++ case 165: ++ return "\\xA5"; ++ case 166: ++ return "\\xA6"; ++ case 167: ++ return "\\xA7"; ++ case 168: ++ return "\\xA8"; ++ case 169: ++ return "\\xA9"; ++ case 170: ++ return "\\xAA"; ++ case 171: ++ return "\\xAB"; ++ case 172: ++ return "\\xAC"; ++ case 173: ++ return "\\xAD"; ++ case 174: ++ return "\\xAE"; ++ case 175: ++ return "\\xAF"; ++ case 176: ++ return "\\xB0"; ++ case 177: ++ return "\\xB1"; ++ case 178: ++ return "\\xB2"; ++ case 179: ++ return "\\xB3"; ++ case 180: ++ return "\\xB4"; ++ case 181: ++ return "\\xB5"; ++ case 182: ++ return "\\xB6"; ++ case 183: ++ return "\\xB7"; ++ case 184: ++ return "\\xB8"; ++ case 185: ++ return "\\xB9"; ++ case 186: ++ return "\\xBA"; ++ case 187: ++ return "\\xBB"; ++ case 188: ++ return "\\xBC"; ++ case 189: ++ return "\\xBD"; ++ case 190: ++ return "\\xBE"; ++ case 191: ++ return "\\xBF"; ++ case 192: ++ return "\\xC0"; ++ case 193: ++ return "\\xC1"; ++ case 194: ++ return "\\xC2"; ++ case 195: ++ return "\\xC3"; ++ case 196: ++ return "\\xC4"; ++ case 197: ++ return "\\xC5"; ++ case 198: ++ return "\\xC6"; ++ case 199: ++ return "\\xC7"; ++ case 200: ++ return "\\xC8"; ++ case 201: ++ return "\\xC9"; ++ case 202: ++ return "\\xCA"; ++ case 203: ++ return "\\xCB"; ++ case 204: ++ return "\\xCC"; ++ case 205: ++ return "\\xCD"; ++ case 206: ++ return "\\xCE"; ++ case 207: ++ return "\\xCF"; ++ case 208: ++ return "\\xD0"; ++ case 209: ++ return "\\xD1"; ++ case 210: ++ return "\\xD2"; ++ case 211: ++ return "\\xD3"; ++ case 212: ++ return "\\xD4"; ++ case 213: ++ return "\\xD5"; ++ case 214: ++ return "\\xD6"; ++ case 215: ++ return "\\xD7"; ++ case 216: ++ return "\\xD8"; ++ case 217: ++ return "\\xD9"; ++ case 218: ++ return "\\xDA"; ++ case 219: ++ return "\\xDB"; ++ case 220: ++ return "\\xDC"; ++ case 221: ++ return "\\xDD"; ++ case 222: ++ return "\\xDE"; ++ case 223: ++ return "\\xDF"; ++ case 224: ++ return "\\xE0"; ++ case 225: ++ return "\\xE1"; ++ case 226: ++ return "\\xE2"; ++ case 227: ++ return "\\xE3"; ++ case 228: ++ return "\\xE4"; ++ case 229: ++ return "\\xE5"; ++ case 230: ++ return "\\xE6"; ++ case 231: ++ return "\\xE7"; ++ case 232: ++ return "\\xE8"; ++ case 233: ++ return "\\xE9"; ++ case 234: ++ return "\\xEA"; ++ case 235: ++ return "\\xEB"; ++ case 236: ++ return "\\xEC"; ++ case 237: ++ return "\\xED"; ++ case 238: ++ return "\\xEE"; ++ case 239: ++ return "\\xEF"; ++ case 240: ++ return "\\xF0"; ++ case 241: ++ return "\\xF1"; ++ case 242: ++ return "\\xF2"; ++ case 243: ++ return "\\xF3"; ++ case 244: ++ return "\\xF4"; ++ case 245: ++ return "\\xF5"; ++ case 246: ++ return "\\xF6"; ++ case 247: ++ return "\\xF7"; ++ case 248: ++ return "\\xF8"; ++ case 249: ++ return "\\xF9"; ++ case 250: ++ return "\\xFA"; ++ case 251: ++ return "\\xFB"; ++ case 252: ++ return "\\xFC"; ++ case 253: ++ return "\\xFD"; ++ case 254: ++ return "\\xFE"; ++ case 255: ++ return "\\xFF"; ++ default: ++ assert(0); /* never gets here */ ++ return "dead code"; ++ } ++ assert(0); /* never gets here */ ++} ++ ++#endif /* XML_DTD */ ++ ++static unsigned long ++getDebugLevel(const char *variableName, unsigned long defaultDebugLevel) { ++ const char *const valueOrNull = getenv(variableName); ++ if (valueOrNull == NULL) { ++ return defaultDebugLevel; ++ } ++ const char *const value = valueOrNull; ++ ++ errno = 0; ++ char *afterValue = (char *)value; ++ unsigned long debugLevel = strtoul(value, &afterValue, 10); ++ if ((errno != 0) || (afterValue[0] != '\0')) { ++ errno = 0; ++ return defaultDebugLevel; ++ } ++ ++ return debugLevel; ++} +-- +1.8.3.1 + diff --git a/backport-CVE-2013-0340-tests-Cover-accounting-of-CDATA-sections.patch b/backport-CVE-2013-0340-tests-Cover-accounting-of-CDATA-sections.patch new file mode 100644 index 0000000000000000000000000000000000000000..8baaa521c5ad2dd47d66496c14e9e2c79b9f5044 --- /dev/null +++ b/backport-CVE-2013-0340-tests-Cover-accounting-of-CDATA-sections.patch @@ -0,0 +1,34 @@ +From 77cfb8f4cd9679cef27ae9bc38e39ac51235af2d Mon Sep 17 00:00:00 2001 +From: Sebastian Pipping +Date: Fri, 14 May 2021 20:26:26 +0200 +Subject: [PATCH] tests: Cover accounting of CDATA sections inside of general + entities + +--- + tests/runtests.c | 10 ++++++++++ + 1 file changed, 10 insertions(+) + +diff --git a/tests/runtests.c b/tests/runtests.c +index 0e2b49f..e394456 100644 +--- a/tests/runtests.c ++++ b/tests/runtests.c +@@ -11318,6 +11318,16 @@ START_TEST(test_accounting_precision) { + + /* CDATA */ + {"", NULL, NULL, 0, filled_later}, ++ /* The following is the essence of this OSS-Fuzz finding: ++ https://bugs.chromium.org/p/oss-fuzz/issues/detail?id=34302 ++ https://oss-fuzz.com/testcase-detail/4860575394955264 ++ */ ++ {"333\">\n" ++ "]>\n" ++ "&e;\n", ++ NULL, NULL, sizeof(XML_Char) * strlen("111333"), ++ filled_later}, + + /* Conditional sections */ + {" +Date: Wed, 21 Apr 2021 00:11:04 +0200 +Subject: [PATCH] tests: Cover accounting + +--- + lib/internal.h | 6 +- + tests/runtests.c | 300 ++++++++++++++++++++++++++++++++++++++++++++++++- + 2 files changed, 304 insertions(+), 2 deletions(-) + +diff --git a/lib/internal.h b/lib/internal.h +index ce6d27a..377c12b 100644 +--- a/lib/internal.h ++++ b/lib/internal.h +@@ -109,10 +109,12 @@ + + #if defined(_WIN32) && ! defined(__USE_MINGW_ANSI_STDIO) + # define EXPAT_FMT_ULL(midpart) "%" midpart "I64u" +-# if defined(_WIN64) // Note: modifier "td" does not work for MinGW ++# if defined(_WIN64) // Note: modifiers "td" and "zu" do not work for MinGW + # define EXPAT_FMT_PTRDIFF_T(midpart) "%" midpart "I64d" ++# define EXPAT_FMT_SIZE_T(midpart) "%" midpart "I64u" + # else + # define EXPAT_FMT_PTRDIFF_T(midpart) "%" midpart "d" ++# define EXPAT_FMT_SIZE_T(midpart) "%" midpart "u" + # endif + #else + # define EXPAT_FMT_ULL(midpart) "%" midpart "llu" +@@ -120,8 +122,10 @@ + # error Compiler did not define ULONG_MAX for us + # elif ULONG_MAX == 18446744073709551615u // 2^64-1 + # define EXPAT_FMT_PTRDIFF_T(midpart) "%" midpart "ld" ++# define EXPAT_FMT_SIZE_T(midpart) "%" midpart "lu" + # else + # define EXPAT_FMT_PTRDIFF_T(midpart) "%" midpart "d" ++# define EXPAT_FMT_SIZE_T(midpart) "%" midpart "u" + # endif + #endif + +diff --git a/tests/runtests.c b/tests/runtests.c +index 40fdfb4..5234f49 100644 +--- a/tests/runtests.c ++++ b/tests/runtests.c +@@ -61,7 +61,7 @@ + #include "expat.h" + #include "chardata.h" + #include "structdata.h" +-#include "internal.h" /* for UNUSED_P only */ ++#include "internal.h" + #include "minicheck.h" + #include "memcheck.h" + #include "siphash.h" +@@ -11225,6 +11225,296 @@ START_TEST(test_nsalloc_prefixed_element) { + } + END_TEST + ++#if defined(XML_DTD) ++typedef enum XML_Status (*XmlParseFunction)(XML_Parser, const char *, int, int); ++ ++struct AccountingTestCase { ++ const char *primaryText; ++ const char *firstExternalText; /* often NULL */ ++ const char *secondExternalText; /* often NULL */ ++ const unsigned long long expectedCountBytesIndirectExtra; ++ XML_Bool singleBytesWanted; ++}; ++ ++static int ++accounting_external_entity_ref_handler(XML_Parser parser, ++ const XML_Char *context, ++ const XML_Char *base, ++ const XML_Char *systemId, ++ const XML_Char *publicId) { ++ UNUSED_P(context); ++ UNUSED_P(base); ++ UNUSED_P(publicId); ++ ++ const struct AccountingTestCase *const testCase ++ = (const struct AccountingTestCase *)XML_GetUserData(parser); ++ ++ const char *externalText = NULL; ++ if (xcstrcmp(systemId, XCS("first.ent")) == 0) { ++ externalText = testCase->firstExternalText; ++ } else if (xcstrcmp(systemId, XCS("second.ent")) == 0) { ++ externalText = testCase->secondExternalText; ++ } else { ++ assert(! "systemId is neither \"first.ent\" nor \"second.ent\""); ++ } ++ assert(externalText); ++ ++ XML_Parser entParser = XML_ExternalEntityParserCreate(parser, context, 0); ++ assert(entParser); ++ ++ const XmlParseFunction xmlParseFunction ++ = testCase->singleBytesWanted ? _XML_Parse_SINGLE_BYTES : XML_Parse; ++ ++ const enum XML_Status status = xmlParseFunction( ++ entParser, externalText, (int)strlen(externalText), XML_TRUE); ++ ++ XML_ParserFree(entParser); ++ return status; ++} ++ ++START_TEST(test_accounting_precision) { ++ const XML_Bool filled_later = XML_TRUE; /* value is arbitrary */ ++ struct AccountingTestCase cases[] = { ++ {"", NULL, NULL, 0, 0}, ++ {"", NULL, NULL, 0, 0}, ++ ++ /* Attributes */ ++ {"", NULL, NULL, 0, filled_later}, ++ {"", NULL, NULL, 0, 0}, ++ {"", NULL, NULL, 0, ++ filled_later}, ++ {"", NULL, NULL, ++ sizeof(XML_Char) * 5 /* number of predefined entites */, filled_later}, ++ {"\n" ++ " \n" ++ "", ++ NULL, NULL, 0, filled_later}, ++ ++ /* Text */ ++ {"text", NULL, NULL, 0, filled_later}, ++ {"text1text2", NULL, NULL, 0, filled_later}, ++ {"&'><"", NULL, NULL, ++ sizeof(XML_Char) * 5 /* number of predefined entites */, filled_later}, ++ {"A)", NULL, NULL, 0, filled_later}, ++ ++ /* Prolog */ ++ {"", NULL, NULL, 0, filled_later}, ++ ++ /* Whitespace */ ++ {" ", NULL, NULL, 0, filled_later}, ++ {"", NULL, NULL, 0, filled_later}, ++ {"", NULL, NULL, 0, filled_later}, ++ ++ /* Comments */ ++ {"", NULL, NULL, 0, filled_later}, ++ ++ /* Processing instructions */ ++ {"", ++ NULL, NULL, 0, filled_later}, ++ {"", ++ "%e1;", "", ++ 0, filled_later}, ++ ++ /* CDATA */ ++ {"", NULL, NULL, 0, filled_later}, ++ ++ /* Conditional sections */ ++ {"\n" ++ "\n" ++ "\n" ++ "%import;\n" ++ "]>\n" ++ "\n", ++ "]]>\n" ++ "]]>", ++ NULL, sizeof(XML_Char) * (strlen("INCLUDE") + strlen("IGNORE")), ++ filled_later}, ++ ++ /* General entities */ ++ {"\n" ++ "]>\n" ++ "&nine;", ++ NULL, NULL, sizeof(XML_Char) * strlen("123456789"), filled_later}, ++ {"\n" ++ "]>\n" ++ "", ++ NULL, NULL, sizeof(XML_Char) * strlen("123456789"), filled_later}, ++ {"\n" ++ "\n" ++ "]>\n" ++ "&nine2;&nine2;&nine2;", ++ NULL, NULL, ++ sizeof(XML_Char) * 3 /* calls to &nine2; */ * 2 /* calls to &nine; */ ++ * (strlen("&nine;") + strlen("123456789")), ++ filled_later}, ++ {"\n" ++ "]>\n" ++ "&five;", ++ "12345", NULL, 0, filled_later}, ++ ++ /* Parameter entities */ ++ {"\">\n" ++ "%comment;\n" ++ "]>\n" ++ "", ++ NULL, NULL, sizeof(XML_Char) * strlen(""), filled_later}, ++ {"\n" ++ "%ninedef;\n" ++ "]>\n" ++ "&nine;", ++ NULL, NULL, ++ sizeof(XML_Char) ++ * (strlen("") + strlen("123456789")), ++ filled_later}, ++ {"\">\n" ++ "%comment;\">\n" ++ "%comment2;\n" ++ "]>\n" ++ "\n", ++ NULL, NULL, ++ sizeof(XML_Char) ++ * (strlen("%comment;%comment;") + 2 * strlen("")), ++ filled_later}, ++ {"\n" ++ " \n" ++ " %five2def;\n" ++ "]>\n" ++ "&five2;", ++ NULL, NULL, /* from "%five2def;": */ ++ sizeof(XML_Char) ++ * (strlen("") ++ + 2 /* calls to "%five;" */ * strlen("12345") ++ + /* from "&five2;": */ strlen("[12345][12345]]]]")), ++ filled_later}, ++ {"\n" ++ "", ++ "'>\n" ++ "%comment;%comment;'>\n" ++ "%comment2;", ++ NULL, ++ sizeof(XML_Char) ++ * (strlen("%comment;%comment;") ++ + 2 /* calls to "%comment;" */ * strlen("")), ++ filled_later}, ++ {"\n" ++ "", ++ "\n" ++ "%e1;'>\n" ++ "%e2;\n", ++ "", sizeof(XML_Char) * strlen(""), ++ filled_later}, ++ { ++ "\n" ++ "", ++ "\n" ++ "", ++ "\n" ++ "hello\n" ++ "xml" /* without trailing newline! */, ++ 0, ++ filled_later, ++ }, ++ { ++ "\n" ++ "", ++ "\n" ++ "", ++ "\n" ++ "hello\n" ++ "xml\n" /* with trailing newline! */, ++ 0, ++ filled_later, ++ }, ++ {"\n" ++ "\n", ++ "\n" ++ "\n" ++ "\n" ++ "%e1;\n", ++ "\xEF\xBB\xBF" /* UTF-8 BOM */, ++ strlen("\xEF\xBB\xBF"), filled_later}, ++ {"\n" ++ "]>\n" ++ "&five;", ++ "\xEF\xBB\xBF" /* UTF-8 BOM */, NULL, 0, filled_later}, ++ }; ++ ++ const size_t countCases = sizeof(cases) / sizeof(cases[0]); ++ size_t u = 0; ++ for (; u < countCases; u++) { ++ size_t v = 0; ++ for (; v < 2; v++) { ++ const XML_Bool singleBytesWanted = (v == 0) ? XML_FALSE : XML_TRUE; ++ const unsigned long long expectedCountBytesDirect ++ = strlen(cases[u].primaryText); ++ const unsigned long long expectedCountBytesIndirect ++ = (cases[u].firstExternalText ? strlen(cases[u].firstExternalText) ++ : 0) ++ + (cases[u].secondExternalText ? strlen(cases[u].secondExternalText) ++ : 0) ++ + cases[u].expectedCountBytesIndirectExtra; ++ ++ XML_Parser parser = XML_ParserCreate(NULL); ++ XML_SetParamEntityParsing(parser, XML_PARAM_ENTITY_PARSING_ALWAYS); ++ if (cases[u].firstExternalText) { ++ XML_SetExternalEntityRefHandler(parser, ++ accounting_external_entity_ref_handler); ++ XML_SetUserData(parser, (void *)&cases[u]); ++ cases[u].singleBytesWanted = singleBytesWanted; ++ } ++ ++ const XmlParseFunction xmlParseFunction ++ = singleBytesWanted ? _XML_Parse_SINGLE_BYTES : XML_Parse; ++ ++ enum XML_Status status ++ = xmlParseFunction(parser, cases[u].primaryText, ++ (int)strlen(cases[u].primaryText), XML_TRUE); ++ if (status != XML_STATUS_OK) { ++ _xml_failure(parser, __FILE__, __LINE__); ++ } ++ ++ const unsigned long long actualCountBytesDirect ++ = testingAccountingGetCountBytesDirect(parser); ++ const unsigned long long actualCountBytesIndirect ++ = testingAccountingGetCountBytesIndirect(parser); ++ ++ XML_ParserFree(parser); ++ ++ if (actualCountBytesDirect != expectedCountBytesDirect) { ++ fprintf( ++ stderr, ++ "Document " EXPAT_FMT_SIZE_T("") " of " EXPAT_FMT_SIZE_T("") ", %s: Expected " EXPAT_FMT_ULL( ++ "") " count direct bytes, got " EXPAT_FMT_ULL("") " instead.\n", ++ u + 1, countCases, singleBytesWanted ? "single bytes" : "chunks", ++ expectedCountBytesDirect, actualCountBytesDirect); ++ fail("Count of direct bytes is off"); ++ } ++ ++ if (actualCountBytesIndirect != expectedCountBytesIndirect) { ++ fprintf( ++ stderr, ++ "Document " EXPAT_FMT_SIZE_T("") " of " EXPAT_FMT_SIZE_T("") ", %s: Expected " EXPAT_FMT_ULL( ++ "") " count indirect bytes, got " EXPAT_FMT_ULL("") " instead.\n", ++ u + 1, countCases, singleBytesWanted ? "single bytes" : "chunks", ++ expectedCountBytesIndirect, actualCountBytesIndirect); ++ fail("Count of indirect bytes is off"); ++ } ++ } ++ } ++} ++END_TEST ++#endif // defined(XML_DTD) ++ + static Suite * + make_suite(void) { + Suite *s = suite_create("basic"); +@@ -11233,6 +11523,9 @@ make_suite(void) { + TCase *tc_misc = tcase_create("miscellaneous tests"); + TCase *tc_alloc = tcase_create("allocation tests"); + TCase *tc_nsalloc = tcase_create("namespace allocation tests"); ++#if defined(XML_DTD) ++ TCase *tc_accounting = tcase_create("accounting tests"); ++#endif + + suite_add_tcase(s, tc_basic); + tcase_add_checked_fixture(tc_basic, basic_setup, basic_teardown); +@@ -11593,6 +11886,11 @@ make_suite(void) { + tcase_add_test(tc_nsalloc, test_nsalloc_long_systemid_in_ext); + tcase_add_test(tc_nsalloc, test_nsalloc_prefixed_element); + ++#if defined(XML_DTD) ++ suite_add_tcase(s, tc_accounting); ++ tcase_add_test(tc_accounting, test_accounting_precision); ++#endif ++ + return s; + } + +-- +1.8.3.1 + diff --git a/backport-CVE-2013-0340-tests-Cover-billion-laughs-attack-protection-API.patch b/backport-CVE-2013-0340-tests-Cover-billion-laughs-attack-protection-API.patch new file mode 100644 index 0000000000000000000000000000000000000000..9d9674f3327ed2f55486825b662e9306cf515bec --- /dev/null +++ b/backport-CVE-2013-0340-tests-Cover-billion-laughs-attack-protection-API.patch @@ -0,0 +1,103 @@ +From e9d8f115580c3a25a9579c213f096af623dd92ce Mon Sep 17 00:00:00 2001 +From: Sebastian Pipping +Date: Mon, 26 Apr 2021 14:52:45 +0200 +Subject: [PATCH] tests: Cover billion laughs attack protection API + +--- + tests/runtests.c | 66 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + 1 file changed, 66 insertions(+) + +diff --git a/tests/runtests.c b/tests/runtests.c +index 100574a..d4e05a7 100644 +--- a/tests/runtests.c ++++ b/tests/runtests.c +@@ -45,6 +45,7 @@ + #include /* ptrdiff_t */ + #include + #include ++#include /* NAN, INFINITY, isnan */ + + #if defined(_WIN32) && defined(_MSC_VER) && (_MSC_VER < 1600) + /* For vs2003/7.1 up to vs2008/9.0; _MSC_VER 1600 is vs2010/10.0 */ +@@ -11485,6 +11486,70 @@ START_TEST(test_accounting_precision) { + } + } + END_TEST ++ ++START_TEST(test_billion_laughs_attack_protection_api) { ++ XML_Parser parserWithoutParent = XML_ParserCreate(NULL); ++ XML_Parser parserWithParent ++ = XML_ExternalEntityParserCreate(parserWithoutParent, NULL, NULL); ++ if (parserWithoutParent == NULL) ++ fail("parserWithoutParent is NULL"); ++ if (parserWithParent == NULL) ++ fail("parserWithParent is NULL"); ++ ++ // XML_SetBillionLaughsAttackProtectionMaximumAmplification, error cases ++ if (XML_SetBillionLaughsAttackProtectionMaximumAmplification(NULL, 123.0f) ++ == XML_TRUE) ++ fail("Call with NULL parser is NOT supposed to succeed"); ++ if (XML_SetBillionLaughsAttackProtectionMaximumAmplification(parserWithParent, ++ 123.0f) ++ == XML_TRUE) ++ fail("Call with non-root parser is NOT supposed to succeed"); ++ if (XML_SetBillionLaughsAttackProtectionMaximumAmplification( ++ parserWithoutParent, NAN) ++ == XML_TRUE) ++ fail("Call with NaN limit is NOT supposed to succeed"); ++ if (XML_SetBillionLaughsAttackProtectionMaximumAmplification( ++ parserWithoutParent, -1.0f) ++ == XML_TRUE) ++ fail("Call with negative limit is NOT supposed to succeed"); ++ if (XML_SetBillionLaughsAttackProtectionMaximumAmplification( ++ parserWithoutParent, 0.9f) ++ == XML_TRUE) ++ fail("Call with positive limit <1.0 is NOT supposed to succeed"); ++ ++ // XML_SetBillionLaughsAttackProtectionMaximumAmplification, success cases ++ if (XML_SetBillionLaughsAttackProtectionMaximumAmplification( ++ parserWithoutParent, 1.0f) ++ == XML_FALSE) ++ fail("Call with positive limit >=1.0 is supposed to succeed"); ++ if (XML_SetBillionLaughsAttackProtectionMaximumAmplification( ++ parserWithoutParent, 123456.789f) ++ == XML_FALSE) ++ fail("Call with positive limit >=1.0 is supposed to succeed"); ++ if (XML_SetBillionLaughsAttackProtectionMaximumAmplification( ++ parserWithoutParent, INFINITY) ++ == XML_FALSE) ++ fail("Call with positive limit >=1.0 is supposed to succeed"); ++ ++ // XML_SetBillionLaughsAttackProtectionActivationThreshold, error cases ++ if (XML_SetBillionLaughsAttackProtectionActivationThreshold(NULL, 123) ++ == XML_TRUE) ++ fail("Call with NULL parser is NOT supposed to succeed"); ++ if (XML_SetBillionLaughsAttackProtectionActivationThreshold(parserWithParent, ++ 123) ++ == XML_TRUE) ++ fail("Call with non-root parser is NOT supposed to succeed"); ++ ++ // XML_SetBillionLaughsAttackProtectionActivationThreshold, success cases ++ if (XML_SetBillionLaughsAttackProtectionActivationThreshold( ++ parserWithoutParent, 123) ++ == XML_FALSE) ++ fail("Call with non-NULL parentless parser is supposed to succeed"); ++ ++ XML_ParserFree(parserWithParent); ++ XML_ParserFree(parserWithoutParent); ++} ++END_TEST + #endif // defined(XML_DTD) + + static Suite * +@@ -11859,6 +11924,7 @@ make_suite(void) { + #if defined(XML_DTD) + suite_add_tcase(s, tc_accounting); + tcase_add_test(tc_accounting, test_accounting_precision); ++ tcase_add_test(tc_accounting, test_billion_laughs_attack_protection_api); + #endif + + return s; +-- +1.8.3.1 + diff --git a/backport-CVE-2013-0340-tests-Cover-helper-unsignedCharToPrintable.patch b/backport-CVE-2013-0340-tests-Cover-helper-unsignedCharToPrintable.patch new file mode 100644 index 0000000000000000000000000000000000000000..44cce0b705354325762eab2e0bbbe41ce192df6c --- /dev/null +++ b/backport-CVE-2013-0340-tests-Cover-helper-unsignedCharToPrintable.patch @@ -0,0 +1,85 @@ +From 5dbc857f47ecd6c37748ce279c6535fbbf526590 Mon Sep 17 00:00:00 2001 +From: Sebastian Pipping +Date: Mon, 26 Apr 2021 15:12:53 +0200 +Subject: [PATCH] tests: Cover helper unsignedCharToPrintable + +--- + lib/internal.h | 1 + + lib/xmlparse.c | 3 +-- + tests/runtests.c | 20 ++++++++++++++++++++ + 3 files changed, 22 insertions(+), 2 deletions(-) + +diff --git a/lib/internal.h b/lib/internal.h +index 377c12b..444eba0 100644 +--- a/lib/internal.h ++++ b/lib/internal.h +@@ -155,6 +155,7 @@ void _INTERNAL_trim_to_complete_utf8_characters(const char *from, + #if defined(XML_DTD) + unsigned long long testingAccountingGetCountBytesDirect(XML_Parser parser); + unsigned long long testingAccountingGetCountBytesIndirect(XML_Parser parser); ++const char *unsignedCharToPrintable(unsigned char c); + #endif + + #ifdef __cplusplus +diff --git a/lib/xmlparse.c b/lib/xmlparse.c +index adaab23..a1aadd8 100644 +--- a/lib/xmlparse.c ++++ b/lib/xmlparse.c +@@ -580,7 +580,6 @@ static void entityTrackingOnClose(XML_Parser parser, ENTITY *entity, + + static XML_Parser getRootParserOf(XML_Parser parser, + unsigned int *outLevelDiff); +-static const char *unsignedCharToPrintable(unsigned char c); + #endif /* XML_DTD */ + + static unsigned long getDebugLevel(const char *variableName, +@@ -7433,7 +7432,7 @@ getRootParserOf(XML_Parser parser, unsigned int *outLevelDiff) { + return rootParser; + } + +-static const char * ++const char * + unsignedCharToPrintable(unsigned char c) { + switch (c) { + case 0: +diff --git a/tests/runtests.c b/tests/runtests.c +index 8c5ad72..0e2b49f 100644 +--- a/tests/runtests.c ++++ b/tests/runtests.c +@@ -11578,6 +11578,25 @@ START_TEST(test_billion_laughs_attack_protection_api) { + XML_ParserFree(parserWithoutParent); + } + END_TEST ++ ++START_TEST(test_helper_unsigned_char_to_printable) { ++ // Smoke test ++ unsigned char uc = 0; ++ for (; uc < (unsigned char)-1; uc++) { ++ const char *const printable = unsignedCharToPrintable(uc); ++ if (printable == NULL) ++ fail("unsignedCharToPrintable returned NULL"); ++ if (strlen(printable) < (size_t)1) ++ fail("unsignedCharToPrintable returned empty string"); ++ } ++ ++ // Two concrete samples ++ if (strcmp(unsignedCharToPrintable('A'), "A") != 0) ++ fail("unsignedCharToPrintable result mistaken"); ++ if (strcmp(unsignedCharToPrintable('\\'), "\\\\") != 0) ++ fail("unsignedCharToPrintable result mistaken"); ++} ++END_TEST + #endif // defined(XML_DTD) + + static Suite * +@@ -11955,6 +11974,7 @@ make_suite(void) { + suite_add_tcase(s, tc_accounting); + tcase_add_test(tc_accounting, test_accounting_precision); + tcase_add_test(tc_accounting, test_billion_laughs_attack_protection_api); ++ tcase_add_test(tc_accounting, test_helper_unsigned_char_to_printable); + #endif + + return s; +-- +1.8.3.1 + diff --git a/backport-CVE-2013-0340-xmlwf-Add-support-for-custom-attack-protection-param.patch b/backport-CVE-2013-0340-xmlwf-Add-support-for-custom-attack-protection-param.patch new file mode 100644 index 0000000000000000000000000000000000000000..1a814293ad429619be6bf55fb4cc94f4a1613983 --- /dev/null +++ b/backport-CVE-2013-0340-xmlwf-Add-support-for-custom-attack-protection-param.patch @@ -0,0 +1,161 @@ +From c6223b3b0f3d8e6a37b5775b44eeded02e9c3ea7 Mon Sep 17 00:00:00 2001 +From: Sebastian Pipping +Date: Sat, 17 Apr 2021 17:26:17 +0200 +Subject: [PATCH] xmlwf: Add support for custom attack protection parameters + +--- + xmlwf/xmltchar.h | 5 +++- + xmlwf/xmlwf.c | 69 ++++++++++++++++++++++++++++++++++++++++++++ + xmlwf/xmlwf_helpgen.py | 8 +++++ + 3 files changed, 82 insertions(+), 1 deletion(-) + +diff --git a/xmlwf/xmltchar.h b/xmlwf/xmltchar.h +index 4843fbe..30283d0 100644 +--- a/xmlwf/xmltchar.h ++++ b/xmlwf/xmltchar.h +@@ -55,6 +55,8 @@ + # define tmain wmain + # define tremove _wremove + # define tchar wchar_t ++# define tcstof wcstof ++# define tcstoull wcstoull + #else /* not XML_UNICODE */ + # define T(x) x + # define ftprintf fprintf +@@ -72,4 +74,6 @@ + # define tmain main + # define tremove remove + # define tchar char ++# define tcstof strtof ++# define tcstoull strtoull + #endif /* not XML_UNICODE */ +diff --git a/xmlwf/xmlwf.c b/xmlwf/xmlwf.c +index 2b86966..342d6c5 100644 +--- a/xmlwf/xmlwf.c ++++ b/xmlwf/xmlwf.c +@@ -46,6 +46,8 @@ + #include + #include + #include ++#include /* for isnan */ ++#include + + #include "expat.h" + #include "codepage.h" +@@ -905,6 +907,12 @@ usage(const XML_Char *prog, int rc) { + T(" -t write no XML output for [t]iming of plain parsing\n") + T(" -N enable adding doctype and [n]otation declarations\n") + T("\n") ++ T("billion laughs attack protection:\n") ++ T(" NOTE: If you ever need to increase these values for non-attack payload, please file a bug report.\n") ++ T("\n") ++ T(" -a FACTOR set maximum tolerated [a]mplification factor (default: 100.0)\n") ++ T(" -b BYTES set number of output [b]ytes needed to activate (default: 8 MiB)\n") ++ T("\n") + T("info arguments:\n") + T(" -h show this [h]elp message and exit\n") + T(" -v show program's [v]ersion number and exit\n") +@@ -953,6 +961,11 @@ tmain(int argc, XML_Char **argv) { + int useNamespaces = 0; + int requireStandalone = 0; + int requiresNotations = 0; ++ ++ float attackMaximumAmplification = -1.0f; /* signaling "not set" */ ++ unsigned long long attackThresholdBytes; ++ XML_Bool attackThresholdGiven = XML_FALSE; ++ + enum XML_ParamEntityParsing paramEntityParsing + = XML_PARAM_ENTITY_PARSING_NEVER; + int useStdin = 0; +@@ -1032,6 +1045,49 @@ tmain(int argc, XML_Char **argv) { + case T('v'): + showVersion(argv[0]); + return 0; ++ case T('a'): { ++ const XML_Char *valueText = NULL; ++ XMLWF_SHIFT_ARG_INTO(valueText, argc, argv, i, j); ++ ++ errno = 0; ++ XML_Char *afterValueText = (XML_Char *)valueText; ++ attackMaximumAmplification = tcstof(valueText, &afterValueText); ++ if ((errno != 0) || (afterValueText[0] != T('\0')) ++ || isnan(attackMaximumAmplification) ++ || (attackMaximumAmplification < 1.0f)) { ++ // This prevents tperror(..) from reporting misleading "[..]: Success" ++ errno = ERANGE; ++ tperror(T("invalid amplification limit") T( ++ " (needs a floating point number greater or equal than 1.0)")); ++ exit(2); ++ } ++#ifndef XML_DTD ++ ftprintf(stderr, T("Warning: Given amplification limit ignored") T( ++ ", xmlwf has been compiled without DTD support.\n")); ++#endif ++ break; ++ } ++ case T('b'): { ++ const XML_Char *valueText = NULL; ++ XMLWF_SHIFT_ARG_INTO(valueText, argc, argv, i, j); ++ ++ errno = 0; ++ XML_Char *afterValueText = (XML_Char *)valueText; ++ attackThresholdBytes = tcstoull(valueText, &afterValueText, 10); ++ if ((errno != 0) || (afterValueText[0] != T('\0'))) { ++ // This prevents tperror(..) from reporting misleading "[..]: Success" ++ errno = ERANGE; ++ tperror(T("invalid ignore threshold") ++ T(" (needs an integer from 0 to 2^64-1)")); ++ exit(2); ++ } ++ attackThresholdGiven = XML_TRUE; ++#ifndef XML_DTD ++ ftprintf(stderr, T("Warning: Given attack threshold ignored") T( ++ ", xmlwf has been compiled without DTD support.\n")); ++#endif ++ break; ++ } + case T('\0'): + if (j > 1) { + i++; +@@ -1062,6 +1118,19 @@ tmain(int argc, XML_Char **argv) { + exit(1); + } + ++ if (attackMaximumAmplification != -1.0f) { ++#ifdef XML_DTD ++ XML_SetBillionLaughsAttackProtectionMaximumAmplification( ++ parser, attackMaximumAmplification); ++#endif ++ } ++ if (attackThresholdGiven) { ++#ifdef XML_DTD ++ XML_SetBillionLaughsAttackProtectionActivationThreshold( ++ parser, attackThresholdBytes); ++#endif ++ } ++ + if (requireStandalone) + XML_SetNotStandaloneHandler(parser, notStandalone); + XML_SetParamEntityParsing(parser, paramEntityParsing); +diff --git a/xmlwf/xmlwf_helpgen.py b/xmlwf/xmlwf_helpgen.py +index 8ec8d4e..c2a527f 100755 +--- a/xmlwf/xmlwf_helpgen.py ++++ b/xmlwf/xmlwf_helpgen.py +@@ -73,6 +73,14 @@ output_mode.add_argument('-m', action='store_true', help='write [m]eta XML, not + output_mode.add_argument('-t', action='store_true', help='write no XML output for [t]iming of plain parsing') + output_related.add_argument('-N', action='store_true', help='enable adding doctype and [n]otation declarations') + ++billion_laughs = parser.add_argument_group('billion laughs attack protection', ++ description='NOTE: ' ++ 'If you ever need to increase these values ' ++ 'for non-attack payload, please file a bug report.') ++billion_laughs.add_argument('-a', metavar='FACTOR', ++ help='set maximum tolerated [a]mplification factor (default: 100.0)') ++billion_laughs.add_argument('-b', metavar='BYTES', help='set number of output [b]ytes needed to activate (default: 8 MiB)') ++ + parser.add_argument('files', metavar='FILE', nargs='*', help='files to process (default: STDIN)') + + info = parser.add_argument_group('info arguments') +-- +1.8.3.1 + diff --git a/backport-CVE-2013-0340-xmlwf-Include-expat_config.h-so-we-can-check-for-mac.patch b/backport-CVE-2013-0340-xmlwf-Include-expat_config.h-so-we-can-check-for-mac.patch new file mode 100644 index 0000000000000000000000000000000000000000..14b07b6a392381cc718a70e52792aa17c4f82d62 --- /dev/null +++ b/backport-CVE-2013-0340-xmlwf-Include-expat_config.h-so-we-can-check-for-mac.patch @@ -0,0 +1,26 @@ +From 65cddaa4e93263fb88261b60f97cf29f1589d038 Mon Sep 17 00:00:00 2001 +From: Sebastian Pipping +Date: Sun, 18 Apr 2021 20:17:43 +0200 +Subject: [PATCH] xmlwf: Include expat_config.h so we can check for macro + XML_DTD + +--- + xmlwf/xmlwf.c | 2 ++ + 1 file changed, 2 insertions(+) + +diff --git a/xmlwf/xmlwf.c b/xmlwf/xmlwf.c +index 4242e1c..2b86966 100644 +--- a/xmlwf/xmlwf.c ++++ b/xmlwf/xmlwf.c +@@ -39,6 +39,8 @@ + USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + ++#include ++ + #include + #include + #include +-- +1.8.3.1 + diff --git a/backport-CVE-2013-0340-xmlwf.1-Document-arguments-a-and-b.patch b/backport-CVE-2013-0340-xmlwf.1-Document-arguments-a-and-b.patch new file mode 100644 index 0000000000000000000000000000000000000000..7c5c780b6e613988d0f0d87b29ae987cee361990 --- /dev/null +++ b/backport-CVE-2013-0340-xmlwf.1-Document-arguments-a-and-b.patch @@ -0,0 +1,84 @@ +From bf878495985b81731c620bbac26df79e6c98c9fd Mon Sep 17 00:00:00 2001 +From: Sebastian Pipping +Date: Sun, 25 Apr 2021 18:16:14 +0200 +Subject: [PATCH] xmlwf.1: Document arguments -a and -b + +--- + doc/xmlwf.xml | 47 ++++++++++++++++++++++++++++++++++++++++++++++- + 1 file changed, 46 insertions(+), 1 deletion(-) + +diff --git a/doc/xmlwf.xml b/doc/xmlwf.xml +index 5e2a4ae..648b581 100644 +--- a/doc/xmlwf.xml ++++ b/doc/xmlwf.xml +@@ -3,7 +3,7 @@ + Scott"> + Bronson"> + +- March 11, 2016"> ++ May 4, 2021"> + + 1"> +@@ -140,6 +140,50 @@ supports both. + + + ++ factor ++ ++ ++ Sets the maximum tolerated amplification factor ++ for protection against billion laughs attacks (default: 100.0). ++ The amplification factor is calculated as .. ++ ++ ++ amplification := (direct + indirect) / direct ++ ++ ++ .. while parsing, whereas ++ <direct> is the number of bytes read ++ from the primary document in parsing and ++ <indirect> is the number of bytes ++ added by expanding entities and reading of external DTD files, ++ combined. ++ ++ ++ NOTE: ++ If you ever need to increase this value for non-attack payload, ++ please file a bug report. ++ ++ ++ ++ ++ ++ bytes ++ ++ ++ Sets the number of output bytes (including amplification) ++ needed to activate protection against billion laughs attacks ++ (default: 8 MiB). ++ This can be thought of as an "activation threshold". ++ ++ ++ NOTE: ++ If you ever need to increase this value for non-attack payload, ++ please file a bug report. ++ ++ ++ ++ ++ + + + +@@ -434,6 +478,7 @@ http://www.xml.com/pub/a/tools/ruwf/check.html + + The Expat home page: http://www.libexpat.org/ + The W3 XML specification: http://www.w3.org/TR/REC-xml ++Billion laughs attack: https://en.wikipedia.org/wiki/Billion_laughs_attack + + + +-- +1.8.3.1 + diff --git a/backport-xmlparse.c-Fix-reading-uninitialized-variable-404.patch b/backport-xmlparse.c-Fix-reading-uninitialized-variable-404.patch new file mode 100644 index 0000000000000000000000000000000000000000..c445112d055053a5035e65b1d8107aebfe58d687 --- /dev/null +++ b/backport-xmlparse.c-Fix-reading-uninitialized-variable-404.patch @@ -0,0 +1,56 @@ +From 13bb381d296ef8da09a3f00f5b23724b5deb6a38 Mon Sep 17 00:00:00 2001w +From: Sebastian Pipping +Date: Wed, 27 May 2020 20:28:06 +0200 +Subject: [PATCH] xmlparse.c: Fix reading uninitialized variable (#404) + +--- + Changes | 3 +++ + lib/xmlparse.c | 4 ++-- + 2 files changed, 5 insertions(+), 2 deletions(-) + +diff --git a/Changes b/Changes +index a642bf6..72c1f13 100644 +--- a/Changes ++++ b/Changes +@@ -18,6 +18,7 @@ Release 2.2.9 Wed Septemper 25 2019 + Bug fixes: + #390 #395 Fix undefined behavior during parsing when compiled with + -DXML_UNICODE that was introduced with Expat 2.0.1 ++ #404 Fix reading uninitialized variable during parsing + + New features: + #34 #466 #484 Add two new API functions to further tighten billion laughs +@@ -55,6 +56,8 @@ Release 2.2.9 Wed Septemper 25 2019 + Clang LeakSan + JetBrains + OSS-Fuzz ++ and ++ Cppcheck 2.0 and the Cppcheck team + + Release 2.2.8 Fri Septemper 13 2019 + Security fixes: +diff --git a/lib/xmlparse.c b/lib/xmlparse.c +index f2ad416..03da172 100644 +--- a/lib/xmlparse.c ++++ b/lib/xmlparse.c +@@ -3732,7 +3732,7 @@ doCdataSection(XML_Parser parser, const ENCODING *enc, const char **startPtr, + *startPtr = NULL; + + for (;;) { +- const char *next; ++ 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 (! accountingDiffTolerated(parser, tok, s, next, __LINE__, account)) { +@@ -3858,7 +3858,7 @@ ignoreSectionProcessor(XML_Parser parser, const char *start, const char *end, + static enum XML_Error + doIgnoreSection(XML_Parser parser, const ENCODING *enc, const char **startPtr, + const char *end, const char **nextPtr, XML_Bool haveMore) { +- const char *next; ++ const char *next = *startPtr; /* in case of XML_TOK_NONE or XML_TOK_PARTIAL */ + int tok; + const char *s = *startPtr; + const char **eventPP; +-- +1.8.3.1 + diff --git a/backport-xmlwf-Extract-macro-XMLWF_SHIFT_ARGUMENT.patch b/backport-xmlwf-Extract-macro-XMLWF_SHIFT_ARGUMENT.patch new file mode 100644 index 0000000000000000000000000000000000000000..a685e806fea50ead872721f859de0010c702eeb0 --- /dev/null +++ b/backport-xmlwf-Extract-macro-XMLWF_SHIFT_ARGUMENT.patch @@ -0,0 +1,63 @@ +From 385aeb477bac9538ab2b3d5cf0e76e8c751374be Mon Sep 17 00:00:00 2001 +From: Sebastian Pipping +Date: Sat, 17 Apr 2021 18:35:51 +0200 +Subject: [PATCH] xmlwf: Extract macro XMLWF_SHIFT_ARGUMENT + +--- + xmlwf/xmlwf.c | 31 +++++++++++++++---------------- + 1 file changed, 15 insertions(+), 16 deletions(-) + +diff --git a/xmlwf/xmlwf.c b/xmlwf/xmlwf.c +index 493b697..fc83f73 100644 +--- a/xmlwf/xmlwf.c ++++ b/xmlwf/xmlwf.c +@@ -908,6 +908,19 @@ usage(const XML_Char *prog, int rc) { + int wmain(int argc, XML_Char **argv); + #endif + ++#define XMLWF_SHIFT_ARG_INTO(constCharStarTarget, argc, argv, i, j) \ ++ { \ ++ if (argv[i][j + 1] == T('\0')) { \ ++ if (++i == argc) \ ++ usage(argv[0], 2); \ ++ constCharStarTarget = argv[i]; \ ++ } else { \ ++ constCharStarTarget = argv[i] + j + 1; \ ++ } \ ++ i++; \ ++ j = 0; \ ++ } ++ + int + tmain(int argc, XML_Char **argv) { + int i, j; +@@ -984,24 +997,10 @@ tmain(int argc, XML_Char **argv) { + j++; + break; + case T('d'): +- if (argv[i][j + 1] == T('\0')) { +- if (++i == argc) +- usage(argv[0], 2); +- outputDir = argv[i]; +- } else +- outputDir = argv[i] + j + 1; +- i++; +- j = 0; ++ XMLWF_SHIFT_ARG_INTO(outputDir, argc, argv, i, j); + break; + case T('e'): +- if (argv[i][j + 1] == T('\0')) { +- if (++i == argc) +- usage(argv[0], 2); +- encoding = argv[i]; +- } else +- encoding = argv[i] + j + 1; +- i++; +- j = 0; ++ XMLWF_SHIFT_ARG_INTO(encoding, argc, argv, i, j); + break; + case T('h'): + usage(argv[0], 0); +-- +1.8.3.1 + diff --git a/expat.spec b/expat.spec index 380fd5aabba6bf928db8411a8fabda590dfe9d72..e15ae0cd6d0c6edf23eca09b405e498f07c2d112 100644 --- a/expat.spec +++ b/expat.spec @@ -1,14 +1,34 @@ %define Rversion %(echo %{version} | sed -e 's/\\./_/g' -e 's/^/R_/') Name: expat Version: 2.2.9 -Release: 2 +Release: 3 Summary: An XML parser library License: MIT URL: https://libexpat.github.io/ Source0: https://github.com/libexpat/libexpat/releases/download/%{Rversion}/expat-%{version}.tar.gz -Patch0000: xmlparse.c-Fix-undefined-behavior-for-XML_UNICODE.patch -Patch0001: Don-t-add-to-NULL-in-iterator.patch +Patch0: xmlparse.c-Fix-undefined-behavior-for-XML_UNICODE.patch +Patch1: Don-t-add-to-NULL-in-iterator.patch + +Patch2: backport-Autotools-Give-test-suite-access-to-internal-symbols.patch +Patch3: backport-xmlwf-Extract-macro-XMLWF_SHIFT_ARGUMENT.patch +Patch4: backport-CVE-2013-0340-lib-Add-prefix-expat-to-EXPAT_ENTROPY_DEBUG-1-stderr.patch +Patch5: backport-CVE-2013-0340-xmlwf-Add-support-for-custom-attack-protection-param.patch +Patch6: backport-CVE-2013-0340-xmlwf-Include-expat_config.h-so-we-can-check-for-mac.patch +Patch7: backport-CVE-2013-0340-Changes-Document-protection-against-billion-laughs-a.patch +Patch8: backport-CVE-2013-0340-lib-Protect-against-billion-laughs-attacks-approach-.patch +Patch9: backport-CVE-2013-0340-lib-Make-EXPAT_ENTROPY_DEBUG-consistent-with-other-E.patch +Patch10: backport-CVE-2013-0340-lib-Allow-test-suite-to-access-raw-accounting-values.patch +Patch11: backport-CVE-2013-0340-Autotools-CMake-Suppress-Wpedantic-ms-format-false-p.patch +Patch12: backport-CVE-2013-0340-lib-Address-Cppcheck-2.4.1-warning-uninitvar.patch +Patch13: backport-CVE-2013-0340-tests-Cover-accounting.patch +Patch14: backport-CVE-2013-0340-xmlwf.1-Document-arguments-a-and-b.patch +Patch15: backport-CVE-2013-0340-doc-reference.html-Document-billion-laughs-attack-pr.patch +Patch16: backport-CVE-2013-0340-tests-Cover-billion-laughs-attack-protection-API.patch +Patch17: backport-CVE-2013-0340-tests-Cover-helper-unsignedCharToPrintable.patch +Patch18: backport-CVE-2013-0340-tests-Cover-accounting-of-CDATA-sections.patch +Patch19: backport-CVE-2013-0340-lib-Fix-accounting-of-CDATA-sections-inside.patch +Patch20: backport-xmlparse.c-Fix-reading-uninitialized-variable-404.patch BuildRequires: sed,autoconf,automake,gcc-c++,libtool,xmlto @@ -61,6 +81,10 @@ make check %{_mandir}/man1/* %changelog +* Fri Jul 2 2021 panxiaohe - 2.2.9-3 +- fix CVE-2013-0340 +- xmlparse.c: Fix reading uninitialized variable + * Sun Jun 28 2020 liuchenguang - 2.2.9-2 - quality enhancement synchronization github patch