From c7d69ca874c393ae51a0f4f5b92a6e6baeac289c Mon Sep 17 00:00:00 2001 From: eabdullin Date: Tue, 22 Apr 2025 07:43:01 +0000 Subject: [PATCH] Import from CS git --- .gitignore | 2 +- ...ity-Expansion-Depth-in-libexpat-CVE-.patch | 473 ++++++++++++++++++ ...issing-files-for-the-benchmark-tests.patch | 286 +++++++++++ SPECS/xmlrpc-c.spec | 7 +- 4 files changed, 766 insertions(+), 2 deletions(-) create mode 100644 SOURCES/0009-Restrict-XML-Entity-Expansion-Depth-in-libexpat-CVE-.patch create mode 100644 SOURCES/0010-Add-missing-files-for-the-benchmark-tests.patch diff --git a/.gitignore b/.gitignore index 75d8751..ecc1b3c 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,2 @@ SOURCES/benchmark-tests.tar.xz -SOURCES/xmlrpc-c-1.51.0.tar.xz \ No newline at end of file +SOURCES/xmlrpc-c-1.51.0.tar.xz diff --git a/SOURCES/0009-Restrict-XML-Entity-Expansion-Depth-in-libexpat-CVE-.patch b/SOURCES/0009-Restrict-XML-Entity-Expansion-Depth-in-libexpat-CVE-.patch new file mode 100644 index 0000000..cb38c4b --- /dev/null +++ b/SOURCES/0009-Restrict-XML-Entity-Expansion-Depth-in-libexpat-CVE-.patch @@ -0,0 +1,473 @@ +From 6d8259c43c7d675987d96bf990b6b57402d27f28 Mon Sep 17 00:00:00 2001 +From: root +Date: Wed, 19 Mar 2025 12:52:04 -0400 +Subject: [PATCH 09/10] Restrict XML Entity Expansion Depth in libexpat + CVE-2024-8176 + +The embedded libexpat library is vulnerable to a stack overflow +due to uncontrolled recursion when processing deeply nested XML +entities. This can cause the application to crash, resulting in +a denial of service (DoS) or potentially leading to memory +corruption, depending on the user's environment and how the +library is used. The issue is triggered by supplying a specially +crafted XML document designed to create a long chain of recursive +entities. + +libexpat addressed this upstream in +https://github.com/libexpat/libexpat/pull/973 +but the embedded copy within xmlrpc-c is so old there is no chance +of applying this without rebasing it. Instead a recursion counter +is added to the parser to limit the depth. + +Resolves: RHEL-57536 +--- + lib/expat/xmlparse/xmlparse.c | 40 +++++++++++++--- + test/Makefile | 30 ++++++++++-- + test/overflow_attr_test.c | 88 +++++++++++++++++++++++++++++++++++ + test/overflow_entity_test.c | 86 ++++++++++++++++++++++++++++++++++ + 4 files changed, 234 insertions(+), 10 deletions(-) + create mode 100644 test/overflow_attr_test.c + create mode 100644 test/overflow_entity_test.c + +diff --git a/lib/expat/xmlparse/xmlparse.c b/lib/expat/xmlparse/xmlparse.c +index c440ac7..467910a 100644 +--- a/lib/expat/xmlparse/xmlparse.c ++++ b/lib/expat/xmlparse/xmlparse.c +@@ -29,6 +29,8 @@ See the file copying.txt for copying permission. + #include "xmldef.h" + #include "xmlparse.h" + ++#define MAX_RECURSE 28 ++ + static const char * + extractXmlSample(const char * const start, + const char * const end, +@@ -312,6 +314,7 @@ typedef struct { + enum XML_ParamEntityParsing m_paramEntityParsing; + XML_Parser m_parentParser; + unsigned long m_hash_secret_salt; ++ int m_recurse_lock; + } Parser; + + #define userData (((Parser *)parser)->m_userData) +@@ -388,6 +391,7 @@ typedef struct { + #define namespaceSeparator (((Parser *)parser)->m_namespaceSeparator) + #define parentParser (((Parser *)parser)->m_parentParser) + #define hash_secret_salt (((Parser *)parser)->m_hash_secret_salt) ++#define recurse_lock (((Parser *)parser)->m_recurse_lock) + + + +@@ -515,7 +519,7 @@ poolGrow(STRING_POOL * const poolP, + size_t const newSize = + offsetof(BLOCK, s) + blockSize * sizeof(XML_Char); + +- BLOCK * const newBlocksP = malloc(newSize); ++ BLOCK * const newBlocksP = calloc(offsetof(BLOCK, s) + blockSize, sizeof(XML_Char)); + + if (newBlocksP) { + newBlocksP->size = blockSize; +@@ -1143,6 +1147,7 @@ const XML_Char *getContext(XML_Parser parser) + { + HASH_TABLE_ITER iter; + int needSep = 0; ++ Parser * const parserP = (Parser *) parser; + + if (dtd.defaultPrefix.binding) { + int i; +@@ -1191,7 +1196,7 @@ const XML_Char *getContext(XML_Parser parser) + ENTITY *e = (ENTITY *)hashTableIterNext(&iter); + if (!e) + break; +- if (!e->open) ++ if (!e->open || parserP->m_recurse_lock > MAX_RECURSE) + continue; + if (needSep && !poolAppendChar(&tempPool, CONTEXT_SEP)) + return 0; +@@ -1210,6 +1215,7 @@ static + int setContext(XML_Parser parser, const XML_Char *context) + { + const XML_Char *s = context; ++ Parser * const parserP = (Parser *) parser; + + while (*context != XML_T('\0')) { + if (*s == CONTEXT_SEP || *s == XML_T('\0')) { +@@ -1218,8 +1224,9 @@ int setContext(XML_Parser parser, const XML_Char *context) + return 0; + e = (ENTITY *)lookup(parser, &dtd.generalEntities, poolStart(&tempPool), + 0); +- if (e) ++ if (e) { + e->open = 1; ++ } + if (*s != XML_T('\0')) + s++; + context = s; +@@ -1860,7 +1867,7 @@ appendAttributeValue(XML_Parser const xmlParserP, + return XML_ERROR_UNDEFINED_ENTITY; + } + } +- else if (entity->open) { ++ else if (entity->open || parser->m_recurse_lock > MAX_RECURSE) { + if (enc == parser->m_encoding) + eventPtr = ptr; + return XML_ERROR_RECURSIVE_ENTITY_REF; +@@ -1879,10 +1886,12 @@ appendAttributeValue(XML_Parser const xmlParserP, + enum XML_Error result; + const XML_Char *textEnd = entity->textPtr + entity->textLen; + entity->open = 1; ++ parser->m_recurse_lock++; + result = appendAttributeValue(xmlParserP, internalEncoding, + isCdata, (char *)entity->textPtr, + (char *)textEnd, pool); + entity->open = 0; ++ parser->m_recurse_lock--; + if (result) + return result; + } +@@ -1952,7 +1961,7 @@ storeEntityValue(XML_Parser const xmlParserP, + eventPtr = entityTextPtr; + return XML_ERROR_UNDEFINED_ENTITY; + } +- if (entity->open) { ++ if (entity->open || parser->m_recurse_lock > MAX_RECURSE) { + if (enc == parser->m_encoding) + eventPtr = entityTextPtr; + return XML_ERROR_RECURSIVE_ENTITY_REF; +@@ -2641,8 +2650,13 @@ doInternalEntityRef(XML_Parser const xmlParserP, + *errorP = NULL; + } else { + OPEN_INTERNAL_ENTITY openEntity; ++ if (entityP->open || parserP->m_recurse_lock > MAX_RECURSE) { ++ *errorCodeP = XML_ERROR_RECURSIVE_ENTITY_REF; ++ return; ++ } + + entityP->open = 1; /* recursion control */ ++ parserP->m_recurse_lock++; + + openEntity.next = parserP->m_openInternalEntities; + +@@ -2659,6 +2673,7 @@ doInternalEntityRef(XML_Parser const xmlParserP, + 0, errorCodeP, errorP); + + entityP->open = 0; /* recursion control */ ++ parserP->m_recurse_lock--; + parserP->m_openInternalEntities = openEntity.next; + } + } +@@ -2681,8 +2696,14 @@ doExternalEntityRef(XML_Parser const xmlParserP, + if (parserP->m_externalEntityRefHandler) { + const XML_Char * contextP; + ++ if (entityP->open || parserP->m_recurse_lock > MAX_RECURSE) { ++ *errorCodeP = XML_ERROR_RECURSIVE_ENTITY_REF; ++ return; ++ } + entityP->open = 1; ++ parserP->m_recurse_lock++; + contextP = getContext(xmlParserP); ++ parserP->m_recurse_lock--; + entityP->open = 0; + + if (!contextP) +@@ -2753,7 +2774,7 @@ doEntityRef(XML_Parser const xmlParserP, + *errorCodeP = XML_ERROR_NONE; + } + } else { +- if (entityP->open) ++ if (entityP->open || parserP->m_recurse_lock > MAX_RECURSE) + *errorCodeP = XML_ERROR_RECURSIVE_ENTITY_REF; + else if (entityP->notation) + *errorCodeP = XML_ERROR_BINARY_ENTITY_REF; +@@ -4059,7 +4080,7 @@ doProlog(XML_Parser const xmlParserP, + *errorCodeP = XML_ERROR_UNDEFINED_ENTITY; + return; + } +- if (entity->open) { ++ if (entity->open || parser->m_recurse_lock > MAX_RECURSE) { + *errorCodeP = XML_ERROR_RECURSIVE_ENTITY_REF; + return; + } +@@ -4076,16 +4097,19 @@ doProlog(XML_Parser const xmlParserP, + if (externalEntityRefHandler) { + dtd.complete = 0; + entity->open = 1; ++ parser->m_recurse_lock++; + if (!externalEntityRefHandler(externalEntityRefHandlerArg, + 0, + entity->base, + entity->systemId, + entity->publicId)) { + entity->open = 0; ++ parser->m_recurse_lock--; + *errorCodeP = XML_ERROR_EXTERNAL_ENTITY_HANDLING; + return; + } + entity->open = 0; ++ parser->m_recurse_lock--; + if (dtd.complete) + break; + } +@@ -4199,6 +4223,7 @@ processInternalParamEntity(XML_Parser const parser, + const char *s, *end, *next; + int tok; + OPEN_INTERNAL_ENTITY openEntity; ++ Parser * const parserP = (Parser *) parser; + + entity->open = 1; + openEntity.next = openInternalEntities; +@@ -4292,6 +4317,7 @@ xmlrpc_XML_ParserCreate(const XML_Char * const encodingName) { + parser->m_paramEntityParsing = XML_PARAM_ENTITY_PARSING_NEVER; + parser->m_hash_secret_salt = 0; + parser->m_ns = 0; ++ parser->m_recurse_lock = 0; + poolInit(&parser->m_tempPool); + poolInit(&parser->m_temp2Pool); + parser->m_protocolEncodingName = +diff --git a/test/Makefile b/test/Makefile +index 1242910..59c698c 100644 +--- a/test/Makefile ++++ b/test/Makefile +@@ -16,9 +16,9 @@ LDADD_CGI_SERVER = \ + + default: all + +-INCLUDES = -I$(BLDDIR) -Isrcdir/include -Isrcdir/lib/util/include \ ++INCLUDES = -I$(BLDDIR) -I$(SRCDIR)/include -I$(SRCDIR)/lib/util/include \ + +-PROGS = test cgitest1 ++PROGS = test cgitest1 overflow_attr_test overflow_entity_test + + all: $(PROGS) $(SUBDIRS:%=%/all) + +@@ -86,6 +86,28 @@ OBJS = $(TEST_OBJS) cgitest1.o + $(OBJS):%.o:%.c + $(CC) -c $(INCLUDES) $(CFLAGS_ALL) $< + ++OVERFLOW_ATTR_OBJS = overflow_attr_test.o ++ ++overflow_attr_test: $(OVERFLOW_ATTR_OBJS) $(LIBXMLRPC_SERVER_A) \ ++ $(LIBXMLRPC_A) $(LIBXMLRPC_UTIL_A) $(LIBXMLRPC_XML) ++ $(CCLD) -o $@ $(LDFLAGS_ALL) \ ++ $(OVERFLOW_ATTR_OBJS) $(LDADD_CLIENT) $(LDADD_ABYSS_SERVER) $(CASPRINTF) ++OBJS = overflow_attr_test.o ++ ++$(OBJS):%.o:%.c ++ $(CC) -c $(INCLUDES) -I$(SRCDIR)/lib/expat/xmlparse/ $(CFLAGS_ALL) $< ++ ++OVERFLOW_ENTITY_OBJS = overflow_entity_test.o ++ ++overflow_entity_test: $(OVERFLOW_ENTITY_OBJS) $(LIBXMLRPC_SERVER_A) \ ++ $(LIBXMLRPC_A) $(LIBXMLRPC_UTIL_A) $(LIBXMLRPC_XML) ++ $(CCLD) -o $@ $(LDFLAGS_ALL) \ ++ $(OVERFLOW_ENTITY_OBJS) $(LDADD_CLIENT) $(LDADD_ABYSS_SERVER) $(CASPRINTF) ++OBJS = overflow_entity_test.o ++ ++$(OBJS):%.o:%.c ++ $(CC) -c $(INCLUDES) -I$(SRCDIR)/lib/expat/xmlparse/ $(CFLAGS_ALL) $< ++ + # Note the difference between 'check' and 'runtests'. 'check' means to check + # our own correctness. 'runtests' means to run the tests that check our + # parent's correctness +@@ -94,8 +116,10 @@ $(OBJS):%.o:%.c + check: + + .PHONY: runtests_local +-runtests_local: test cgitest1 ++runtests_local: test cgitest1 overflow_attr_test overflow_entity_test + ./test ++ ./overflow_attr_test ++ ./overflow_entity_test + + .PHONY: runtests + runtests: runtests_local cpp/runtests benchmark/runtests +diff --git a/test/overflow_attr_test.c b/test/overflow_attr_test.c +new file mode 100644 +index 0000000..eb06556 +--- /dev/null ++++ b/test/overflow_attr_test.c +@@ -0,0 +1,88 @@ ++/* gcc -g -std=c11 -D_POSIX_C_SOURCE=200809L -I lib/expat/xmlparse -lxmlrpc_xmlparse -lxmlrpc_xmltok overflow_attr_test.c -o overflow_attr_test */ ++ ++#include ++#include ++#include ++#include ++#include ++#include "xmlparse.h" ++#include ++ ++#define XML_STATUS_OK 1 ++#define XML_STATUS_ERROR 0 ++ ++typedef unsigned char XML_Bool; ++#define XML_TRUE ((XML_Bool)1) ++#define XML_FALSE ((XML_Bool)0) ++ ++#define SIZE_PER_LINE 50 ++ ++static int ++doParse(int N_LINES) { ++ XML_Parser parser; ++ char tmp[SIZE_PER_LINE]; ++ int ret = 0; ++ int rval = 0; ++ ++ if ((parser = xmlrpc_XML_ParserCreate(NULL)) == NULL) ++ errx(1, "XML_ParserCreate"); ++ ++ char *const text = (char *)malloc((N_LINES + 4) * SIZE_PER_LINE); ++ if (text == NULL) { ++ printf("malloc failed"); ++ return 1; ++ } ++ ++ // Create the XML ++ snprintf(text, SIZE_PER_LINE, ++ "\n"); ++ ++ for (size_t i = 1; i < N_LINES; ++i) { ++ snprintf(tmp, SIZE_PER_LINE, " \n", ++ (long unsigned)i, (long unsigned)(i - 1)); ++ strncat(text, tmp, SIZE_PER_LINE); ++ } ++ ++ snprintf(tmp, SIZE_PER_LINE, "]> mainText\n", ++ (long unsigned)(N_LINES - 1)); ++ strncat(text, tmp, SIZE_PER_LINE); ++ ++ ret = xmlrpc_XML_Parse(parser, text, strlen(text), XML_TRUE); ++ if (ret == XML_STATUS_ERROR) { ++ int err = xmlrpc_XML_GetErrorCode(parser); ++ if (err == XML_ERROR_RECURSIVE_ENTITY_REF) { ++ if (N_LINES <= 28) { ++ printf("%d Unexpected FAIL (%s)\n", N_LINES, xmlrpc_XML_ErrorString(err)); ++ } else { ++ printf("%d expected failure OK (%s)\n", N_LINES, xmlrpc_XML_ErrorString(err)); ++ } ++ } else { ++ printf("%d Unexpected FAIL (%s)\n", N_LINES, xmlrpc_XML_ErrorString(err)); ++ rval = 1; ++ } ++ } else { ++ printf("%d OK\n", N_LINES); ++ } ++ free(text); ++ xmlrpc_XML_ParserFree(parser); ++ return rval; ++} ++ ++int main(int argc, char *argv[]) { ++ int status = 0; ++ ++ for (int i = 2; i <= 28; i++ ) ++ status += doParse(i); ++ ++ status += doParse(29); ++ status += doParse(30); ++ status += doParse(100); ++ status += doParse(1000); ++ status += doParse(10000); ++ status += doParse(60000); ++ ++ if (status == 0) ++ printf("PASSED\n"); ++ return (status != 0); ++} +diff --git a/test/overflow_entity_test.c b/test/overflow_entity_test.c +new file mode 100644 +index 0000000..d0d1290 +--- /dev/null ++++ b/test/overflow_entity_test.c +@@ -0,0 +1,86 @@ ++/* gcc -g -std=c11 -D_POSIX_C_SOURCE=200809L -I lib/expat/xmlparse -lxmlrpc_xmlparse -lxmlrpc_xmltok overflow_entity_test.c -o overflow_entity_test */ ++ ++#include ++#include ++#include ++#include ++#include ++#include "xmlparse.h" ++#include ++ ++#define XML_STATUS_OK 1 ++#define XML_STATUS_ERROR 0 ++ ++typedef unsigned char XML_Bool; ++#define XML_TRUE ((XML_Bool)1) ++#define XML_FALSE ((XML_Bool)0) ++ ++#define SIZE_PER_LINE 50 ++ ++static int ++doParse(int N_LINES) { ++ XML_Parser parser; ++ char tmp[SIZE_PER_LINE]; ++ int ret = 0; ++ int rval = 0; ++ ++ if ((parser = xmlrpc_XML_ParserCreate(NULL)) == NULL) ++ errx(1, "XML_ParserCreate"); ++ ++ char *const text = (char *)malloc((N_LINES + 4) * SIZE_PER_LINE); ++ if (text == NULL) { ++ printf("malloc failed"); ++ return 1; ++ } ++ ++ // Create the XML ++ snprintf(text, SIZE_PER_LINE, ++ "\n"); ++ ++ for (size_t i = 1; i < N_LINES; ++i) { ++ snprintf(tmp, SIZE_PER_LINE, " \n", ++ (long unsigned)i, (long unsigned)(i - 1)); ++ strncat(text, tmp, SIZE_PER_LINE); ++ } ++ ++ snprintf(tmp, SIZE_PER_LINE, "]> &s%lu;\n", ++ (long unsigned)(N_LINES - 1)); ++ strncat(text, tmp, SIZE_PER_LINE); ++ ++ ret = xmlrpc_XML_Parse(parser, text, strlen(text), XML_TRUE); ++ if (ret == XML_STATUS_ERROR){ ++ int err = xmlrpc_XML_GetErrorCode(parser); ++ if (err == XML_ERROR_RECURSIVE_ENTITY_REF) { ++ if (N_LINES <= 28) { ++ printf("%d Unexpected FAIL (%s)\n", N_LINES, xmlrpc_XML_ErrorString(err)); ++ rval = 1; ++ } else { ++ printf("%d expected failure OK (%s)\n", N_LINES, xmlrpc_XML_ErrorString(err)); ++ } ++ } ++ } else { ++ printf("%d OK\n", N_LINES); ++ } ++ free(text); ++ xmlrpc_XML_ParserFree(parser); ++ return rval; ++} ++ ++int main(int argc, char *argv[]) { ++ int status = 0; ++ ++ for (int i = 2; i <= 28; i++ ) ++ status += doParse(i); ++ ++ status += doParse(29); ++ status += doParse(30); ++ status += doParse(100); ++ status += doParse(1000); ++ status += doParse(10000); ++ status += doParse(60000); ++ ++ if (status == 0) ++ printf("PASSED\n"); ++ return (status != 0); ++} +-- +2.43.5 + diff --git a/SOURCES/0010-Add-missing-files-for-the-benchmark-tests.patch b/SOURCES/0010-Add-missing-files-for-the-benchmark-tests.patch new file mode 100644 index 0000000..8056bb3 --- /dev/null +++ b/SOURCES/0010-Add-missing-files-for-the-benchmark-tests.patch @@ -0,0 +1,286 @@ +From 0f3661572b50df9878412181dc600e69eafbd868 Mon Sep 17 00:00:00 2001 +From: root +Date: Wed, 19 Mar 2025 13:54:48 -0400 +Subject: [PATCH] Add missing files for the benchmark tests + +The SOURCES tarball needs to be manually unpacked. +--- + test/benchmark/Makefile | 82 ++++++++++++++++++++ + test/benchmark/README.txt | 16 ++++ + test/benchmark/benchmark.c | 152 +++++++++++++++++++++++++++++++++++++ + 3 files changed, 250 insertions(+) + create mode 100644 test/benchmark/Makefile + create mode 100644 test/benchmark/README.txt + create mode 100644 test/benchmark/benchmark.c + +diff --git a/test/benchmark/Makefile b/test/benchmark/Makefile +new file mode 100644 +index 0000000..20e648c +--- /dev/null ++++ b/test/benchmark/Makefile +@@ -0,0 +1,82 @@ ++ifeq ($(SRCDIR),) ++ updir = $(shell echo $(dir $(1)) | sed 's/.$$//') ++ testDIR := $(call updir,$(CURDIR)) ++ SRCDIR := $(call updir,$(testDIR)) ++ BLDDIR := $(SRCDIR) ++endif ++SUBDIR := test/benchmark ++ ++include $(BLDDIR)/config.mk ++ ++PROGS = benchmark ++ ++default: all ++ ++all: $(PROGS) ++ ++XMLRPC_C_CONFIG = $(BLDDIR)/xmlrpc-c-config.test ++ ++LIBS := ++ ++LIBS += $(SOCKETLIBOPT) $(THREAD_LIBS) ++ ++INCLUDES = -Isrcdir/include -I$(BLDDIR) -Isrcdir -Isrcdir/lib/util/include -Isrcdir/lib/expat/xmlparse ++ ++# This 'common.mk' dependency makes sure the symlinks get built before ++# this make file is used for anything. ++ ++$(SRCDIR)/common.mk: srcdir blddir ++ ++include $(SRCDIR)/common.mk ++ ++CFLAGS += $(INCLUDES) ++ ++TEST_OBJS = benchmark.o ++ ++#----------------------------------------------------------------------------- ++# TEST_LIBS ++#----------------------------------------------------------------------------- ++TEST_LIBS = ++TEST_LIBS += $(LIBXMLRPC_UTIL_A) ++TEST_LIBS += $(LIBXMLRPC_XMLPARSE_A) ++TEST_LIBS += $(LIBXMLRPC_XMLTOK_A) ++ ++benchmark: $(TEST_OBJS) $(TEST_LIBS) ++ $(CXXLD) -o $@ $(INCLUDES) $(LDFLAGS_ALL) $^ $(LIB_XML) $(TEST_LIBS) ++ ++%.o:%.cpp ++ $(CC) -c $(INCLUDES) $(CXXFLAGS_ALL) $(D_INTERNAL_EXPAT) $< ++ ++# Note the difference between 'check' and 'runtests'. 'check' means to check ++# our own correctness. 'runtests' means to run the tests that check our ++# parent's correctness ++ ++.PHONY: check ++check: ++ ++.PHONY: runtests ++runtests: benchmark ++ ./benchmark aaaaaa_attr.xml 4096 1 ++ ./benchmark aaaaaa_comment.xml 4096 1 ++ ./benchmark aaaaaa_tag.xml 4096 1 ++ ./benchmark aaaaaa_text.xml 4096 1 ++ -./benchmark aaaaaa_cdata.xml 4096 1 ++ echo "PASSED" ++ ++.PHONY: install ++install: ++ ++.PHONY: uninstall ++uninstall: ++ ++.PHONY: clean clean-local distclean ++clean: clean-common clean-local ++clean-local: ++ rm -f $(PROGS) ++ ++distclean: clean distclean-common ++ ++.PHONY: dep ++dep: dep-common ++ ++include depend.mk +diff --git a/test/benchmark/README.txt b/test/benchmark/README.txt +new file mode 100644 +index 0000000..86414d5 +--- /dev/null ++++ b/test/benchmark/README.txt +@@ -0,0 +1,16 @@ ++Use this benchmark command line utility as follows: ++ ++ benchmark [-n] <# iterations> ++ ++The command line arguments are: ++ ++ -n ... optional; if supplied, namespace processing is turned on ++ ... name/path of test xml file ++ ... size of processing buffer; ++ the file is parsed in chunks of this size ++ <# iterations> ... how often will the file be parsed ++ ++Returns: ++ ++ The time (in seconds) it takes to parse the test file, ++ averaged over the number of iterations.@ +diff --git a/test/benchmark/benchmark.c b/test/benchmark/benchmark.c +new file mode 100644 +index 0000000..3864280 +--- /dev/null ++++ b/test/benchmark/benchmark.c +@@ -0,0 +1,152 @@ ++/* ++ __ __ _ ++ ___\ \/ /_ __ __ _| |_ ++ / _ \\ /| '_ \ / _` | __| ++ | __// \| |_) | (_| | |_ ++ \___/_/\_\ .__/ \__,_|\__| ++ |_| XML parser ++ ++ Copyright (c) 2003-2006 Karl Waclawek ++ Copyright (c) 2005-2007 Steven Solie ++ Copyright (c) 2017 Sebastian Pipping ++ Copyright (c) 2017 Rhodri James ++ 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. ++*/ ++ ++#include ++#include ++#include // ptrdiff_t ++#include ++#include ++#include ++#include "xmlparse.h" ++ ++#ifdef XML_LARGE_SIZE ++# define XML_FMT_INT_MOD "ll" ++#else ++# define XML_FMT_INT_MOD "l" ++#endif ++ ++#ifdef XML_UNICODE_WCHAR_T ++# define XML_FMT_STR "ls" ++#else ++# define XML_FMT_STR "s" ++#endif ++ ++static void ++usage(const char *prog, int rc) { ++ fprintf(stderr, "usage: %s [-n] filename bufferSize nr_of_loops\n", prog); ++ exit(rc); ++} ++ ++int ++main(int argc, char *argv[]) { ++ XML_Parser parser; ++ char *XMLBuf, *XMLBufEnd, *XMLBufPtr; ++ FILE *fd; ++ struct stat fileAttr; ++ int nrOfLoops, bufferSize, i, isFinal; ++ size_t fileSize; ++ int j = 0, ns = 0; ++ clock_t tstart, tend; ++ double cpuTime = 0.0; ++ ++ if (argc > 1) { ++ if (argv[1][0] == '-') { ++ if (argv[1][1] == 'n' && argv[1][2] == '\0') { ++ ns = 1; ++ j = 1; ++ } else ++ usage(argv[0], 1); ++ } ++ } ++ ++ if (argc != j + 4) ++ usage(argv[0], 1); ++ ++ if (stat(argv[j + 1], &fileAttr) != 0) { ++ fprintf(stderr, "could not access file '%s'\n", argv[j + 1]); ++ return 2; ++ } ++ ++ fd = fopen(argv[j + 1], "r"); ++ if (! fd) { ++ fprintf(stderr, "could not open file '%s'\n", argv[j + 1]); ++ exit(2); ++ } ++ ++ bufferSize = atoi(argv[j + 2]); ++ nrOfLoops = atoi(argv[j + 3]); ++ if (bufferSize <= 0 || nrOfLoops <= 0) { ++ fprintf(stderr, "buffer size and nr of loops must be greater than zero.\n"); ++ exit(3); ++ } ++ ++ XMLBuf = malloc(fileAttr.st_size); ++ fileSize = fread(XMLBuf, sizeof(char), fileAttr.st_size, fd); ++ fclose(fd); ++ ++ if (ns) ++ parser = xmlrpc_XML_ParserCreateNS(NULL, '!'); ++ else ++ parser = xmlrpc_XML_ParserCreate(NULL); ++ ++ i = 0; ++ XMLBufEnd = XMLBuf + fileSize; ++ while (i < nrOfLoops) { ++ XMLBufPtr = XMLBuf; ++ isFinal = 0; ++ tstart = clock(); ++ do { ++ ptrdiff_t parseBufferSize = XMLBufEnd - XMLBufPtr; ++ if (parseBufferSize <= (ptrdiff_t)bufferSize) ++ isFinal = 1; ++ else ++ parseBufferSize = bufferSize; ++ assert(parseBufferSize <= (ptrdiff_t)bufferSize); ++ if (! xmlrpc_XML_Parse(parser, XMLBufPtr, (int)parseBufferSize, isFinal)) { ++ fprintf(stderr, ++ "error '%" XML_FMT_STR "' at line %" XML_FMT_INT_MOD ++ "u character %" XML_FMT_INT_MOD "u\n", ++ xmlrpc_XML_ErrorString(xmlrpc_XML_GetErrorCode(parser)), ++ xmlrpc_XML_GetCurrentLineNumber(parser), ++ xmlrpc_XML_GetCurrentColumnNumber(parser)); ++ free(XMLBuf); ++ xmlrpc_XML_ParserFree(parser); ++ exit(4); ++ } ++ XMLBufPtr += bufferSize; ++ } while (! isFinal); ++ tend = clock(); ++ cpuTime += ((double)(tend - tstart)) / CLOCKS_PER_SEC; ++ //xmlrpc-c lacks XML_ParserReset ++ //xmlrpc_XML_ParserReset(parser, NULL); ++ i++; ++ } ++ ++ xmlrpc_XML_ParserFree(parser); ++ free(XMLBuf); ++ ++ printf("%d loops, with buffer size %d. Average time per loop: %f\n", ++ nrOfLoops, bufferSize, cpuTime / (double)nrOfLoops); ++ return 0; ++} +-- +2.43.5 + diff --git a/SPECS/xmlrpc-c.spec b/SPECS/xmlrpc-c.spec index b8a37ab..3878e87 100644 --- a/SPECS/xmlrpc-c.spec +++ b/SPECS/xmlrpc-c.spec @@ -6,7 +6,7 @@ Name: xmlrpc-c Version: 1.51.0 -Release: 10%{?dist} +Release: 11%{?dist} Summary: Lightweight RPC library based on XML and HTTP # See doc/COPYING for details. # The Python 1.5.2 license used by a few files is just BSD. @@ -30,6 +30,8 @@ Patch105: 0005-lib-Prevent-more-integer-overflows-CVE-2022-22822-to.patch Patch106: 0006-Prevent-integer-overflow-on-m_groupSize-in-doProlog-.patch Patch107: 0007-Address-segfault-found-in-CVE-2023-52425.patch Patch108: 0008-Prevent-integer-overflow-or-wraparound-CVE-2024-4549.patch +Patch109: 0009-Restrict-XML-Entity-Expansion-Depth-in-libexpat-CVE-.patch +Patch110: 0010-Add-missing-files-for-the-benchmark-tests.patch # Backported patches # https://sourceforge.net/p/xmlrpc-c/code/2981/ @@ -198,6 +200,9 @@ tar xf %{SOURCE1} %{_bindir}/xmlrpc_dumpserver %changelog +* Wed Mar 19 2025 Rob Crittenden - 1.51.0-11 +- Restrict XML Entity Expansion Depth in libexpat CVE-2024-8176 + * Thu Sep 19 2024 Rob Crittenden - 1.51.0-10 - Prevent integer overflow or wraparound, CVE-2024-4549 (RHEL-57519)