diff --git a/Makefile.pre.in b/Makefile.pre.in index b0680f595a95e1c..e74deee353488fe 100644 --- a/Makefile.pre.in +++ b/Makefile.pre.in @@ -659,6 +659,7 @@ LIBEXPAT_OBJS= \ LIBEXPAT_HEADERS= \ Modules/expat/ascii.h \ Modules/expat/asciitab.h \ + Modules/expat/entities.h \ Modules/expat/expat.h \ Modules/expat/expat_config.h \ Modules/expat/expat_external.h \ diff --git a/Misc/NEWS.d/next/Security/2026-11-02-18-30-09.gh-issue-161192.2ScSRO.rst b/Misc/NEWS.d/next/Security/2026-11-02-18-30-09.gh-issue-161192.2ScSRO.rst new file mode 100644 index 000000000000000..14682058abb2351 --- /dev/null +++ b/Misc/NEWS.d/next/Security/2026-11-02-18-30-09.gh-issue-161192.2ScSRO.rst @@ -0,0 +1,3 @@ +Update bundled `libexpat `_ to version 2.10.0 +for the fixes to :cve:`2026-113351`, :cve:`2026-115506`, :cve:`2026-117610` +and :cve:`2026-118370`. diff --git a/Misc/sbom.spdx.json b/Misc/sbom.spdx.json index 0923602b2f1d871..68630ad7b45dec7 100644 --- a/Misc/sbom.spdx.json +++ b/Misc/sbom.spdx.json @@ -20,11 +20,11 @@ "checksums": [ { "algorithm": "SHA1", - "checksumValue": "27529094aa9998963bbacdab4dd77f23691adc93" + "checksumValue": "28cdbd89d1956e24045d83e5f50b4adeca1b8c0c" }, { "algorithm": "SHA256", - "checksumValue": "5ce49460794894b78f97060f634c129d13f2e9cc9b8dacd829f4be2dbc6e0649" + "checksumValue": "73dd48d5f41e789d5616e8998e5118ea0f25bde65b87a8f04e3e74d4dd51b1a4" } ], "fileName": "Modules/expat/ascii.h" @@ -43,16 +43,30 @@ ], "fileName": "Modules/expat/asciitab.h" }, + { + "SPDXID": "SPDXRef-FILE-Modules-expat-entities.h", + "checksums": [ + { + "algorithm": "SHA1", + "checksumValue": "27c38e2ca1427fe62d9ef6a30ef50f1c2c5bb9b7" + }, + { + "algorithm": "SHA256", + "checksumValue": "ed3546fec0e33f14a99c95753357becfaec7c577965dc442d1cefd437acc5e92" + } + ], + "fileName": "Modules/expat/entities.h" + }, { "SPDXID": "SPDXRef-FILE-Modules-expat-expat.h", "checksums": [ { "algorithm": "SHA1", - "checksumValue": "6bb4cc0b942c84d120fabf76299572ee8078f59c" + "checksumValue": "9a53ad014b4be691f12e9011175117e7dc5cdcf8" }, { "algorithm": "SHA256", - "checksumValue": "805f17ae5f597eeb9f058f60428a1a62268de242a096af17cbcc2e17af111bad" + "checksumValue": "71be1f9e24a8edf1c27038a28441c947c3d75f19cf36d3a17ce59f8bb7298317" } ], "fileName": "Modules/expat/expat.h" @@ -62,11 +76,11 @@ "checksums": [ { "algorithm": "SHA1", - "checksumValue": "4857095da1f3fac0c52db3fbfa455898d90ac321" + "checksumValue": "942c816cf73281239728035ca2d2211b5982ed5c" }, { "algorithm": "SHA256", - "checksumValue": "a02fba65fcf68652fc24619d673ee32969c1dca46c7a0fd1f15906722babff6d" + "checksumValue": "d4e226932e8fd84afd0e396e71b307b2c8f2bf0556d8ea037f43c2dd449b7a73" } ], "fileName": "Modules/expat/expat_external.h" @@ -118,11 +132,11 @@ "checksums": [ { "algorithm": "SHA1", - "checksumValue": "14f839bca5a88c8020ba4e3b0650b03b7c05b829" + "checksumValue": "a99cd29b07e030956bda16e9bf11dee5166417e2" }, { "algorithm": "SHA256", - "checksumValue": "9dd01acd513ff6143781e1c01f5738126a512b20f42600f9523ec95e2dd4dcf1" + "checksumValue": "2a2821fc7a2982978a8630e26cf3b8158ab21a2c2f2d06354f8c0c6547dded44" } ], "fileName": "Modules/expat/internal.h" @@ -216,11 +230,11 @@ "checksums": [ { "algorithm": "SHA1", - "checksumValue": "e5ba4185088e0217eb7be7bf90c426b33cebb524" + "checksumValue": "4f4a913dc8385799ee56263aed7e60ddb265ed51" }, { "algorithm": "SHA256", - "checksumValue": "15759b35a51a35c59b2b8f5b07057578e3505dbbce57a5adaae9f2d113b4b852" + "checksumValue": "ab9228cf9662af965bd2222cae6797c6379ab0408501965294c00ca828f6bcb8" } ], "fileName": "Modules/expat/xcs.c" @@ -230,11 +244,11 @@ "checksums": [ { "algorithm": "SHA1", - "checksumValue": "b7b265932c21cb554f3a520f754271c5af51db97" + "checksumValue": "9ec26ee2958c5e8a00eae16813c3681afee15d56" }, { "algorithm": "SHA256", - "checksumValue": "866a6cadbb9eaaaebefe08ee4f3de72a00ffdf77a6da3408605dfc16803f0bd3" + "checksumValue": "105e1e3f694821583c6475a8b8bcad23e40c846c98fbe15988aaafbca8f7a8a6" } ], "fileName": "Modules/expat/xcs.h" @@ -244,11 +258,11 @@ "checksums": [ { "algorithm": "SHA1", - "checksumValue": "b56b7fd9a13af5719738bf142fef0beb936139d5" + "checksumValue": "12420b9bec13114125947340555120b02f074288" }, { "algorithm": "SHA256", - "checksumValue": "533c0f387350f49c3e5cd392d87b8cc0268d9b49d414c10095e2143eaf8b3774" + "checksumValue": "47a6dafa356754fada8a53cf4fba7f9fbc1bcaf374c6f5f81aa37de5521e17d4" } ], "fileName": "Modules/expat/xmlparse.c" @@ -300,11 +314,11 @@ "checksums": [ { "algorithm": "SHA1", - "checksumValue": "ecbd8a75bbe1bc5a82790553d73d4f11833dcdd2" + "checksumValue": "aa4a4a6b024455f1475535b79f34dfcd9230ac8c" }, { "algorithm": "SHA256", - "checksumValue": "623dea24bc27712e2e205a0c0517c79e211234f8d1051fe46d780839f754cf9e" + "checksumValue": "83dcfa0416240e070eee2217703faf9d28aa2c56372e307e712b7ee80520d9de" } ], "fileName": "Modules/expat/xmltok.h" @@ -314,11 +328,11 @@ "checksums": [ { "algorithm": "SHA1", - "checksumValue": "1d1aabfb8dd3e9d89841d1b3f385d1def9ab46a3" + "checksumValue": "ad7531e46b116d1f9f29c579cf701161d55eea27" }, { "algorithm": "SHA256", - "checksumValue": "857d4d0984aaaca0ff6c907f0e9abf26c476fd80f6cb85896c2aa1d78eb66ae1" + "checksumValue": "dd11f257be67c2a069a91774bcf16acc2fd5052f9abf50d0819b0acb28d4803c" } ], "fileName": "Modules/expat/xmltok_impl.c" @@ -1072,14 +1086,14 @@ "checksums": [ { "algorithm": "SHA256", - "checksumValue": "16afbb9cefead2aa278105cf27d9f597bde7fbf3dbb85015857ca7ca6a4e89ba" + "checksumValue": "5466a341f9c692ef7a958a6881311be597f8e1ce345984cf78414f54793dea4c" } ], - "downloadLocation": "https://github.com/libexpat/libexpat/releases/download/R_2_9_0/expat-2.9.0.tar.gz", + "downloadLocation": "https://github.com/libexpat/libexpat/releases/download/R_2_10_0/expat-2.10.0.tar.gz", "externalRefs": [ { "referenceCategory": "SECURITY", - "referenceLocator": "cpe:2.3:a:libexpat_project:libexpat:2.9.0:*:*:*:*:*:*:*", + "referenceLocator": "cpe:2.3:a:libexpat_project:libexpat:2.10.0:*:*:*:*:*:*:*", "referenceType": "cpe23Type" } ], @@ -1087,7 +1101,7 @@ "name": "expat", "originator": "Organization: Expat development team", "primaryPackagePurpose": "SOURCE", - "versionInfo": "2.9.0" + "versionInfo": "2.10.0" }, { "SPDXID": "SPDXRef-PACKAGE-hacl-star", @@ -1150,6 +1164,11 @@ "relationshipType": "CONTAINS", "spdxElementId": "SPDXRef-PACKAGE-expat" }, + { + "relatedSpdxElement": "SPDXRef-FILE-Modules-expat-entities.h", + "relationshipType": "CONTAINS", + "spdxElementId": "SPDXRef-PACKAGE-expat" + }, { "relatedSpdxElement": "SPDXRef-FILE-Modules-expat-expat.h", "relationshipType": "CONTAINS", diff --git a/Modules/expat/ascii.h b/Modules/expat/ascii.h index 1d9cf70bf695da1..d5d82bf41e82dbd 100644 --- a/Modules/expat/ascii.h +++ b/Modules/expat/ascii.h @@ -10,7 +10,7 @@ Copyright (c) 2000 Clark Cooper Copyright (c) 2002 Fred L. Drake, Jr. Copyright (c) 2007 Karl Waclawek - Copyright (c) 2017 Sebastian Pipping + Copyright (c) 2017-2026 Sebastian Pipping Licensed under the MIT license: Permission is hereby granted, free of charge, to any person obtaining @@ -113,6 +113,7 @@ #define ASCII_LT 0x3C #define ASCII_EQUALS 0x3D #define ASCII_GT 0x3E +#define ASCII_QUEST 0x3F #define ASCII_LSQB 0x5B #define ASCII_RSQB 0x5D #define ASCII_UNDERSCORE 0x5F diff --git a/Modules/expat/entities.h b/Modules/expat/entities.h new file mode 100644 index 000000000000000..51e3ed5c151b546 --- /dev/null +++ b/Modules/expat/entities.h @@ -0,0 +1,99 @@ +/* Internal helpers related to entities + __ __ _ + ___\ \/ /_ __ __ _| |_ + / _ \\ /| '_ \ / _` | __| + | __// \| |_) | (_| | |_ + \___/_/\_\ .__/ \__,_|\__| + |_| XML parser + + Copyright (c) 2026 Sebastian Pipping + Licensed under the MIT license: + + Permission is hereby granted, free of charge, to any person obtaining + a copy of this software and associated documentation files (the + "Software"), to deal in the Software without restriction, including + without limitation the rights to use, copy, modify, merge, publish, + distribute, sublicense, and/or sell copies of the Software, and to permit + persons to whom the Software is furnished to do so, subject to the + following conditions: + + The above copyright notice and this permission notice shall be included + in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN + NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, + DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR + OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE + USE OR OTHER DEALINGS IN THE SOFTWARE. + + SPDX-License-Identifier: MIT +*/ + +#if ! defined(EXPAT_ENTITIES_H) +# define EXPAT_ENTITIES_H 1 + +# include "expat_config.h" // for XML_GE +# include "expat_external.h" // for XML_Char +# include "internal.h" // for XML_NONTESTING_STATIC +# include // for size_t +# include // for size_t + +typedef struct { + const XML_Char *name; + const XML_Char *textPtr; + size_t textLen; /* length in XML_Chars */ + size_t processed; /* # of processed bytes - when suspended */ + const XML_Char *systemId; + const XML_Char *base; + const XML_Char *publicId; + const XML_Char *notation; + bool open; + bool hasMore; /* true if entity has not been completely processed */ + /* An entity can be open while being already completely processed (!hasMore). + The reason is the delayed closing of entities until their inner + entities are processed and closed */ + bool is_param; + bool is_internal; /* true if declared in internal subset outside PE */ +# if XML_GE == 1 + /* Indirect accounting bytes charged when this entity is expanded: 1 per text + character, plus each reference's "&name;"/"%name;" token length and its own + expansion. Matches XML_ACCOUNT_ENTITY_EXPANSION, so it is not the + replacement-text size. 0 means "not computed yet" (an empty entity is 0 + too, told apart by textLen == 0). */ + XmlBigCount expandedSize; + bool beingAnalyzed; /* on the resolveEntityClosureExpandedSize depth-first + search stack; used for cycle detection */ + const XML_Char + *scanPtr; /* cursor position into the entity's replacement text; NULL when + `textPtr` is NULL, else a pointer into the entity value, + ranging from `.textPtr` to `.textPtr + .textLen`. */ +# endif /* XML_GE == 1 */ +} ENTITY; + +# if XML_GE == 1 +/* How a scanEntityText call ended. */ +enum EntityScanResult { + ENTITY_SCAN_COMPLETE, /* reached end of text; *expandedSize is final */ + ENTITY_SCAN_BLOCKED, /* stopped at a defined entity whose expanded + size has not been computed yet; *blockerOut + names the entity whose size to compute + first */ + ENTITY_SCAN_OVERFLOW /* the expanded size would exceed XmlBigCount; + callers must abort parsing */ +}; + +XML_NONTESTING_STATIC int isCDataCommentOrPIStart(const XML_Char *pos, + size_t maxLen, + size_t *openTextLen); + +XML_NONTESTING_STATIC int isPredefinedEntityName(const XML_Char *name, + size_t nameLen); + +XML_NONTESTING_STATIC enum EntityScanResult +scanEntityText(XML_Parser parser, ENTITY *entity, const XML_Char **posPtr, + XmlBigCount *expandedSize, ENTITY **blockerOut); +# endif /* XML_GE == 1 */ + +#endif // ! defined(EXPAT_ENTITIES_H) diff --git a/Modules/expat/expat.h b/Modules/expat/expat.h index 1b2852dd76cb487..5756ed31936c499 100644 --- a/Modules/expat/expat.h +++ b/Modules/expat/expat.h @@ -21,6 +21,7 @@ Copyright (c) 2024 Taichi Haradaguchi <20001722@ymail.ne.jp> Copyright (c) 2025 Matthew Fernandez Copyright (c) 2026 Braian Plaku + Copyright (c) 2026 Xisco Fauli Licensed under the MIT license: Permission is hereby granted, free of charge, to any person obtaining @@ -1084,7 +1085,7 @@ enum XML_Parser_Property { # if defined(XML_TESTING) XML_PROP_INVALID = 0, # endif -# if XML_GE == 1 +# if defined(XML_GE) && XML_GE == 1 XML_PROP_ALLOC_TRACKER_ACTIVATION_THRESHOLD = 1, // of type `uint64_t` XML_PROP_ALLOC_TRACKER_MAXIMUM_AMPLIFICATION = 2, // of type `double` XML_PROP_BILLION_LAUGHS_ACTIVATION_THRESHOLD = 3, // of type `uint64_t` @@ -1146,7 +1147,7 @@ XML_SetReparseDeferralEnabled(XML_Parser parser, XML_Bool enabled); See https://semver.org */ # define XML_MAJOR_VERSION 2 -# define XML_MINOR_VERSION 9 +# define XML_MINOR_VERSION 10 # define XML_MICRO_VERSION 0 # ifdef __cplusplus diff --git a/Modules/expat/expat_external.h b/Modules/expat/expat_external.h index 120a30e7ef3e498..b373407b10c4a60 100644 --- a/Modules/expat/expat_external.h +++ b/Modules/expat/expat_external.h @@ -12,7 +12,7 @@ Copyright (c) 2001-2002 Greg Stein Copyright (c) 2002-2006 Karl Waclawek Copyright (c) 2016 Cristian Rodríguez - Copyright (c) 2016-2025 Sebastian Pipping + Copyright (c) 2016-2026 Sebastian Pipping Copyright (c) 2017 Rhodri James Copyright (c) 2018 Yury Gribov Copyright (c) 2026 Matthew Fernandez diff --git a/Modules/expat/internal.h b/Modules/expat/internal.h index de34cd7dfc55c58..3c50a1e404ab73a 100644 --- a/Modules/expat/internal.h +++ b/Modules/expat/internal.h @@ -29,6 +29,7 @@ Copyright (c) 2026 Matthew Wozniczka Copyright (c) 2026 Braian Plaku Copyright (c) 2026 Florian Schmaus + Copyright (c) 2026 Brooks Davis Licensed under the MIT license: Permission is hereby granted, free of charge, to any person obtaining @@ -90,11 +91,10 @@ # define EXPAT_FMT_SIZE_T(midpart) "%" midpart "u" # endif # else -# include // PRIdPTR, PRIuPTR # define EXPAT_FMT_LLX(midpart) "%" midpart "llx" # define EXPAT_FMT_ULL(midpart) "%" midpart "llu" -# define EXPAT_FMT_PTRDIFF_T(midpart) "%" midpart PRIdPTR -# define EXPAT_FMT_SIZE_T(midpart) "%" midpart PRIuPTR +# define EXPAT_FMT_PTRDIFF_T(midpart) "%" midpart "td" +# define EXPAT_FMT_SIZE_T(midpart) "%" midpart "zu" # endif # ifndef UNUSED_P @@ -127,6 +127,8 @@ union expat_align { # include "expat.h" // so we can use type XML_Parser below +typedef unsigned long long XmlBigCount; + void _INTERNAL_trim_to_complete_utf8_characters(const char *from, const char **fromLimRef); diff --git a/Modules/expat/refresh.sh b/Modules/expat/refresh.sh index 734bceb3437bf5e..55f491da479e083 100755 --- a/Modules/expat/refresh.sh +++ b/Modules/expat/refresh.sh @@ -12,9 +12,9 @@ fi # Update this when updating to a new version after verifying that the changes # the update brings in are good. These values are used for verifying the SBOM, too. -expected_libexpat_tag="R_2_9_0" -expected_libexpat_version="2.9.0" -expected_libexpat_sha256="16afbb9cefead2aa278105cf27d9f597bde7fbf3dbb85015857ca7ca6a4e89ba" +expected_libexpat_tag="R_2_10_0" +expected_libexpat_version="2.10.0" +expected_libexpat_sha256="5466a341f9c692ef7a958a6881311be597f8e1ce345984cf78414f54793dea4c" expat_dir="$(realpath "$(dirname -- "${BASH_SOURCE[0]}")")" cd ${expat_dir} @@ -31,6 +31,7 @@ declare -a lib_files lib_files=( ascii.h asciitab.h + entities.h expat.h expat_external.h fallthrough.h diff --git a/Modules/expat/xcs.c b/Modules/expat/xcs.c index ae5e042d778fffa..a97647fa9d69e32 100644 --- a/Modules/expat/xcs.c +++ b/Modules/expat/xcs.c @@ -88,3 +88,24 @@ xcsncmp(const XML_Char *a, const XML_Char *b, size_t len) { return strncmp(a, b, len); #endif } + +// A port of `strnstr(3bsd)` to `XML_Char` +const XML_Char * +xcsnstr(const XML_Char *haystack, const XML_Char *needle, size_t maxLen) { + // NOTE: We are not calling `xcsnlen(haystack, maxLen)` because + // `haystack` could be long and `xcsnlen` could be used in a loop + // and we need to avoid quadratic runtime behavior. + if (! *needle) + return haystack; // i.e. empty needle is always a match + const size_t needleLen = xcslen(needle); + if (needleLen > maxLen) + return NULL; + const size_t maxTries = (maxLen - needleLen) + 1; + for (size_t i = 0; i < maxTries && haystack[i]; i++) { + if (haystack[i] == needle[0] + && xcsncmp(haystack + i, needle, needleLen) == 0) { + return haystack + i; + } + } + return NULL; +} diff --git a/Modules/expat/xcs.h b/Modules/expat/xcs.h index 1b942647d333093..9fbc4fe4c8510f5 100644 --- a/Modules/expat/xcs.h +++ b/Modules/expat/xcs.h @@ -41,4 +41,7 @@ extern size_t xcslen(const XML_Char *s); extern int xcscmp(const XML_Char *a, const XML_Char *b); extern int xcsncmp(const XML_Char *a, const XML_Char *b, size_t len); +extern const XML_Char *xcsnstr(const XML_Char *haystack, const XML_Char *needle, + size_t maxLen); + #endif // ! defined(EXPAT_XCS_H) diff --git a/Modules/expat/xmlparse.c b/Modules/expat/xmlparse.c index eefc74599cbe839..6c0c4506156176c 100644 --- a/Modules/expat/xmlparse.c +++ b/Modules/expat/xmlparse.c @@ -1,4 +1,4 @@ -/* e3ca845466942140c9af531ac57808c4650116df5d6dbeda3b6546d2548a586e (2.9.0+) +/* d0712c5efef760b5355ed61473a927256c6474aecb853016d2e335e571c64b0c (2.10.0+) __ __ _ ___\ \/ /_ __ __ _| |_ / _ \\ /| '_ \ / _` | __| @@ -58,6 +58,8 @@ Copyright (c) 2026 Braian Plaku Copyright (c) 2026 Filippo Tedeschi Copyright (c) 2026 Junki Lee + Copyright (c) 2026 Shani Yosef + Copyright (c) 2026 Pu Cheng Licensed under the MIT license: Permission is hereby granted, free of charge, to any person obtaining @@ -227,11 +229,13 @@ typedef char ICHAR; /* Do safe (NULL-aware) pointer arithmetic */ #define EXPAT_SAFE_PTR_DIFF(p, q) (((p) && (q)) ? ((p) - (q)) : 0) +#define EXPAT_SAFE_PTR_ADD(p, a) ((p) ? ((p) + (a)) : NULL) #define EXPAT_MIN(a, b) (((a) < (b)) ? (a) : (b)) #include "internal.h" #include "hash_table.h" +#include "entities.h" #include "xmltok.h" #include "xmlrole.h" @@ -309,24 +313,6 @@ typedef struct tag { BINDING *bindings; } TAG; -typedef struct { - const XML_Char *name; - const XML_Char *textPtr; - int textLen; /* length in XML_Chars */ - int processed; /* # of processed bytes - when suspended */ - const XML_Char *systemId; - const XML_Char *base; - const XML_Char *publicId; - const XML_Char *notation; - bool open; - bool hasMore; /* true if entity has not been completely processed */ - /* An entity can be open while being already completely processed (!hasMore). - The reason is the delayed closing of entities until their inner - entities are processed and closed */ - bool is_param; - bool is_internal; /* true if declared in internal subset outside PE */ -} ENTITY; - typedef struct { enum XML_Content_Type type; enum XML_Content_Quant quant; @@ -365,7 +351,7 @@ typedef struct attribute_id { typedef struct { const ATTRIBUTE_ID *id; - XML_Bool isCdata; + bool isCdata; const XML_Char *value; } DEFAULT_ATTRIBUTE; @@ -455,7 +441,6 @@ enum XML_Account { }; #if XML_GE == 1 -typedef unsigned long long XmlBigCount; typedef struct accounting { XmlBigCount countBytesDirect; XmlBigCount countBytesIndirect; @@ -467,6 +452,7 @@ typedef struct accounting { typedef struct MALLOC_TRACKER { size_t bytesAllocated; size_t peakBytesAllocated; // updated live only for debug level >=2 + size_t bytesAllocatedWhenLastReset; unsigned long debugLevel; float maximumAmplificationFactor; // >=1.0 XmlBigCount activationThresholdBytes; @@ -478,6 +464,13 @@ typedef struct entity_stats { unsigned int maximumDepthSeen; unsigned long debugLevel; } ENTITY_STATS; + +/* One pending entity-analysis task on the explicit stack used by + resolveEntityClosureExpandedSize's depth-first search over the entity + reference graph. */ +typedef struct { + ENTITY *entity; /* entity currently being scanned */ +} ENTITY_ANALYSIS_TASK; #endif /* XML_GE == 1 */ typedef enum XML_Error Processor(XML_Parser parser, const char *start, @@ -537,15 +530,15 @@ static enum XML_Error addBinding(XML_Parser parser, PREFIX *prefix, const ATTRIBUTE_ID *attId, const XML_Char *uri, BINDING **bindingsPtr); static int defineAttribute(ELEMENT_TYPE *type, ATTRIBUTE_ID *attId, - XML_Bool isCdata, XML_Bool isId, - const XML_Char *value, XML_Parser parser); + bool isCdata, bool isId, const XML_Char *value, + XML_Parser parser); static enum XML_Error storeAttributeValue(XML_Parser parser, - const ENCODING *enc, XML_Bool isCdata, + const ENCODING *enc, bool isCdata, const char *ptr, const char *end, STRING_POOL *pool, enum XML_Account account); static enum XML_Error -appendAttributeValue(XML_Parser parser, const ENCODING *enc, XML_Bool isCdata, +appendAttributeValue(XML_Parser parser, const ENCODING *enc, bool isCdata, const char *ptr, const char *end, STRING_POOL *pool, enum XML_Account account, const char **nextPtr); static ATTRIBUTE_ID *getAttributeId(XML_Parser parser, const ENCODING *enc, @@ -560,6 +553,9 @@ static enum XML_Error callStoreEntityValue(XML_Parser parser, const ENCODING *enc, const char *start, const char *end, enum XML_Account account); +static enum XML_Error resolveEntityClosureExpandedSize(XML_Parser parser, + HASH_TABLE *table, + ENTITY *root); #else static enum XML_Error storeSelfEntityValue(XML_Parser parser, ENTITY *entity); #endif @@ -597,7 +593,7 @@ static const XML_Char *poolCopyString(STRING_POOL *pool, const XML_Char *s); static const XML_Char *poolCopyStringNoFinish(STRING_POOL *pool, const XML_Char *s); static const XML_Char *poolCopyStringN(STRING_POOL *pool, const XML_Char *s, - int n); + size_t n); static const XML_Char *poolAppendString(STRING_POOL *pool, const XML_Char *s); static int nextScaffoldPart(XML_Parser parser); @@ -615,12 +611,17 @@ static XML_Parser parserCreate(const XML_Char *encodingName, const XML_Char *nameSep, DTD *dtd, XML_Parser parentParser); -static void parserInit(XML_Parser parser, const XML_Char *encodingName); +static void parserInit(XML_Parser parser, const XML_Char *encodingName, + bool isReset); #if XML_GE == 1 -static float accountingGetCurrentAmplification(XML_Parser rootParser); -static void accountingReportStats(XML_Parser originParser, const char *epilog); -static void accountingOnAbort(XML_Parser originParser); +static float accountingGetCurrentAmplification(XML_Parser rootParser, + XmlBigCount extraIndirectBytes); +static void accountingReportStats(XML_Parser originParser, const char *epilog, + XmlBigCount extraIndirectBytes, + int sourceLine); +static void accountingOnAbort(XML_Parser originParser, + XmlBigCount extraIndirectBytes, int sourceLine); static void accountingReportDiff(XML_Parser rootParser, unsigned int levelsAwayFromRootParser, const char *before, const char *after, @@ -630,6 +631,9 @@ static XML_Bool accountingDiffTolerated(XML_Parser originParser, int tok, const char *before, const char *after, int source_line, enum XML_Account account); +static XML_Bool +accountingCurrentAmplificationTolerated(XML_Parser rootParser, + XmlBigCount extraIndirectBytes); static void entityTrackingReportStats(XML_Parser parser, ENTITY *entity, const char *action, int sourceLine); @@ -637,6 +641,10 @@ static void entityTrackingOnOpen(XML_Parser parser, ENTITY *entity, int sourceLine); static void entityTrackingOnClose(XML_Parser parser, ENTITY *entity, int sourceLine); +static void entityTrackingOnScanStart(XML_Parser parser, ENTITY *entity, + int sourceLine); +static void entityTrackingOnScanStop(XML_Parser parser, ENTITY *entity, + int sourceLine); #endif /* XML_GE == 1 */ static XML_Parser getRootParserOf(XML_Parser parser, @@ -792,8 +800,8 @@ struct XML_ParserStruct { const XML_Char *m_declNotationPublicId; ELEMENT_TYPE *m_declElementType; ATTRIBUTE_ID *m_declAttributeId; - XML_Bool m_declAttributeIsCdata; - XML_Bool m_declAttributeIsId; + bool m_declAttributeIsCdata; + bool m_declAttributeIsId; DTD *m_dtd; const XML_Char *m_curBase; TAG *m_tagStack; @@ -865,11 +873,22 @@ expat_heap_increase_tolerable(XML_Parser rootParser, size_t increase, size_t newTotal = 0; bool tolerable = true; + // The idea is to subtract the amount of memory that was already + // allocated the last time that `XML_ParserReset` was called for + // this parser, the memory use of one document could skew the + // memory statso of the next document. + const size_t bytesAllocated + = (rootParser->m_alloc_tracker.bytesAllocatedWhenLastReset + > rootParser->m_alloc_tracker.bytesAllocated) + ? 0 + : (rootParser->m_alloc_tracker.bytesAllocated + - rootParser->m_alloc_tracker.bytesAllocatedWhenLastReset); + // Detect integer overflow - if (SIZE_MAX - rootParser->m_alloc_tracker.bytesAllocated < increase) { + if (SIZE_MAX - bytesAllocated < increase) { tolerable = false; } else { - newTotal = rootParser->m_alloc_tracker.bytesAllocated + increase; + newTotal = bytesAllocated + increase; if (newTotal >= rootParser->m_alloc_tracker.activationThresholdBytes) { assert(newTotal > 0); @@ -1449,7 +1468,7 @@ parserCreate(const XML_Char *encodingName, poolInit(&parser->m_tempPool, parser); poolInit(&parser->m_temp2Pool, parser); - parserInit(parser, encodingName); + parserInit(parser, encodingName, /*isReset=*/false); if (encodingName && ! parser->m_protocolEncodingName) { if (dtd) { @@ -1476,7 +1495,7 @@ parserCreate(const XML_Char *encodingName, } static void -parserInit(XML_Parser parser, const XML_Char *encodingName) { +parserInit(XML_Parser parser, const XML_Char *encodingName, bool isReset) { parser->m_processor = prologInitProcessor; XmlPrologStateInit(&parser->m_prologState); if (encodingName != NULL) { @@ -1524,8 +1543,8 @@ parserInit(XML_Parser parser, const XML_Char *encodingName) { parser->m_declAttributeType = NULL; parser->m_declNotationName = NULL; parser->m_declNotationPublicId = NULL; - parser->m_declAttributeIsCdata = XML_FALSE; - parser->m_declAttributeIsId = XML_FALSE; + parser->m_declAttributeIsCdata = false; + parser->m_declAttributeIsId = false; memset(&parser->m_position, 0, sizeof(POSITION)); parser->m_errorCode = XML_ERROR_NONE; parser->m_eventPtr = NULL; @@ -1544,6 +1563,7 @@ parserInit(XML_Parser parser, const XML_Char *encodingName) { parser->m_unknownEncodingRelease = NULL; parser->m_unknownEncodingData = NULL; parser->m_parsingStatus.parsing = XML_INITIALIZED; + parser->m_parsingStatus.finalBuffer = XML_FALSE; // Reentry can only be triggered inside m_processor calls parser->m_reenter = XML_FALSE; parser->m_handlerCallDepth = 0; @@ -1557,15 +1577,24 @@ parserInit(XML_Parser parser, const XML_Char *encodingName) { parser->m_hash_secret_salt_set = XML_FALSE; #if XML_GE == 1 - memset(&parser->m_accounting, 0, sizeof(ACCOUNTING)); - parser->m_accounting.debugLevel = getDebugLevel("EXPAT_ACCOUNTING_DEBUG", 0u); - parser->m_accounting.maximumAmplificationFactor - = EXPAT_BILLION_LAUGHS_ATTACK_PROTECTION_MAXIMUM_AMPLIFICATION_DEFAULT; - parser->m_accounting.activationThresholdBytes - = 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); + parser->m_accounting.countBytesDirect = 0; + parser->m_accounting.countBytesIndirect = 0; + if (! isReset) { + 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; + } + + parser->m_entity_stats.countEverOpened = 0; + parser->m_entity_stats.currentDepth = 0; + parser->m_entity_stats.maximumDepthSeen = 0; + if (! isReset) + parser->m_entity_stats.debugLevel = getDebugLevel("EXPAT_ENTITY_DEBUG", 0u); +#else + UNUSED_P(isReset); #endif } @@ -1628,8 +1657,16 @@ XML_ParserReset(XML_Parser parser, const XML_Char *encodingName) { poolClear(&parser->m_temp2Pool); FREE(parser, (void *)parser->m_protocolEncodingName); parser->m_protocolEncodingName = NULL; - parserInit(parser, encodingName); + parserInit(parser, encodingName, /*isReset=*/true); dtdReset(parser->m_dtd, parser); + +#if XML_GE == 1 + parser->m_alloc_tracker.bytesAllocatedWhenLastReset + = parser->m_alloc_tracker.bytesAllocated; +#endif + + // NOTE: This effectively checks for `copyString` having failed during the + // call to `parserInit` above if (encodingName && ! parser->m_protocolEncodingName) { parser->m_errorCode = XML_ERROR_NO_MEMORY; return XML_FALSE; @@ -2299,13 +2336,12 @@ XML_SetHashSalt16Bytes(XML_Parser parser, const uint8_t entropy[16]) { enum XML_Status XMLCALL XML_Parse(XML_Parser parser, const char *s, int len, int isFinal) { - if ((parser == NULL) || (len < 0) || ((s == NULL) && (len != 0))) { - if (parser != NULL) - parser->m_errorCode = XML_ERROR_INVALID_ARGUMENT; + if (parser == NULL || isCalledFromInsideHandler(parser)) return XML_STATUS_ERROR; - } - if (isCalledFromInsideHandler(parser)) + if ((len < 0) || ((s == NULL) && (len != 0))) { + parser->m_errorCode = XML_ERROR_INVALID_ARGUMENT; return XML_STATUS_ERROR; + } switch (parser->m_parsingStatus.parsing) { case XML_SUSPENDED: parser->m_errorCode = XML_ERROR_SUSPENDED; @@ -2325,8 +2361,15 @@ XML_Parse(XML_Parser parser, const char *s, int len, int isFinal) { #if XML_CONTEXT_BYTES == 0 if (parser->m_bufferPtr == parser->m_bufferEnd) { - const char *end; - int nLeftOver; + // This null-object trick allows us to stop worrying about case `s == NULL` + // within this branch, e.g. with `s + len - end`. + const char *const empty = ""; + if (s == NULL) { + assert(len == 0); + s = empty; + } + const char *end = s + len; + size_t nLeftOver; enum XML_Status result; /* Detect overflow (a+b > MAX <==> b > MAX-a) */ if ((uint64_t)len > UINT64_MAX - parser->m_parseEndByteIndex) { @@ -2343,7 +2386,7 @@ XML_Parse(XML_Parser parser, const char *s, int len, int isFinal) { parser->m_parsingStatus.finalBuffer = (XML_Bool)isFinal; parser->m_errorCode - = callProcessor(parser, s, parser->m_parseEndPtr = s + len, &end); + = callProcessor(parser, s, parser->m_parseEndPtr = end, &end); if (parser->m_errorCode != XML_ERROR_NONE) { parser->m_eventEndPtr = parser->m_eventPtr; @@ -2373,8 +2416,14 @@ XML_Parse(XML_Parser parser, const char *s, int len, int isFinal) { // Back up and restore the parsing status to avoid XML_ERROR_SUSPENDED // (and XML_ERROR_FINISHED) from XML_GetBuffer. const enum XML_Parsing originalStatus = parser->m_parsingStatus.parsing; + if (nLeftOver > INT_MAX) { + parser->m_errorCode = XML_ERROR_NO_MEMORY; + parser->m_eventPtr = parser->m_eventEndPtr = NULL; + parser->m_processor = errorProcessor; + return XML_STATUS_ERROR; + } parser->m_parsingStatus.parsing = XML_PARSING; - void *const temp = XML_GetBuffer(parser, nLeftOver); + void *const temp = XML_GetBuffer(parser, (int)nLeftOver); parser->m_parsingStatus.parsing = originalStatus; // GetBuffer may have overwritten this, but we want to remember what the // app requested, not how many bytes were left over after parsing. @@ -2392,7 +2441,7 @@ XML_Parse(XML_Parser parser, const char *s, int len, int isFinal) { memcpy(parser->m_buffer, end, nLeftOver); } parser->m_bufferPtr = parser->m_buffer; - parser->m_bufferEnd = parser->m_buffer + nLeftOver; + parser->m_bufferEnd = EXPAT_SAFE_PTR_ADD(parser->m_buffer, nLeftOver); parser->m_positionPtr = parser->m_bufferPtr; parser->m_parseEndPtr = parser->m_bufferEnd; parser->m_eventPtr = parser->m_bufferPtr; @@ -3474,7 +3523,7 @@ externalEntityInitProcessor2(XML_Parser parser, const char *start, #if XML_GE == 1 if (! accountingDiffTolerated(parser, tok, start, next, __LINE__, XML_ACCOUNT_DIRECT)) { - accountingOnAbort(parser); + accountingOnAbort(parser, 0, __LINE__); return XML_ERROR_AMPLIFICATION_LIMIT_BREACH; } #endif /* XML_GE == 1 */ @@ -3504,6 +3553,11 @@ externalEntityInitProcessor2(XML_Parser parser, const char *start, } parser->m_eventPtr = start; return XML_ERROR_PARTIAL_CHAR; + case XML_TOK_NONE: + if (! parser->m_parsingStatus.finalBuffer) { + *endPtr = start; + } + return XML_ERROR_NONE; } parser->m_processor = externalEntityInitProcessor3; return externalEntityInitProcessor3(parser, start, end, endPtr); @@ -3603,7 +3657,7 @@ doContent(XML_Parser parser, int startTagLevel, const ENCODING *enc, : next; if (! accountingDiffTolerated(parser, tok, s, accountAfter, __LINE__, account)) { - accountingOnAbort(parser); + accountingOnAbort(parser, 0, __LINE__); return XML_ERROR_AMPLIFICATION_LIMIT_BREACH; } #endif @@ -4035,10 +4089,12 @@ doContent(XML_Parser parser, int startTagLevel, const ENCODING *enc, const enum XML_Convert_Result convert_res = XmlConvert( enc, &s, next, &dataPtr, (ICHAR *)parser->m_dataBufEnd); *eventEndPP = s; - beforeHandler(parser); - charDataHandler(parser->m_handlerArg, parser->m_dataBuf, - (int)(dataPtr - (ICHAR *)parser->m_dataBuf)); - afterHandler(parser); + if (parser->m_characterDataHandler) { + beforeHandler(parser); + charDataHandler(parser->m_handlerArg, parser->m_dataBuf, + (int)(dataPtr - (ICHAR *)parser->m_dataBuf)); + afterHandler(parser); + } if ((convert_res == XML_CONVERT_COMPLETED) || (convert_res == XML_CONVERT_INPUT_INCOMPLETE)) break; @@ -4135,9 +4191,9 @@ storeAtts(XML_Parser parser, const ENCODING *enc, const char *attStr, TAG_NAME *tagNamePtr, BINDING **bindingsPtr, enum XML_Account account) { DTD *const dtd = parser->m_dtd; /* save one level of indirection */ - int attIndex = 0; + size_t attIndex = 0; XML_Char *uri; - int nPrefixes = 0; + size_t nPrefixes = 0; BINDING *binding; const XML_Char *localPart; @@ -4164,8 +4220,7 @@ storeAtts(XML_Parser parser, const ENCODING *enc, const char *attStr, return XML_ERROR_NO_MEMORY; /* get the attributes from the tokenizer */ - size_t n = (size_t)XmlGetAttributes(enc, attStr, (int)parser->m_attsSize, - parser->m_atts); + size_t n = XmlGetAttributes(enc, attStr, parser->m_attsSize, parser->m_atts); /* Detect and prevent integer overflow */ if (n > SIZE_MAX - nDefaultAtts) { @@ -4200,11 +4255,22 @@ storeAtts(XML_Parser parser, const ENCODING *enc, const char *attStr, /* Detect and prevent integer overflow. */ if (n > (size_t)INT_MAX) return XML_ERROR_NO_MEMORY; - XmlGetAttributes(enc, attStr, (int)n, parser->m_atts); + XmlGetAttributes(enc, attStr, n, parser->m_atts); } } /* the attribute list for the application */ + // NOTE: `parser->m_atts` is used to read `ATTRIBUTE` elements + // from start to end while at the same time writing `XML_Char *` + // elements from start to end *to the same block of memory*. + // That black magic only works because the first member `.name` in + // `ATTRIBUTE` is no longer being read after `appAtts[attIndex]` + // has been written, which for the first element is the very same + // address in memory. + // And because `attIndex` is increased twice for default attributes + // further down, struct `ATTRIBUTE` must be at least twice the size + // of a pointer, or the allocation size math for `m_attsSize` starts + // to underallocate. const XML_Char **const appAtts = (const XML_Char **)parser->m_atts; for (size_t i = 0; i < n; i++) { ATTRIBUTE *currAtt = &parser->m_atts[i]; @@ -4224,9 +4290,11 @@ storeAtts(XML_Parser parser, const ENCODING *enc, const char *attStr, return XML_ERROR_DUPLICATE_ATTRIBUTE; } (attId->name)[-1] = 1; + // NOTE: The next call overwrites `parser->m_atts`, so we cannot use + // e.g. `parser->m_atts[i].name;` after this line! appAtts[attIndex++] = attId->name; if (! parser->m_atts[i].normalized) { - XML_Bool isCdata = XML_TRUE; + bool isCdata = true; /* figure out whether declared as other than CDATA */ if (attId->maybeTokenized) { @@ -4278,11 +4346,16 @@ storeAtts(XML_Parser parser, const ENCODING *enc, const char *attStr, } /* set-up for XML_GetSpecifiedAttributeCount and XML_GetIdAttributeIndex */ - parser->m_nSpecifiedAtts = attIndex; + // Detect and reject integer overflow + if (attIndex > INT_MAX) + return XML_ERROR_NO_MEMORY; + parser->m_nSpecifiedAtts = (int)attIndex; if (elementType->idAtt && (elementType->idAtt->name)[-1]) { - for (int i = 0; i < attIndex; i += 2) + for (size_t i = 0; i < attIndex; i += 2) if (appAtts[i] == elementType->idAtt->name) { - parser->m_idAttIndex = i; + if (i > INT_MAX) + return XML_ERROR_NO_MEMORY; + parser->m_idAttIndex = (int)i; break; } } else @@ -4315,7 +4388,7 @@ storeAtts(XML_Parser parser, const ENCODING *enc, const char *attStr, /* expand prefixed attribute names, check for duplicates, and clear flags that say whether attributes were specified */ - int i = 0; + size_t i = 0; if (nPrefixes) { unsigned int j; /* hash table index */ unsigned long version = parser->m_nsAttsVersion; @@ -4871,7 +4944,7 @@ doCdataSection(XML_Parser parser, const ENCODING *enc, const char **startPtr, int tok = XmlCdataSectionTok(enc, s, end, &next); #if XML_GE == 1 if (! accountingDiffTolerated(parser, tok, s, next, __LINE__, account)) { - accountingOnAbort(parser); + accountingOnAbort(parser, 0, __LINE__); return XML_ERROR_AMPLIFICATION_LIMIT_BREACH; } #else @@ -5043,7 +5116,7 @@ doIgnoreSection(XML_Parser parser, const ENCODING *enc, const char **startPtr, # if XML_GE == 1 if (! accountingDiffTolerated(parser, tok, s, next, __LINE__, XML_ACCOUNT_DIRECT)) { - accountingOnAbort(parser); + accountingOnAbort(parser, 0, __LINE__); return XML_ERROR_AMPLIFICATION_LIMIT_BREACH; } # endif @@ -5135,7 +5208,7 @@ processXmlDecl(XML_Parser parser, int isGeneralTextEntity, const char *s, #if XML_GE == 1 if (! accountingDiffTolerated(parser, XML_TOK_XML_DECL, s, next, __LINE__, XML_ACCOUNT_DIRECT)) { - accountingOnAbort(parser); + accountingOnAbort(parser, 0, __LINE__); return XML_ERROR_AMPLIFICATION_LIMIT_BREACH; } #endif @@ -5356,7 +5429,7 @@ entityValueInitProcessor(XML_Parser parser, const char *s, const char *end, # if XML_GE == 1 if (! accountingDiffTolerated(parser, tok, s, next, __LINE__, XML_ACCOUNT_DIRECT)) { - accountingOnAbort(parser); + accountingOnAbort(parser, 0, __LINE__); return XML_ERROR_AMPLIFICATION_LIMIT_BREACH; } # endif @@ -5409,7 +5482,7 @@ externalParEntProcessor(XML_Parser parser, const char *s, const char *end, else if (tok == XML_TOK_BOM) { if (! accountingDiffTolerated(parser, tok, s, next, __LINE__, XML_ACCOUNT_DIRECT)) { - accountingOnAbort(parser); + accountingOnAbort(parser, 0, __LINE__); return XML_ERROR_AMPLIFICATION_LIMIT_BREACH; } @@ -5588,7 +5661,7 @@ doProlog(XML_Parser parser, const ENCODING *enc, const char *s, const char *end, break; default: if (! accountingDiffTolerated(parser, tok, s, next, __LINE__, account)) { - accountingOnAbort(parser); + accountingOnAbort(parser, 0, __LINE__); return XML_ERROR_AMPLIFICATION_LIMIT_BREACH; } } @@ -5685,7 +5758,7 @@ doProlog(XML_Parser parser, const ENCODING *enc, const char *s, const char *end, return XML_ERROR_INVALID_TOKEN; } - if (parser->m_doctypeName) { + if (parser->m_doctypeName && parser->m_startDoctypeDeclHandler) { beforeHandler(parser); parser->m_startDoctypeDeclHandler( parser->m_handlerArg, parser->m_doctypeName, parser->m_doctypeSysid, @@ -5751,7 +5824,7 @@ doProlog(XML_Parser parser, const ENCODING *enc, const char *s, const char *end, handleDefault = XML_FALSE; } break; - case XML_ROLE_INSTANCE_START: + case XML_ROLE_INSTANCE_START: { #ifdef XML_DTD /* if there is no DOCTYPE declaration then now is the last chance to read the foreign DTD @@ -5795,6 +5868,7 @@ doProlog(XML_Parser parser, const ENCODING *enc, const char *s, const char *end, #endif /* XML_DTD */ parser->m_processor = contentProcessor; return contentProcessor(parser, s, end, nextPtr); + } case XML_ROLE_ATTLIST_ELEMENT_NAME: parser->m_declElementType = getElementType(parser, enc, s, next); if (! parser->m_declElementType) @@ -5804,16 +5878,16 @@ doProlog(XML_Parser parser, const ENCODING *enc, const char *s, const char *end, parser->m_declAttributeId = getAttributeId(parser, enc, s, next); if (! parser->m_declAttributeId) return XML_ERROR_NO_MEMORY; - parser->m_declAttributeIsCdata = XML_FALSE; + parser->m_declAttributeIsCdata = false; parser->m_declAttributeType = NULL; - parser->m_declAttributeIsId = XML_FALSE; + parser->m_declAttributeIsId = false; goto checkAttListDeclHandler; case XML_ROLE_ATTRIBUTE_TYPE_CDATA: - parser->m_declAttributeIsCdata = XML_TRUE; + parser->m_declAttributeIsCdata = true; parser->m_declAttributeType = atypeCDATA; goto checkAttListDeclHandler; case XML_ROLE_ATTRIBUTE_TYPE_ID: - parser->m_declAttributeIsId = XML_TRUE; + parser->m_declAttributeIsId = true; parser->m_declAttributeType = atypeID; goto checkAttListDeclHandler; case XML_ROLE_ATTRIBUTE_TYPE_IDREF: @@ -5901,7 +5975,7 @@ doProlog(XML_Parser parser, const ENCODING *enc, const char *s, const char *end, /* ID attributes aren't allowed to have a default */ if (! defineAttribute( parser->m_declElementType, parser->m_declAttributeId, - parser->m_declAttributeIsCdata, XML_FALSE, attVal, parser)) + parser->m_declAttributeIsCdata, false, attVal, parser)) return XML_ERROR_NO_MEMORY; if (parser->m_attlistDeclHandler && parser->m_declAttributeType) { if (*parser->m_declAttributeType == XML_T(ASCII_LPAREN) @@ -5931,17 +6005,23 @@ doProlog(XML_Parser parser, const ENCODING *enc, const char *s, const char *end, #if XML_GE == 1 // This will store the given replacement text in // parser->m_declEntity->textPtr. - enum XML_Error result = callStoreEntityValue( + const enum XML_Error result = callStoreEntityValue( parser, enc, s + enc->minBytesPerChar, next - enc->minBytesPerChar, XML_ACCOUNT_NONE); + if (result != XML_ERROR_NONE) { + poolDiscard(&dtd->entityValuePool); + return result; + } if (parser->m_declEntity) { /* Detect and prevent signed integer overflow */ if ((size_t)poolLength(&dtd->entityValuePool) > (size_t)INT_MAX) { + poolDiscard(&dtd->entityValuePool); return XML_ERROR_NO_MEMORY; } - parser->m_declEntity->textPtr = poolStart(&dtd->entityValuePool); - parser->m_declEntity->textLen - = (int)(poolLength(&dtd->entityValuePool)); + const XML_Char *const valueStart = poolStart(&dtd->entityValuePool); + parser->m_declEntity->textPtr = valueStart; + parser->m_declEntity->textLen = poolLength(&dtd->entityValuePool); + parser->m_declEntity->scanPtr = valueStart; poolFinish(&dtd->entityValuePool); if (parser->m_entityDeclHandler) { *eventEndPP = s; @@ -5949,14 +6029,12 @@ doProlog(XML_Parser parser, const ENCODING *enc, const char *s, const char *end, parser->m_entityDeclHandler( parser->m_handlerArg, parser->m_declEntity->name, parser->m_declEntity->is_param, parser->m_declEntity->textPtr, - parser->m_declEntity->textLen, parser->m_curBase, 0, 0, 0); + (int)parser->m_declEntity->textLen, parser->m_curBase, 0, 0, 0); afterHandler(parser); handleDefault = XML_FALSE; } } else poolDiscard(&dtd->entityValuePool); - if (result != XML_ERROR_NONE) - return result; #else // This will store "&entity123;" in parser->m_declEntity->textPtr // to end up as "&entity123;" in the handler. @@ -5972,7 +6050,7 @@ doProlog(XML_Parser parser, const ENCODING *enc, const char *s, const char *end, parser->m_entityDeclHandler( parser->m_handlerArg, parser->m_declEntity->name, parser->m_declEntity->is_param, parser->m_declEntity->textPtr, - parser->m_declEntity->textLen, parser->m_curBase, 0, 0, 0); + (int)parser->m_declEntity->textLen, parser->m_curBase, 0, 0, 0); afterHandler(parser); handleDefault = XML_FALSE; } @@ -6091,6 +6169,17 @@ doProlog(XML_Parser parser, const ENCODING *enc, const char *s, const char *end, } break; case XML_ROLE_GENERAL_ENTITY_NAME: { + // NOTE: XML 1.0 fourth edition demands in section + // "4.6 Predefined Entities" that bad redefinitions of predefined + // general entities must be detected and rejected. Expat has been + // ignoring both conforming and non-conforming redefinitions and + // thus has been failing to reject bad redefinitions, for years. + // As a result, users learned that they can get away with + // `` in practice. We cannot start conforming to + // that part of the spec now, or we risk rejecting many documents + // that parsed fine for years and that may not be fixable now or + // in time. So Expat continues to discard redefinitions of + // predefined entities altogether. if (XmlPredefinedEntityName(enc, s, next)) { parser->m_declEntity = NULL; break; @@ -6617,7 +6706,7 @@ epilogProcessor(XML_Parser parser, const char *s, const char *end, #if XML_GE == 1 if (! accountingDiffTolerated(parser, tok, s, next, __LINE__, XML_ACCOUNT_DIRECT)) { - accountingOnAbort(parser); + accountingOnAbort(parser, 0, __LINE__); return XML_ERROR_AMPLIFICATION_LIMIT_BREACH; } #endif @@ -6684,6 +6773,415 @@ epilogProcessor(XML_Parser parser, const char *s, const char *end, } } +#if XML_GE == 1 +// Checks `name` for being a predefined entity (i.e. one of "amp", "apos", "gt", +// "lt", "quot"). Similar in implementation to function +// `PREFIX(predefinedEntityName)` but operating on an `XML_Char` string with an +// explicit length, instead. Returns +// ASCII_AMP/ASCII_APOS/ASCII_GT/ASCII_LT/ASCII_QUOT for a match or 0 for no +// match. +XML_NONTESTING_STATIC int +isPredefinedEntityName(const XML_Char *name, size_t nameLen) { + switch (nameLen) { + case 2: + // Wanted: "lt" or "gt" + if (name[1] != XML_T(ASCII_t)) + break; + switch (name[0]) { + case XML_T(ASCII_l): + return ASCII_LT; + case XML_T(ASCII_g): + return ASCII_GT; + } + break; + case 3: + // Wanted: "amp" + if (name[0] == XML_T(ASCII_a) && name[1] == XML_T(ASCII_m) + && name[2] == XML_T(ASCII_p)) + return ASCII_AMP; + break; + case 4: + // Wanted: "apos" or "quot" + switch (name[0]) { + case XML_T(ASCII_a): + if (name[1] == XML_T(ASCII_p) && name[2] == XML_T(ASCII_o) + && name[3] == XML_T(ASCII_s)) + return ASCII_APOS; + break; + case XML_T(ASCII_q): + if (name[1] == XML_T(ASCII_u) && name[2] == XML_T(ASCII_o) + && name[3] == XML_T(ASCII_t)) + return ASCII_QUOT; + break; + } + } + return 0; +} + +static const XML_Char cdataOpen[] + = {ASCII_LT, ASCII_EXCL, ASCII_LSQB, ASCII_C, ASCII_D, + ASCII_A, ASCII_T, ASCII_A, ASCII_LSQB, '\0'}; // i.e. "" +static const XML_Char commentOpen[] + = {ASCII_LT, ASCII_EXCL, ASCII_MINUS, ASCII_MINUS, '\0'}; // i.e. "" +static const XML_Char piClose[] = {ASCII_QUEST, ASCII_GT, '\0'}; // i.e. "?>" + +static const size_t cdataOpenLen = sizeof(cdataOpen) / sizeof(cdataOpen[0]) - 1; +static const size_t cdataCloseLen + = sizeof(cdataClose) / sizeof(cdataClose[0]) - 1; +static const size_t commentOpenLen + = sizeof(commentOpen) / sizeof(commentOpen[0]) - 1; +static const size_t commentCloseLen + = sizeof(commentClose) / sizeof(commentClose[0]) - 1; +static const size_t piCloseLen = sizeof(piClose) / sizeof(piClose[0]) - 1; + +// Checks the string starting at `pos` for starting a CDATA section, a comment +// or processing instructions. Returns +// XML_TOK_CDATA_SECT_OPEN/XML_TOK_COMMENT/XML_TOK_PI for a match or +// XML_TOK_INVALID (=0) for no match. Only in the success case, the length +// of the token is written to `*openTextLen`. +XML_NONTESTING_STATIC int +isCDataCommentOrPIStart(const XML_Char *pos, size_t maxLen, + size_t *openTextLen) { + assert(pos != NULL); + assert(openTextLen != NULL); + + if (maxLen < 2) + return XML_TOK_INVALID; + + if (pos[0] != XML_T(ASCII_LT)) + return XML_TOK_INVALID; + + if (pos[1] == XML_T(ASCII_QUEST)) { + *openTextLen = 2; + return XML_TOK_PI; + } + + if (pos[1] != XML_T(ASCII_EXCL)) + return XML_TOK_INVALID; + + if (maxLen >= cdataOpenLen && xcsncmp(pos, cdataOpen, cdataOpenLen) == 0) { + *openTextLen = cdataOpenLen; + return XML_TOK_CDATA_SECT_OPEN; + } + + if (maxLen >= commentOpenLen + && xcsncmp(pos, commentOpen, commentOpenLen) == 0) { + *openTextLen = commentOpenLen; + return XML_TOK_COMMENT; + } + + return XML_TOK_INVALID; +} + +/* Scan `entity`'s body, starting at `*posPtr`, accumulating the expanded + size into `*expandedSize`. + + Return value: how the scan ended, see enum EntityScanResult. + + *blockerOut (out, set only for ENTITY_SCAN_BLOCKED): + The non-empty entity the scan stopped at because its expanded size + has not been computed yet. The caller computes that size first and + calls again with *posPtr unchanged to resume. + The pointer is borrowed from the entity hash table (via lookup), not + a new allocation, so it must not be freed. + + *posPtr (in/out, scan cursor): + Advanced to end-of-text on a complete scan, or parked at the + offending reference otherwise, so a follow-up call resumes from + the same position. Not meaningful after ENTITY_SCAN_OVERFLOW. + + *expandedSize (in/out, expanded-byte accumulator): + The expanded size of this one `entity` (its text plus the expandedSize + of entities it references), not a total across all entities. Not + meaningful after ENTITY_SCAN_OVERFLOW; the overflowing value is never + stored anywhere. */ +XML_NONTESTING_STATIC enum EntityScanResult +scanEntityText(XML_Parser parser, ENTITY *entity, const XML_Char **posPtr, + XmlBigCount *expandedSize, ENTITY **blockerOut) { + if (blockerOut != NULL) + *blockerOut = NULL; + const XML_Char refChar = entity->is_param ? XML_T('%') : XML_T('&'); + HASH_TABLE *const refTable +# ifdef XML_DTD + = entity->is_param ? &parser->m_dtd->paramEntities + : &parser->m_dtd->generalEntities; +# else + = &parser->m_dtd->generalEntities; +# endif + + const XML_Char *pos = *posPtr; + const XML_Char *const end = entity->textPtr + entity->textLen; + + while (pos < end) { + // Detect and skip CDATA/comment/PI sections + // because entities have no effect in these places + if (*pos == XML_T('<')) { + // Detect CDATA/comment/PI section start + size_t openTextLen = 0; + const int tok = isCDataCommentOrPIStart(pos, end - pos, &openTextLen); + if (tok != XML_TOK_INVALID) { + assert(openTextLen != 0); + + // Detect and avoid integer overflow + if (openTextLen > (XmlBigCount)(-1) - *expandedSize) + return ENTITY_SCAN_OVERFLOW; + + *expandedSize += openTextLen; + pos += openTextLen; + + // Detect CDATA/comment/PI section end + const XML_Char *closeText = NULL; + size_t closeLen = 0; + + switch (tok) { + case XML_TOK_CDATA_SECT_OPEN: + closeText = cdataClose; + closeLen = cdataCloseLen; + break; + case XML_TOK_COMMENT: + closeText = commentClose; + closeLen = commentCloseLen; + break; + case XML_TOK_PI: + closeText = piClose; + closeLen = piCloseLen; + break; + default: + assert(0 && "never gets here"); + break; + } + + const size_t remainderLen = end - pos; + const XML_Char *const closeStart + = xcsnstr(pos, closeText, remainderLen); + if (closeStart == NULL) { + // The CDATA/comment/PI section is NOT terminated + if (remainderLen > (XmlBigCount)(-1) - *expandedSize) + return ENTITY_SCAN_OVERFLOW; + *expandedSize += remainderLen; + *posPtr = end; + return ENTITY_SCAN_COMPLETE; + } + + // The CDATA/comment/PI section is NOT terminated + const size_t skippedLen = closeStart + closeLen - pos; + + // Detect and avoid integer overflow + if (skippedLen > (XmlBigCount)(-1) - *expandedSize) + return ENTITY_SCAN_OVERFLOW; + + *expandedSize += skippedLen; + pos += skippedLen; + + continue; + } + } + + // Detect an entity reference + if (*pos != refChar) { + // Detect and avoid integer overflow + if (*expandedSize == (XmlBigCount)(-1)) + return ENTITY_SCAN_OVERFLOW; + (*expandedSize)++; + pos++; + continue; + } + + const XML_Char *nameStart = pos + 1; + const XML_Char *nameEnd = nameStart; + while (nameEnd < end && *nameEnd != XML_T(';')) + nameEnd++; + + if (nameEnd >= end) { + /* Unterminated reference at end of text: treat the trailing + bytes as literal characters. */ + XmlBigCount addend = (XmlBigCount)(end - pos); + // Detect and avoid integer overflow + if (*expandedSize > (XmlBigCount)(-1) - addend) + return ENTITY_SCAN_OVERFLOW; + *expandedSize += addend; + *posPtr = end; + return ENTITY_SCAN_COMPLETE; + } + + const size_t nameLen = (size_t)(nameEnd - nameStart); + + /* At runtime the whole "&name;"/"%name;" token is re-scanned and charged to + the indirect accounting, then its expansion is charged separately; so + every reference below counts this token length plus its expansion. */ + const XmlBigCount refTokenLen + = (XmlBigCount)(/*sigil*/ 1 + nameLen + /*semicolon*/ 1); + + ENTITY *refEntity + = (ENTITY *)lookupWithLength(parser, refTable, nameStart, nameLen, 0); + + XmlBigCount addend = 0; + if (! refEntity) { + /* Predefined entities (amp/lt/gt/quot/apos) are general-only, hence the + `! entity->is_param` guard, and expand to a single character. */ + if (! entity->is_param && isPredefinedEntityName(nameStart, nameLen)) { + addend = refTokenLen + 1; + } else { + /* Undefined entity: expands to nothing, so only the token. */ + addend = refTokenLen; + } + } else if (refEntity->beingAnalyzed) { + /* Cycle: refEntity is already on the depth-first search stack, so its + expandedSize is not final yet; charge just the token. Do not fail the + parse here: per XML 1.0r4, + "A parsed entity MUST NOT contain a recursive reference to + itself, either directly or indirectly", but also "Only parsed + entities that are referenced directly or indirectly within the + document are required to be well-formed" - so a declared-but- + never-expanded cycle must be accepted; if the cycle is actually + expanded, the runtime recursive-ref check reports + XML_ERROR_RECURSIVE_ENTITY_REF. */ + addend = refTokenLen; + } else if (refEntity->textLen > 0 && refEntity->expandedSize == 0) { + /* Non-empty entity whose expanded size has not been computed yet; + report refEntity as a blocker so the caller can compute its size + first. */ + *posPtr = pos; + if (blockerOut != NULL) + *blockerOut = refEntity; + return ENTITY_SCAN_BLOCKED; + } else if (refEntity->expandedSize > 0) { + /* refEntity is fully resolved: expandedSize holds its final expanded + size (a non-zero value is never a partial here - the only entity that + briefly holds a partial is one currently on the resolve stack, which + is caught by the beingAnalyzed branch above). Add the token plus that + expansion. */ + if (refEntity->expandedSize > (XmlBigCount)(-1) - refTokenLen) + return ENTITY_SCAN_OVERFLOW; + addend = refTokenLen + refEntity->expandedSize; + } else { + /* Empty entity (textLen == 0): expands to nothing, so only the token. */ + addend = refTokenLen; + } + + // Detect and avoid integer overflow + if (*expandedSize > (XmlBigCount)(-1) - addend) + return ENTITY_SCAN_OVERFLOW; + *expandedSize += addend; + pos = nameEnd + 1; + } + + /* Loop only exits here with pos == end (a complete scan). */ + assert(pos == end); + *posPtr = end; + return ENTITY_SCAN_COMPLETE; +} + +/* Resolve expandedSize using an iterative depth-first search over the + reference graph. Each entity is scanned at most twice (once on push, + once after its blocker resolves), so the total work is O(N + T) where + N is the number of entities and T is the sum of all entities' textLen. + `entity->beingAnalyzed` marks the entities currently on the stack for + cycle detection; it is cleared again on pop. + Returns XML_ERROR_NO_MEMORY if the stack cannot be allocated, and + XML_ERROR_AMPLIFICATION_LIMIT_BREACH if an entity's expanded size + overflows XmlBigCount. */ +static enum XML_Error +resolveEntityClosureExpandedSize(XML_Parser parser, HASH_TABLE *table, + ENTITY *root) { + if (root == NULL || root->expandedSize != 0 || root->textPtr == NULL + || root->textLen == 0) + return XML_ERROR_NONE; + + if (table->used == 0) + return XML_ERROR_NONE; + + // Detect and avoid integer overflow + if (table->used > SIZE_MAX / sizeof(ENTITY_ANALYSIS_TASK)) + return XML_ERROR_NO_MEMORY; + + ENTITY_ANALYSIS_TASK *stack + = MALLOC(parser, sizeof(ENTITY_ANALYSIS_TASK) * table->used); + if (stack == NULL) + return XML_ERROR_NO_MEMORY; + + const XML_Parser rootParser = getRootParserOf(parser, NULL); + assert(! rootParser->m_parentParser); + + size_t top = 0; + stack[top].entity = root; + root->beingAnalyzed = XML_TRUE; + top++; + rootParser->m_entity_stats.currentDepth++; + if (rootParser->m_entity_stats.currentDepth + > rootParser->m_entity_stats.maximumDepthSeen) { + rootParser->m_entity_stats.maximumDepthSeen++; + } + + while (top > 0) { + ENTITY_ANALYSIS_TASK *task = &stack[top - 1]; + ENTITY *e = task->entity; + XmlBigCount sizeSoFar = e->expandedSize; + ENTITY *blocker = NULL; + + entityTrackingOnScanStart(parser, e, __LINE__); + + const enum EntityScanResult scanResult + = scanEntityText(parser, e, &e->scanPtr, &sizeSoFar, &blocker); + switch (scanResult) { + case ENTITY_SCAN_COMPLETE: + case ENTITY_SCAN_BLOCKED: + /* Save the size accumulated so far; together with task->pos it is + the state that lets the scan resume once the blocker's size has + been computed. This is the one place where expandedSize briefly + holds neither 0 nor the final size - only while the entity is + on the stack, where the beingAnalyzed check in scanEntityText + keeps every other reader away from the intermediate value. */ + e->expandedSize = sizeSoFar; + break; + default: + break; + } + + entityTrackingOnScanStop(parser, e, __LINE__); + + switch (scanResult) { + case ENTITY_SCAN_OVERFLOW: + /* Abort parsing right away; the beingAnalyzed marks still set on + the stacked entities do not matter once the parse is dead. */ + FREE(parser, stack); + /* expandedSize overflowed; pass SIZE_MAX (unrepresentable size). */ + accountingOnAbort(parser, SIZE_MAX, __LINE__); + return XML_ERROR_AMPLIFICATION_LIMIT_BREACH; + + case ENTITY_SCAN_COMPLETE: + e->beingAnalyzed = XML_FALSE; + top--; + rootParser->m_entity_stats.currentDepth--; + break; + + case ENTITY_SCAN_BLOCKED: + /* Push the blocker. A blocker's expanded size is always still + uncomputed and it is never already on the stack, so each entity + is pushed at most once and top <= table->used. */ + assert(! blocker->beingAnalyzed); + assert(top < table->used); + stack[top].entity = blocker; + blocker->beingAnalyzed = XML_TRUE; + top++; + rootParser->m_entity_stats.currentDepth++; + if (rootParser->m_entity_stats.currentDepth + > rootParser->m_entity_stats.maximumDepthSeen) { + rootParser->m_entity_stats.maximumDepthSeen++; + } + break; + } + } + + FREE(parser, stack); + return XML_ERROR_NONE; +} +#endif /* XML_GE == 1 */ + static enum XML_Error processEntity(XML_Parser parser, ENTITY *entity, bool betweenDecl, enum EntityType type) { @@ -6712,6 +7210,37 @@ processEntity(XML_Parser parser, ENTITY *entity, bool betweenDecl, /* LCOV_EXCL_STOP */ } +#if XML_GE == 1 + // Compute entity size assuming immediate access + if (entity->textLen > 0 && entity->expandedSize == 0) { + HASH_TABLE *const table +# ifdef XML_DTD + = entity->is_param ? &parser->m_dtd->paramEntities + : &parser->m_dtd->generalEntities; +# else + = &parser->m_dtd->generalEntities; +# endif + const enum XML_Error result + = resolveEntityClosureExpandedSize(parser, table, entity); + if (result != XML_ERROR_NONE) + return result; + } + + /* Reject an amplification attack before acquiring openEntity, so a rejected + entity leaks nothing and leaves entity state untouched. */ + if (entity->expandedSize > 0) { + const XML_Parser rootParser = getRootParserOf(parser, NULL); + /* Overflow of expandedSize aborts parsing during resolution + (on first expansion), so the stored value here is always exact. */ + const XmlBigCount expandedBytes = entity->expandedSize; + if (accountingCurrentAmplificationTolerated(rootParser, expandedBytes) + != XML_TRUE) { + accountingOnAbort(rootParser, expandedBytes, __LINE__); + return XML_ERROR_AMPLIFICATION_LIMIT_BREACH; + } + } +#endif /* XML_GE == 1 */ + if (*freeEntityList) { openEntity = *freeEntityList; *freeEntityList = openEntity->next; @@ -6724,7 +7253,7 @@ processEntity(XML_Parser parser, ENTITY *entity, bool betweenDecl, entity->hasMore = true; #if XML_GE == 1 entityTrackingOnOpen(parser, entity, __LINE__); -#endif +#endif /* XML_GE == 1 */ entity->processed = 0; openEntity->next = *openEntityList; *openEntityList = openEntity; @@ -6786,7 +7315,7 @@ internalEntityProcessor(XML_Parser parser, const char *s, const char *end, && (parser->m_parsingStatus.parsing == XML_SUSPENDED || (parser->m_parsingStatus.parsing == XML_PARSING && parser->m_reenter))) { - entity->processed = (int)(next - (const char *)entity->textPtr); + entity->processed = next - (const char *)entity->textPtr; return result; } @@ -6835,7 +7364,7 @@ errorProcessor(XML_Parser parser, const char *s, const char *end, } static enum XML_Error -storeAttributeValue(XML_Parser parser, const ENCODING *enc, XML_Bool isCdata, +storeAttributeValue(XML_Parser parser, const ENCODING *enc, bool isCdata, const char *ptr, const char *end, STRING_POOL *pool, enum XML_Account account) { const char *next = ptr; @@ -6867,8 +7396,7 @@ storeAttributeValue(XML_Parser parser, const ENCODING *enc, XML_Bool isCdata, // processed. A XML_SUSPENDED check here is not required as // appendAttributeValue will never suspend the parser. if (nextInEntity < textEnd) { - entity->processed - = (int)(nextInEntity - (const char *)entity->textPtr); + entity->processed = nextInEntity - (const char *)entity->textPtr; continue; } assert(nextInEntity == textEnd); @@ -6913,7 +7441,7 @@ storeAttributeValue(XML_Parser parser, const ENCODING *enc, XML_Bool isCdata, } static enum XML_Error -appendAttributeValue(XML_Parser parser, const ENCODING *enc, XML_Bool isCdata, +appendAttributeValue(XML_Parser parser, const ENCODING *enc, bool isCdata, const char *ptr, const char *end, STRING_POOL *pool, enum XML_Account account, const char **nextPtr) { DTD *const dtd = parser->m_dtd; /* save one level of indirection */ @@ -6927,7 +7455,7 @@ appendAttributeValue(XML_Parser parser, const ENCODING *enc, XML_Bool isCdata, int tok = XmlAttributeValueTok(enc, ptr, end, &next); #if XML_GE == 1 if (! accountingDiffTolerated(parser, tok, ptr, next, __LINE__, account)) { - accountingOnAbort(parser); + accountingOnAbort(parser, 0, __LINE__); return XML_ERROR_AMPLIFICATION_LIMIT_BREACH; } #endif @@ -7143,7 +7671,7 @@ storeEntityValue(XML_Parser parser, const ENCODING *enc, if (! accountingDiffTolerated(parser, tok, entityTextPtr, next, __LINE__, account)) { - accountingOnAbort(parser); + accountingOnAbort(parser, 0, __LINE__); result = XML_ERROR_AMPLIFICATION_LIMIT_BREACH; goto endEntityValue; } @@ -7328,8 +7856,7 @@ callStoreEntityValue(XML_Parser parser, const ENCODING *enc, // processed. A XML_SUSPENDED check here is not required as // appendAttributeValue will never suspend the parser. if (textEnd != nextInEntity) { - entity->processed - = (int)(nextInEntity - (const char *)entity->textPtr); + entity->processed = nextInEntity - (const char *)entity->textPtr; continue; } @@ -7389,8 +7916,9 @@ storeSelfEntityValue(XML_Parser parser, ENTITY *entity) { poolDiscard(pool); return XML_ERROR_NO_MEMORY; } - entity->textPtr = poolStart(pool); - entity->textLen = (int)(poolLength(pool)); + const XML_Char *const valueStart = poolStart(pool); + entity->textPtr = valueStart; + entity->textLen = poolLength(pool); poolFinish(pool); return XML_ERROR_NONE; @@ -7523,8 +8051,8 @@ reportDefault(XML_Parser parser, const ENCODING *enc, const char *s, } static int -defineAttribute(ELEMENT_TYPE *type, ATTRIBUTE_ID *attId, XML_Bool isCdata, - XML_Bool isId, const XML_Char *value, XML_Parser parser) { +defineAttribute(ELEMENT_TYPE *type, ATTRIBUTE_ID *attId, bool isCdata, + bool isId, const XML_Char *value, XML_Parser parser) { DEFAULT_ATTRIBUTE *att; if (value || isId) { /* The handling of default attributes gets messed up if we have @@ -7685,7 +8213,7 @@ static const XML_Char * getContext(XML_Parser parser) { DTD *const dtd = parser->m_dtd; /* save one level of indirection */ HASH_TABLE_ITER iter; - XML_Bool needSep = XML_FALSE; + bool needSep = false; if (dtd->defaultPrefix.binding) { if (! poolAppendChar(&parser->m_tempPool, XML_T(ASCII_EQUALS))) @@ -7716,7 +8244,7 @@ getContext(XML_Parser parser) { */ return NULL; /* LCOV_EXCL_LINE */ } - needSep = XML_TRUE; + needSep = true; } hashTableIterInit(&iter, &(dtd->prefixes)); @@ -7745,7 +8273,7 @@ getContext(XML_Parser parser) { len--; if (! poolAppendChars(&parser->m_tempPool, prefix->binding->uri, len)) return NULL; - needSep = XML_TRUE; + needSep = true; } hashTableIterInit(&iter, &(dtd->generalEntities)); @@ -7759,7 +8287,7 @@ getContext(XML_Parser parser) { return NULL; if (! poolAppendChars(&parser->m_tempPool, e->name, xcslen(e->name))) return NULL; - needSep = XML_TRUE; + needSep = true; } if (! poolAppendChar(&parser->m_tempPool, XML_T('\0'))) @@ -8167,6 +8695,9 @@ copyEntityTable(XML_Parser oldParser, HASH_TABLE *newTable, return 0; newE->textPtr = tem; newE->textLen = oldE->textLen; +#if XML_GE == 1 + newE->scanPtr = tem; +#endif /* XML_GE == 1 */ } if (oldE->notation) { const XML_Char *tem = poolCopyString(newPool, oldE->notation); @@ -8489,7 +9020,7 @@ poolCopyStringNoFinish(STRING_POOL *pool, const XML_Char *s) { } static const XML_Char * -poolCopyStringN(STRING_POOL *pool, const XML_Char *s, int n) { +poolCopyStringN(STRING_POOL *pool, const XML_Char *s, size_t n) { if (! pool->ptr && ! poolGrow(pool)) { /* The following line is unreachable given the current usage of * poolCopyStringN(). Currently it is called from exactly one @@ -8934,25 +9465,27 @@ copyString(const XML_Char *s, XML_Parser parser) { #if XML_GE == 1 static float -accountingGetCurrentAmplification(XML_Parser rootParser) { +accountingGetCurrentAmplification(XML_Parser rootParser, + XmlBigCount extraIndirectBytes) { // 1.........1.........12 => 22 const size_t lenOfShortestInclude = sizeof("") - 1; - const XmlBigCount countBytesOutput - = rootParser->m_accounting.countBytesDirect - + rootParser->m_accounting.countBytesIndirect; + /* Add as floats: u64 counts (max ~1.8e19) are far below FLT_MAX, so the sum + is always finite and cannot wrap the way an unsigned integer add could. */ + const float indirect = (float)rootParser->m_accounting.countBytesIndirect + + (float)extraIndirectBytes; const float amplificationFactor = rootParser->m_accounting.countBytesDirect - ? ((float)countBytesOutput + ? (((float)rootParser->m_accounting.countBytesDirect + indirect) / (float)(rootParser->m_accounting.countBytesDirect)) - : ((float)(lenOfShortestInclude - + rootParser->m_accounting.countBytesIndirect) + : (((float)lenOfShortestInclude + indirect) / (float)lenOfShortestInclude); assert(! rootParser->m_parentParser); return amplificationFactor; } static void -accountingReportStats(XML_Parser originParser, const char *epilog) { +accountingReportStats(XML_Parser originParser, const char *epilog, + XmlBigCount extraIndirectBytes, int sourceLine) { const XML_Parser rootParser = getRootParserOf(originParser, NULL); assert(! rootParser->m_parentParser); @@ -8960,19 +9493,33 @@ accountingReportStats(XML_Parser originParser, const char *epilog) { return; } + /* On an early abort the amplifying bytes have not been charged to the + indirect counter yet; fold extraIndirectBytes in (with overflow detection) + so the reported byte count and amplification reflect what was detected. */ + const XmlBigCount indirect = rootParser->m_accounting.countBytesIndirect; + const XmlBigCount indirectWithExtra + = (extraIndirectBytes > (XmlBigCount)(-1) - indirect) + ? (XmlBigCount)(-1) + : indirect + extraIndirectBytes; const float amplificationFactor - = accountingGetCurrentAmplification(rootParser); + = accountingGetCurrentAmplification(rootParser, extraIndirectBytes); 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); + indirectWithExtra, (double)amplificationFactor, epilog); + /* An abort has no accountingReportDiff before it, so append the originating + source line here to show which rejection site these stats belong to. */ + if (sourceLine > 0) { + fprintf(stderr, " (xmlparse.c:%d)\n", sourceLine); + } } static void -accountingOnAbort(XML_Parser originParser) { - accountingReportStats(originParser, " ABORTING\n"); +accountingOnAbort(XML_Parser originParser, XmlBigCount extraIndirectBytes, + int sourceLine) { + accountingReportStats(originParser, " ABORTING", extraIndirectBytes, + sourceLine); } static void @@ -9012,6 +9559,32 @@ accountingReportDiff(XML_Parser rootParser, fprintf(stderr, "\"\n"); } +static XML_Bool +accountingCurrentAmplificationTolerated(XML_Parser rootParser, + XmlBigCount extraIndirectBytes) { + assert(! rootParser->m_parentParser); + + const XmlBigCount countBytesOutput + = rootParser->m_accounting.countBytesDirect + + rootParser->m_accounting.countBytesIndirect; + /* Overflow check: extraIndirectBytes can be SIZE_MAX, which would otherwise + wrap the unsigned total around to a small value and wrongly report the + output as tolerated. */ + const XmlBigCount countBytesOutputWithExtra + = (extraIndirectBytes > (XmlBigCount)(-1) - countBytesOutput) + ? (XmlBigCount)(-1) + : countBytesOutput + extraIndirectBytes; + const float amplificationFactor + = accountingGetCurrentAmplification(rootParser, extraIndirectBytes); + const XML_Bool tolerated + = (countBytesOutputWithExtra + < rootParser->m_accounting.activationThresholdBytes) + || (amplificationFactor + <= rootParser->m_accounting.maximumAmplificationFactor); + + return tolerated; +} + static XML_Bool accountingDiffTolerated(XML_Parser originParser, int tok, const char *before, const char *after, int source_line, @@ -9048,18 +9621,11 @@ accountingDiffTolerated(XML_Parser originParser, int tok, const char *before, 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); + = accountingCurrentAmplificationTolerated(rootParser, 0); if (rootParser->m_accounting.debugLevel >= 2u) { - accountingReportStats(rootParser, ""); + accountingReportStats(rootParser, "", 0, 0); accountingReportDiff(rootParser, levelsAwayFromRootParser, before, after, bytesMore, source_line, account); } @@ -9105,14 +9671,21 @@ entityTrackingReportStats(XML_Parser rootParser, ENTITY *entity, = limitingNeeded ? (maxLimitedDepth - /* make space for ellipis */ 2) : candidateIndentDepth; + const ptrdiff_t scannedLength + = EXPAT_SAFE_PTR_DIFF(entity->scanPtr, entity->textPtr); + const char *const confidenceMarker + = ((size_t)scannedLength == entity->textLen) ? "!" : "?"; + fprintf( stderr, - "expat: Entities(%p): Count %9u, depth %2u/%2u %*s%s%s%s; %s length %d (xmlparse.c:%d)\n", + "expat: Entities(%p): Count %9u, depth %2u/%2u %*s%s%s%s; %s scanned " EXPAT_FMT_PTRDIFF_T( + "") "/" EXPAT_FMT_SIZE_T("") ", expanded " EXPAT_FMT_ULL("") "%s (xmlparse.c:%d)\n", (void *)rootParser, rootParser->m_entity_stats.countEverOpened, rootParser->m_entity_stats.currentDepth, rootParser->m_entity_stats.maximumDepthSeen, indentDepth * 2, "", ellipisOrEmpty, entity->is_param ? "%" : "&", entityName, action, - entity->textLen, sourceLine); + scannedLength, entity->textLen, entity->expandedSize, confidenceMarker, + sourceLine); } static void @@ -9127,6 +9700,7 @@ entityTrackingOnOpen(XML_Parser originParser, ENTITY *entity, int sourceLine) { rootParser->m_entity_stats.maximumDepthSeen++; } + // NOTE: The space at the end of "OPEN " is for length alignment with "CLOSE" entityTrackingReportStats(rootParser, entity, "OPEN ", sourceLine); } @@ -9139,6 +9713,25 @@ entityTrackingOnClose(XML_Parser originParser, ENTITY *entity, int sourceLine) { rootParser->m_entity_stats.currentDepth--; } +static void +entityTrackingOnScanStart(XML_Parser originParser, ENTITY *entity, + int sourceLine) { + const XML_Parser rootParser = getRootParserOf(originParser, NULL); + assert(! rootParser->m_parentParser); + + entityTrackingReportStats(rootParser, entity, "START", sourceLine); +} + +static void +entityTrackingOnScanStop(XML_Parser originParser, ENTITY *entity, + int sourceLine) { + const XML_Parser rootParser = getRootParserOf(originParser, NULL); + assert(! rootParser->m_parentParser); + + // NOTE: The space at the end of "STOP " is for length alignment with "START" + entityTrackingReportStats(rootParser, entity, "STOP ", sourceLine); +} + #endif /* XML_GE == 1 */ static XML_Parser diff --git a/Modules/expat/xmltok.h b/Modules/expat/xmltok.h index bf641af6d4f77f0..973f1fe2ef21814 100644 --- a/Modules/expat/xmltok.h +++ b/Modules/expat/xmltok.h @@ -176,8 +176,8 @@ struct encoding { const char *); int (*nameLength)(const ENCODING *, const char *); const char *(*skipS)(const ENCODING *, const char *); - int (*getAtts)(const ENCODING *enc, const char *ptr, int attsMax, - ATTRIBUTE *atts); + size_t (*getAtts)(const ENCODING *enc, const char *ptr, size_t attsMax, + ATTRIBUTE *atts); int (*charRefNumber)(const ENCODING *enc, const char *ptr); int (*predefinedEntityName)(const ENCODING *, const char *, const char *); void (*updatePosition)(const ENCODING *, const char *ptr, const char *end, diff --git a/Modules/expat/xmltok_impl.c b/Modules/expat/xmltok_impl.c index 7900e94701e2768..b2cb31b01c01213 100644 --- a/Modules/expat/xmltok_impl.c +++ b/Modules/expat/xmltok_impl.c @@ -1505,11 +1505,11 @@ PREFIX(isPublicId)(const ENCODING *enc, const char *ptr, const char *end, first attsMax attributes are stored in atts. */ -static int -PREFIX(getAtts)(const ENCODING *enc, const char *ptr, int attsMax, +static size_t +PREFIX(getAtts)(const ENCODING *enc, const char *ptr, size_t attsMax, ATTRIBUTE *atts) { enum { other, inName, inValue } state = inName; - int nAtts = 0; + size_t nAtts = 0; int open = 0; /* defined when state == inValue; initialization just to shut up compilers */ diff --git a/PCbuild/_elementtree.vcxproj b/PCbuild/_elementtree.vcxproj index cd8c1ff1731dfb1..418954cd972675d 100644 --- a/PCbuild/_elementtree.vcxproj +++ b/PCbuild/_elementtree.vcxproj @@ -100,6 +100,7 @@ + diff --git a/PCbuild/_elementtree.vcxproj.filters b/PCbuild/_elementtree.vcxproj.filters index 2ce694e1b5390fe..ead096bbd03ca5d 100644 --- a/PCbuild/_elementtree.vcxproj.filters +++ b/PCbuild/_elementtree.vcxproj.filters @@ -24,6 +24,9 @@ Header Files\expat + + Header Files\expat + Header Files\expat diff --git a/PCbuild/pyexpat.vcxproj b/PCbuild/pyexpat.vcxproj index ae9ad59197b5915..15639930758da6b 100644 --- a/PCbuild/pyexpat.vcxproj +++ b/PCbuild/pyexpat.vcxproj @@ -95,6 +95,7 @@ + diff --git a/PCbuild/pyexpat.vcxproj.filters b/PCbuild/pyexpat.vcxproj.filters index baa06e4491f6850..096e4ad38895469 100644 --- a/PCbuild/pyexpat.vcxproj.filters +++ b/PCbuild/pyexpat.vcxproj.filters @@ -12,6 +12,9 @@ + + Header Files + Header Files