summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorRemi Collet <remi@remirepo.net>2023-08-01 16:08:10 +0200
committerRemi Collet <remi@php.net>2023-08-01 16:08:10 +0200
commit3812d43d43dbfafedd9b4e36f0374a076dcb3278 (patch)
treee56dcebd52b7cee09d8d9144449d50b43947a82b
parent8da122209fa1562074a584866397816211e24681 (diff)
Fix Security issue with external entity loading in XML without enabling it
GHSA-3qrf-m4j2-pcrr CVE-2023-3823 Fix Buffer mismanagement in phar_dir_read() GHSA-jqcx-ccgc-xwhv CVE-2023-3824 move httpd/nginx wants directive to config files in /etc
-rw-r--r--php-cve-2023-3823.patch91
-rw-r--r--php-cve-2023-3824.patch714
-rw-r--r--php.spec21
3 files changed, 821 insertions, 5 deletions
diff --git a/php-cve-2023-3823.patch b/php-cve-2023-3823.patch
new file mode 100644
index 0000000..70829bf
--- /dev/null
+++ b/php-cve-2023-3823.patch
@@ -0,0 +1,91 @@
+From f8f433d0d8eaac21af4f4532496d33f9c2b381d6 Mon Sep 17 00:00:00 2001
+From: Niels Dossche <7771979+nielsdos@users.noreply.github.com>
+Date: Mon, 10 Jul 2023 13:25:34 +0200
+Subject: [PATCH 1/5] Fix buffer mismanagement in phar_dir_read()
+
+Fixes GHSA-jqcx-ccgc-xwhv.
+
+(cherry picked from commit 80316123f3e9dcce8ac419bd9dd43546e2ccb5ef)
+(cherry picked from commit c398fe98c044c8e7c23135acdc38d4ef7bedc983)
+(cherry picked from commit 3f14261065e4c0552afa9cb16411475050a41c2c)
+---
+ ext/phar/dirstream.c | 15 ++++++++------
+ ext/phar/tests/GHSA-jqcx-ccgc-xwhv.phpt | 27 +++++++++++++++++++++++++
+ 2 files changed, 36 insertions(+), 6 deletions(-)
+ create mode 100644 ext/phar/tests/GHSA-jqcx-ccgc-xwhv.phpt
+
+diff --git a/ext/phar/dirstream.c b/ext/phar/dirstream.c
+index 1ee886ec2f..66931b652c 100644
+--- a/ext/phar/dirstream.c
++++ b/ext/phar/dirstream.c
+@@ -92,25 +92,28 @@ static int phar_dir_seek(php_stream *stream, zend_off_t offset, int whence, zend
+ */
+ static size_t phar_dir_read(php_stream *stream, char *buf, size_t count) /* {{{ */
+ {
+- size_t to_read;
+ HashTable *data = (HashTable *)stream->abstract;
+ zend_string *str_key;
+ zend_ulong unused;
+
++ if (count != sizeof(php_stream_dirent)) {
++ return -1;
++ }
++
+ if (HASH_KEY_NON_EXISTENT == zend_hash_get_current_key(data, &str_key, &unused)) {
+ return 0;
+ }
+
+ zend_hash_move_forward(data);
+- to_read = MIN(ZSTR_LEN(str_key), count);
+
+- if (to_read == 0 || count < ZSTR_LEN(str_key)) {
++ php_stream_dirent *dirent = (php_stream_dirent *) buf;
++
++ if (sizeof(dirent->d_name) <= ZSTR_LEN(str_key)) {
+ return 0;
+ }
+
+- memset(buf, 0, sizeof(php_stream_dirent));
+- memcpy(((php_stream_dirent *) buf)->d_name, ZSTR_VAL(str_key), to_read);
+- ((php_stream_dirent *) buf)->d_name[to_read + 1] = '\0';
++ memset(dirent, 0, sizeof(php_stream_dirent));
++ PHP_STRLCPY(dirent->d_name, ZSTR_VAL(str_key), sizeof(dirent->d_name), ZSTR_LEN(str_key));
+
+ return sizeof(php_stream_dirent);
+ }
+diff --git a/ext/phar/tests/GHSA-jqcx-ccgc-xwhv.phpt b/ext/phar/tests/GHSA-jqcx-ccgc-xwhv.phpt
+new file mode 100644
+index 0000000000..4e12f05fb6
+--- /dev/null
++++ b/ext/phar/tests/GHSA-jqcx-ccgc-xwhv.phpt
+@@ -0,0 +1,27 @@
++--TEST--
++GHSA-jqcx-ccgc-xwhv (Buffer overflow and overread in phar_dir_read())
++--SKIPIF--
++<?php if (!extension_loaded("phar")) die("skip"); ?>
++--INI--
++phar.readonly=0
++--FILE--
++<?php
++$phar = new Phar(__DIR__. '/GHSA-jqcx-ccgc-xwhv.phar');
++$phar->startBuffering();
++$phar->addFromString(str_repeat('A', PHP_MAXPATHLEN - 1), 'This is the content of file 1.');
++$phar->addFromString(str_repeat('B', PHP_MAXPATHLEN - 1).'C', 'This is the content of file 2.');
++$phar->stopBuffering();
++
++$handle = opendir('phar://' . __DIR__ . '/GHSA-jqcx-ccgc-xwhv.phar');
++var_dump(strlen(readdir($handle)));
++// Must not be a string of length PHP_MAXPATHLEN+1
++var_dump(readdir($handle));
++closedir($handle);
++?>
++--CLEAN--
++<?php
++unlink(__DIR__. '/GHSA-jqcx-ccgc-xwhv.phar');
++?>
++--EXPECTF--
++int(%d)
++bool(false)
+--
+2.41.0
+
diff --git a/php-cve-2023-3824.patch b/php-cve-2023-3824.patch
new file mode 100644
index 0000000..e807dd6
--- /dev/null
+++ b/php-cve-2023-3824.patch
@@ -0,0 +1,714 @@
+From d7de6908dfc8774e86a54100ad4e2ee810426001 Mon Sep 17 00:00:00 2001
+From: Niels Dossche <7771979+nielsdos@users.noreply.github.com>
+Date: Sat, 15 Jul 2023 17:33:52 +0200
+Subject: [PATCH 2/5] Sanitize libxml2 globals before parsing
+
+Fixes GHSA-3qrf-m4j2-pcrr.
+
+To parse a document with libxml2, you first need to create a parsing context.
+The parsing context contains parsing options (e.g. XML_NOENT to substitute
+entities) that the application (in this case PHP) can set.
+Unfortunately, libxml2 also supports providing default set options.
+For example, if you call xmlSubstituteEntitiesDefault(1) then the XML_NOENT
+option will be added to the parsing options every time you create a parsing
+context **even if the application never requested XML_NOENT**.
+
+Third party extensions can override these globals, in particular the
+substitute entity global. This causes entity substitution to be
+unexpectedly active.
+
+Fix it by setting the parsing options to a sane known value.
+For API calls that depend on global state we introduce
+PHP_LIBXML_SANITIZE_GLOBALS() and PHP_LIBXML_RESTORE_GLOBALS().
+For other APIs that work directly with a context we introduce
+php_libxml_sanitize_parse_ctxt_options().
+
+(cherry picked from commit c283c3ab0ba45d21b2b8745c1f9c7cbfe771c975)
+(cherry picked from commit b3758bd21223b97c042cae7bd26a66cde081ea98)
+(cherry picked from commit 4fb61f06b1aff89a4d7e548c37ffa5bf573270c3)
+---
+ ext/dom/document.c | 15 ++++++++
+ ext/dom/documentfragment.c | 2 ++
+ ...xml_global_state_entity_loader_bypass.phpt | 36 +++++++++++++++++++
+ ext/libxml/php_libxml.h | 36 +++++++++++++++++++
+ ext/simplexml/simplexml.c | 6 ++++
+ ...xml_global_state_entity_loader_bypass.phpt | 36 +++++++++++++++++++
+ ext/soap/php_xml.c | 2 ++
+ ext/xml/compat.c | 2 ++
+ ext/xmlreader/php_xmlreader.c | 9 +++++
+ ...xml_global_state_entity_loader_bypass.phpt | 35 ++++++++++++++++++
+ ext/xsl/xsltprocessor.c | 9 +++--
+ 11 files changed, 183 insertions(+), 5 deletions(-)
+ create mode 100644 ext/dom/tests/libxml_global_state_entity_loader_bypass.phpt
+ create mode 100644 ext/simplexml/tests/libxml_global_state_entity_loader_bypass.phpt
+ create mode 100644 ext/xmlreader/tests/libxml_global_state_entity_loader_bypass.phpt
+
+diff --git a/ext/dom/document.c b/ext/dom/document.c
+index c212faa695..b0eed5820b 100644
+--- a/ext/dom/document.c
++++ b/ext/dom/document.c
+@@ -1438,6 +1438,7 @@ static xmlDocPtr dom_document_parser(zval *id, int mode, char *source, size_t so
+ options |= XML_PARSE_NOBLANKS;
+ }
+
++ php_libxml_sanitize_parse_ctxt_options(ctxt);
+ xmlCtxtUseOptions(ctxt, options);
+
+ ctxt->recovery = recover;
+@@ -1735,7 +1736,9 @@ PHP_FUNCTION(dom_document_xinclude)
+
+ DOM_GET_OBJ(docp, id, xmlDocPtr, intern);
+
++ PHP_LIBXML_SANITIZE_GLOBALS(xinclude);
+ err = xmlXIncludeProcessFlags(docp, (int)flags);
++ PHP_LIBXML_RESTORE_GLOBALS(xinclude);
+
+ /* XML_XINCLUDE_START and XML_XINCLUDE_END nodes need to be removed as these
+ are added via xmlXIncludeProcess to mark beginning and ending of xincluded document
+@@ -1774,6 +1777,7 @@ PHP_FUNCTION(dom_document_validate)
+
+ DOM_GET_OBJ(docp, id, xmlDocPtr, intern);
+
++ PHP_LIBXML_SANITIZE_GLOBALS(validate);
+ cvp = xmlNewValidCtxt();
+
+ cvp->userData = NULL;
+@@ -1785,6 +1789,7 @@ PHP_FUNCTION(dom_document_validate)
+ } else {
+ RETVAL_FALSE;
+ }
++ PHP_LIBXML_RESTORE_GLOBALS(validate);
+
+ xmlFreeValidCtxt(cvp);
+
+@@ -1818,14 +1823,18 @@ static void _dom_document_schema_validate(INTERNAL_FUNCTION_PARAMETERS, int type
+
+ DOM_GET_OBJ(docp, id, xmlDocPtr, intern);
+
++ PHP_LIBXML_SANITIZE_GLOBALS(new_parser_ctxt);
++
+ switch (type) {
+ case DOM_LOAD_FILE:
+ if (CHECK_NULL_PATH(source, source_len)) {
++ PHP_LIBXML_RESTORE_GLOBALS(new_parser_ctxt);
+ php_error_docref(NULL, E_WARNING, "Invalid Schema file source");
+ RETURN_FALSE;
+ }
+ valid_file = _dom_get_valid_file_path(source, resolved_path, MAXPATHLEN);
+ if (!valid_file) {
++ PHP_LIBXML_RESTORE_GLOBALS(new_parser_ctxt);
+ php_error_docref(NULL, E_WARNING, "Invalid Schema file source");
+ RETURN_FALSE;
+ }
+@@ -1846,6 +1855,7 @@ static void _dom_document_schema_validate(INTERNAL_FUNCTION_PARAMETERS, int type
+ parser);
+ sptr = xmlSchemaParse(parser);
+ xmlSchemaFreeParserCtxt(parser);
++ PHP_LIBXML_RESTORE_GLOBALS(new_parser_ctxt);
+ if (!sptr) {
+ php_error_docref(NULL, E_WARNING, "Invalid Schema");
+ RETURN_FALSE;
+@@ -1866,11 +1876,13 @@ static void _dom_document_schema_validate(INTERNAL_FUNCTION_PARAMETERS, int type
+ }
+ #endif
+
++ PHP_LIBXML_SANITIZE_GLOBALS(validate);
+ xmlSchemaSetValidOptions(vptr, valid_opts);
+ xmlSchemaSetValidErrors(vptr, php_libxml_error_handler, php_libxml_error_handler, vptr);
+ is_valid = xmlSchemaValidateDoc(vptr, docp);
+ xmlSchemaFree(sptr);
+ xmlSchemaFreeValidCtxt(vptr);
++ PHP_LIBXML_RESTORE_GLOBALS(validate);
+
+ if (is_valid == 0) {
+ RETURN_TRUE;
+@@ -1940,12 +1952,14 @@ static void _dom_document_relaxNG_validate(INTERNAL_FUNCTION_PARAMETERS, int typ
+ return;
+ }
+
++ PHP_LIBXML_SANITIZE_GLOBALS(parse);
+ xmlRelaxNGSetParserErrors(parser,
+ (xmlRelaxNGValidityErrorFunc) php_libxml_error_handler,
+ (xmlRelaxNGValidityWarningFunc) php_libxml_error_handler,
+ parser);
+ sptr = xmlRelaxNGParse(parser);
+ xmlRelaxNGFreeParserCtxt(parser);
++ PHP_LIBXML_RESTORE_GLOBALS(parse);
+ if (!sptr) {
+ php_error_docref(NULL, E_WARNING, "Invalid RelaxNG");
+ RETURN_FALSE;
+@@ -2045,6 +2059,7 @@ static void dom_load_html(INTERNAL_FUNCTION_PARAMETERS, int mode) /* {{{ */
+ ctxt->sax->error = php_libxml_ctx_error;
+ ctxt->sax->warning = php_libxml_ctx_warning;
+ }
++ php_libxml_sanitize_parse_ctxt_options(ctxt);
+ if (options) {
+ htmlCtxtUseOptions(ctxt, (int)options);
+ }
+diff --git a/ext/dom/documentfragment.c b/ext/dom/documentfragment.c
+index 87cb691501..b7ecfdc14b 100644
+--- a/ext/dom/documentfragment.c
++++ b/ext/dom/documentfragment.c
+@@ -134,7 +134,9 @@ PHP_METHOD(domdocumentfragment, appendXML) {
+ }
+
+ if (data) {
++ PHP_LIBXML_SANITIZE_GLOBALS(parse);
+ err = xmlParseBalancedChunkMemory(nodep->doc, NULL, NULL, 0, (xmlChar *) data, &lst);
++ PHP_LIBXML_RESTORE_GLOBALS(parse);
+ if (err != 0) {
+ RETURN_FALSE;
+ }
+diff --git a/ext/dom/tests/libxml_global_state_entity_loader_bypass.phpt b/ext/dom/tests/libxml_global_state_entity_loader_bypass.phpt
+new file mode 100644
+index 0000000000..b28afd4694
+--- /dev/null
++++ b/ext/dom/tests/libxml_global_state_entity_loader_bypass.phpt
+@@ -0,0 +1,36 @@
++--TEST--
++GHSA-3qrf-m4j2-pcrr (libxml global state entity loader bypass)
++--SKIPIF--
++<?php
++if (!extension_loaded('libxml')) die('skip libxml extension not available');
++if (!extension_loaded('dom')) die('skip dom extension not available');
++if (!extension_loaded('zend-test')) die('skip zend-test extension not available');
++?>
++--FILE--
++<?php
++
++$xml = "<?xml version='1.0'?><!DOCTYPE root [<!ENTITY % bork SYSTEM \"php://nope\"> %bork;]><nothing/>";
++
++libxml_use_internal_errors(true);
++
++function parseXML($xml) {
++ $doc = new DOMDocument();
++ @$doc->loadXML($xml);
++ $doc->createDocumentFragment()->appendXML("&bork;");
++ foreach (libxml_get_errors() as $error) {
++ var_dump(trim($error->message));
++ }
++}
++
++parseXML($xml);
++zend_test_override_libxml_global_state();
++parseXML($xml);
++
++echo "Done\n";
++
++?>
++--EXPECT--
++string(25) "Entity 'bork' not defined"
++string(25) "Entity 'bork' not defined"
++string(25) "Entity 'bork' not defined"
++Done
+diff --git a/ext/libxml/php_libxml.h b/ext/libxml/php_libxml.h
+index f7aa726d88..371b4eecc5 100644
+--- a/ext/libxml/php_libxml.h
++++ b/ext/libxml/php_libxml.h
+@@ -122,6 +122,42 @@ PHP_LIBXML_API void php_libxml_shutdown(void);
+ ZEND_TSRMLS_CACHE_EXTERN()
+ #endif
+
++/* Other extension may override the global state options, these global options
++ * are copied initially to ctxt->options. Set the options to a known good value.
++ * See libxml2 globals.c and parserInternals.c.
++ * The unique_name argument allows multiple sanitizes and restores within the
++ * same function, even nested is necessary. */
++#define PHP_LIBXML_SANITIZE_GLOBALS(unique_name) \
++ int xml_old_loadsubset_##unique_name = xmlLoadExtDtdDefaultValue; \
++ xmlLoadExtDtdDefaultValue = 0; \
++ int xml_old_validate_##unique_name = xmlDoValidityCheckingDefaultValue; \
++ xmlDoValidityCheckingDefaultValue = 0; \
++ int xml_old_pedantic_##unique_name = xmlPedanticParserDefault(0); \
++ int xml_old_substitute_##unique_name = xmlSubstituteEntitiesDefault(0); \
++ int xml_old_linenrs_##unique_name = xmlLineNumbersDefault(0); \
++ int xml_old_blanks_##unique_name = xmlKeepBlanksDefault(1);
++
++#define PHP_LIBXML_RESTORE_GLOBALS(unique_name) \
++ xmlLoadExtDtdDefaultValue = xml_old_loadsubset_##unique_name; \
++ xmlDoValidityCheckingDefaultValue = xml_old_validate_##unique_name; \
++ (void) xmlPedanticParserDefault(xml_old_pedantic_##unique_name); \
++ (void) xmlSubstituteEntitiesDefault(xml_old_substitute_##unique_name); \
++ (void) xmlLineNumbersDefault(xml_old_linenrs_##unique_name); \
++ (void) xmlKeepBlanksDefault(xml_old_blanks_##unique_name);
++
++/* Alternative for above, working directly on the context and not setting globals.
++ * Generally faster because no locking is involved, and this has the advantage that it sets the options to a known good value. */
++static zend_always_inline void php_libxml_sanitize_parse_ctxt_options(xmlParserCtxtPtr ctxt)
++{
++ ctxt->loadsubset = 0;
++ ctxt->validate = 0;
++ ctxt->pedantic = 0;
++ ctxt->replaceEntities = 0;
++ ctxt->linenumbers = 0;
++ ctxt->keepBlanks = 1;
++ ctxt->options = 0;
++}
++
+ #else /* HAVE_LIBXML */
+ #define libxml_module_ptr NULL
+ #endif
+diff --git a/ext/simplexml/simplexml.c b/ext/simplexml/simplexml.c
+index 341daed0ee..c2b0230e19 100644
+--- a/ext/simplexml/simplexml.c
++++ b/ext/simplexml/simplexml.c
+@@ -2217,7 +2217,9 @@ PHP_FUNCTION(simplexml_load_file)
+ RETURN_FALSE;
+ }
+
++ PHP_LIBXML_SANITIZE_GLOBALS(read_file);
+ docp = xmlReadFile(filename, NULL, (int)options);
++ PHP_LIBXML_RESTORE_GLOBALS(read_file);
+
+ if (!docp) {
+ RETURN_FALSE;
+@@ -2271,7 +2273,9 @@ PHP_FUNCTION(simplexml_load_string)
+ RETURN_FALSE;
+ }
+
++ PHP_LIBXML_SANITIZE_GLOBALS(read_memory);
+ docp = xmlReadMemory(data, (int)data_len, NULL, NULL, (int)options);
++ PHP_LIBXML_RESTORE_GLOBALS(read_memory);
+
+ if (!docp) {
+ RETURN_FALSE;
+@@ -2321,7 +2325,9 @@ SXE_METHOD(__construct)
+ return;
+ }
+
++ PHP_LIBXML_SANITIZE_GLOBALS(read_file_or_memory);
+ docp = is_url ? xmlReadFile(data, NULL, (int)options) : xmlReadMemory(data, (int)data_len, NULL, NULL, (int)options);
++ PHP_LIBXML_RESTORE_GLOBALS(read_file_or_memory);
+
+ if (!docp) {
+ ((php_libxml_node_object *)sxe)->document = NULL;
+diff --git a/ext/simplexml/tests/libxml_global_state_entity_loader_bypass.phpt b/ext/simplexml/tests/libxml_global_state_entity_loader_bypass.phpt
+new file mode 100644
+index 0000000000..2152e01232
+--- /dev/null
++++ b/ext/simplexml/tests/libxml_global_state_entity_loader_bypass.phpt
+@@ -0,0 +1,36 @@
++--TEST--
++GHSA-3qrf-m4j2-pcrr (libxml global state entity loader bypass)
++--SKIPIF--
++<?php
++if (!extension_loaded('libxml')) die('skip libxml extension not available');
++if (!extension_loaded('simplexml')) die('skip simplexml extension not available');
++if (!extension_loaded('zend-test')) die('skip zend-test extension not available');
++?>
++--FILE--
++<?php
++
++$xml = "<?xml version='1.0'?><!DOCTYPE root [<!ENTITY % bork SYSTEM \"php://nope\"> %bork;]><nothing/>";
++
++libxml_use_internal_errors(true);
++zend_test_override_libxml_global_state();
++
++echo "--- String test ---\n";
++simplexml_load_string($xml);
++echo "--- Constructor test ---\n";
++new SimpleXMLElement($xml);
++echo "--- File test ---\n";
++file_put_contents("libxml_global_state_entity_loader_bypass.tmp", $xml);
++simplexml_load_file("libxml_global_state_entity_loader_bypass.tmp");
++
++echo "Done\n";
++
++?>
++--CLEAN--
++<?php
++@unlink("libxml_global_state_entity_loader_bypass.tmp");
++?>
++--EXPECT--
++--- String test ---
++--- Constructor test ---
++--- File test ---
++Done
+diff --git a/ext/soap/php_xml.c b/ext/soap/php_xml.c
+index 1ac684eb81..053960c559 100644
+--- a/ext/soap/php_xml.c
++++ b/ext/soap/php_xml.c
+@@ -94,6 +94,7 @@ xmlDocPtr soap_xmlParseFile(const char *filename)
+ if (ctxt) {
+ zend_bool old;
+
++ php_libxml_sanitize_parse_ctxt_options(ctxt);
+ ctxt->keepBlanks = 0;
+ ctxt->sax->ignorableWhitespace = soap_ignorableWhitespace;
+ ctxt->sax->comment = soap_Comment;
+@@ -144,6 +145,7 @@ xmlDocPtr soap_xmlParseMemory(const void *buf, size_t buf_size)
+ if (ctxt) {
+ zend_bool old;
+
++ php_libxml_sanitize_parse_ctxt_options(ctxt);
+ ctxt->sax->ignorableWhitespace = soap_ignorableWhitespace;
+ ctxt->sax->comment = soap_Comment;
+ ctxt->sax->warning = NULL;
+diff --git a/ext/xml/compat.c b/ext/xml/compat.c
+index ef83485722..c55047215b 100644
+--- a/ext/xml/compat.c
++++ b/ext/xml/compat.c
+@@ -19,6 +19,7 @@
+ #include "php.h"
+ #if defined(HAVE_LIBXML) && (defined(HAVE_XML) || defined(HAVE_XMLRPC)) && !defined(HAVE_LIBEXPAT)
+ #include "expat_compat.h"
++#include "ext/libxml/php_libxml.h"
+
+ typedef struct _php_xml_ns {
+ xmlNsPtr nsptr;
+@@ -473,6 +474,7 @@ XML_ParserCreate_MM(const XML_Char *encoding, const XML_Memory_Handling_Suite *m
+ parser->parser->charset = XML_CHAR_ENCODING_NONE;
+ #endif
+
++ php_libxml_sanitize_parse_ctxt_options(parser->parser);
+ #if LIBXML_VERSION >= 20703
+ xmlCtxtUseOptions(parser->parser, XML_PARSE_OLDSAX);
+ #endif
+diff --git a/ext/xmlreader/php_xmlreader.c b/ext/xmlreader/php_xmlreader.c
+index 0f2b62ae20..f0fd013ff9 100644
+--- a/ext/xmlreader/php_xmlreader.c
++++ b/ext/xmlreader/php_xmlreader.c
+@@ -301,6 +301,7 @@ static xmlRelaxNGPtr _xmlreader_get_relaxNG(char *source, size_t source_len, siz
+ return NULL;
+ }
+
++ PHP_LIBXML_SANITIZE_GLOBALS(parse);
+ if (error_func || warn_func) {
+ xmlRelaxNGSetParserErrors(parser,
+ (xmlRelaxNGValidityErrorFunc) error_func,
+@@ -309,6 +310,7 @@ static xmlRelaxNGPtr _xmlreader_get_relaxNG(char *source, size_t source_len, siz
+ }
+ sptr = xmlRelaxNGParse(parser);
+ xmlRelaxNGFreeParserCtxt(parser);
++ PHP_LIBXML_RESTORE_GLOBALS(parse);
+
+ return sptr;
+ }
+@@ -881,7 +883,9 @@ PHP_METHOD(xmlreader, open)
+ valid_file = _xmlreader_get_valid_file_path(source, resolved_path, MAXPATHLEN );
+
+ if (valid_file) {
++ PHP_LIBXML_SANITIZE_GLOBALS(reader_for_file);
+ reader = xmlReaderForFile(valid_file, encoding, options);
++ PHP_LIBXML_RESTORE_GLOBALS(reader_for_file);
+ }
+
+ if (reader == NULL) {
+@@ -959,7 +963,9 @@ PHP_METHOD(xmlreader, setSchema)
+
+ intern = Z_XMLREADER_P(id);
+ if (intern && intern->ptr) {
++ PHP_LIBXML_SANITIZE_GLOBALS(schema);
+ retval = xmlTextReaderSchemaValidate(intern->ptr, source);
++ PHP_LIBXML_RESTORE_GLOBALS(schema);
+
+ if (retval == 0) {
+ RETURN_TRUE;
+@@ -1079,6 +1085,7 @@ PHP_METHOD(xmlreader, XML)
+ }
+ uri = (char *) xmlCanonicPath((const xmlChar *) resolved_path);
+ }
++ PHP_LIBXML_SANITIZE_GLOBALS(text_reader);
+ reader = xmlNewTextReader(inputbfr, uri);
+
+ if (reader != NULL) {
+@@ -1099,9 +1106,11 @@ PHP_METHOD(xmlreader, XML)
+ xmlFree(uri);
+ }
+
++ PHP_LIBXML_RESTORE_GLOBALS(text_reader);
+ return;
+ }
+ }
++ PHP_LIBXML_RESTORE_GLOBALS(text_reader);
+ }
+
+ if (uri) {
+diff --git a/ext/xmlreader/tests/libxml_global_state_entity_loader_bypass.phpt b/ext/xmlreader/tests/libxml_global_state_entity_loader_bypass.phpt
+new file mode 100644
+index 0000000000..e9ffb04c2b
+--- /dev/null
++++ b/ext/xmlreader/tests/libxml_global_state_entity_loader_bypass.phpt
+@@ -0,0 +1,35 @@
++--TEST--
++GHSA-3qrf-m4j2-pcrr (libxml global state entity loader bypass)
++--SKIPIF--
++<?php
++if (!extension_loaded('libxml')) die('skip libxml extension not available');
++if (!extension_loaded('xmlreader')) die('skip xmlreader extension not available');
++if (!extension_loaded('zend-test')) die('skip zend-test extension not available');
++?>
++--FILE--
++<?php
++
++$xml = "<?xml version='1.0'?><!DOCTYPE root [<!ENTITY % bork SYSTEM \"php://nope\"> %bork;]><nothing/>";
++
++libxml_use_internal_errors(true);
++zend_test_override_libxml_global_state();
++
++echo "--- String test ---\n";
++$reader = XMLReader::xml($xml);
++$reader->read();
++echo "--- File test ---\n";
++file_put_contents("libxml_global_state_entity_loader_bypass.tmp", $xml);
++$reader = XMLReader::open("libxml_global_state_entity_loader_bypass.tmp");
++$reader->read();
++
++echo "Done\n";
++
++?>
++--CLEAN--
++<?php
++@unlink("libxml_global_state_entity_loader_bypass.tmp");
++?>
++--EXPECT--
++--- String test ---
++--- File test ---
++Done
+diff --git a/ext/xsl/xsltprocessor.c b/ext/xsl/xsltprocessor.c
+index 9948d6f0b3..7bbe640a5c 100644
+--- a/ext/xsl/xsltprocessor.c
++++ b/ext/xsl/xsltprocessor.c
+@@ -396,7 +396,7 @@ PHP_FUNCTION(xsl_xsltprocessor_import_stylesheet)
+ xmlDoc *doc = NULL, *newdoc = NULL;
+ xsltStylesheetPtr sheetp, oldsheetp;
+ xsl_object *intern;
+- int prevSubstValue, prevExtDtdValue, clone_docu = 0;
++ int clone_docu = 0;
+ xmlNode *nodep = NULL;
+ zend_object_handlers *std_hnd;
+ zval *cloneDocu, member, rv;
+@@ -419,13 +419,12 @@ PHP_FUNCTION(xsl_xsltprocessor_import_stylesheet)
+ stylesheet document otherwise the node proxies will be a mess */
+ newdoc = xmlCopyDoc(doc, 1);
+ xmlNodeSetBase((xmlNodePtr) newdoc, (xmlChar *)doc->URL);
+- prevSubstValue = xmlSubstituteEntitiesDefault(1);
+- prevExtDtdValue = xmlLoadExtDtdDefaultValue;
++ PHP_LIBXML_SANITIZE_GLOBALS(parse);
++ xmlSubstituteEntitiesDefault(1);
+ xmlLoadExtDtdDefaultValue = XML_DETECT_IDS | XML_COMPLETE_ATTRS;
+
+ sheetp = xsltParseStylesheetDoc(newdoc);
+- xmlSubstituteEntitiesDefault(prevSubstValue);
+- xmlLoadExtDtdDefaultValue = prevExtDtdValue;
++ PHP_LIBXML_RESTORE_GLOBALS(parse);
+
+ if (!sheetp) {
+ xmlFreeDoc(newdoc);
+--
+2.41.0
+
+From 3535016313ece1e12ffd5d9fc2f39478941be3d8 Mon Sep 17 00:00:00 2001
+From: Remi Collet <remi@remirepo.net>
+Date: Tue, 1 Aug 2023 07:37:25 +0200
+Subject: [PATCH 3/5] backport zend_test changes
+ (zend_test_override_libxml_global_state)
+
+(cherry picked from commit 24e669e790e6aebd219c9a9fa19017455c8646b4)
+(cherry picked from commit 79a97d0e2b93c40c3728d587046266989c5acc42)
+---
+ ...xml_global_state_entity_loader_bypass.phpt | 1 +
+ ...xml_global_state_entity_loader_bypass.phpt | 1 +
+ ...xml_global_state_entity_loader_bypass.phpt | 5 +++--
+ ext/zend_test/test.c | 22 +++++++++++++++++++
+ 4 files changed, 27 insertions(+), 2 deletions(-)
+
+diff --git a/ext/dom/tests/libxml_global_state_entity_loader_bypass.phpt b/ext/dom/tests/libxml_global_state_entity_loader_bypass.phpt
+index b28afd4694..7fc2a249ac 100644
+--- a/ext/dom/tests/libxml_global_state_entity_loader_bypass.phpt
++++ b/ext/dom/tests/libxml_global_state_entity_loader_bypass.phpt
+@@ -5,6 +5,7 @@ GHSA-3qrf-m4j2-pcrr (libxml global state entity loader bypass)
+ if (!extension_loaded('libxml')) die('skip libxml extension not available');
+ if (!extension_loaded('dom')) die('skip dom extension not available');
+ if (!extension_loaded('zend-test')) die('skip zend-test extension not available');
++if (!function_exists('zend_test_override_libxml_global_state')) die('skip not for Windows');
+ ?>
+ --FILE--
+ <?php
+diff --git a/ext/simplexml/tests/libxml_global_state_entity_loader_bypass.phpt b/ext/simplexml/tests/libxml_global_state_entity_loader_bypass.phpt
+index 2152e01232..54f9d4941e 100644
+--- a/ext/simplexml/tests/libxml_global_state_entity_loader_bypass.phpt
++++ b/ext/simplexml/tests/libxml_global_state_entity_loader_bypass.phpt
+@@ -5,6 +5,7 @@ GHSA-3qrf-m4j2-pcrr (libxml global state entity loader bypass)
+ if (!extension_loaded('libxml')) die('skip libxml extension not available');
+ if (!extension_loaded('simplexml')) die('skip simplexml extension not available');
+ if (!extension_loaded('zend-test')) die('skip zend-test extension not available');
++if (!function_exists('zend_test_override_libxml_global_state')) die('skip not for Windows');
+ ?>
+ --FILE--
+ <?php
+diff --git a/ext/xmlreader/tests/libxml_global_state_entity_loader_bypass.phpt b/ext/xmlreader/tests/libxml_global_state_entity_loader_bypass.phpt
+index e9ffb04c2b..b0120b325e 100644
+--- a/ext/xmlreader/tests/libxml_global_state_entity_loader_bypass.phpt
++++ b/ext/xmlreader/tests/libxml_global_state_entity_loader_bypass.phpt
+@@ -5,6 +5,7 @@ GHSA-3qrf-m4j2-pcrr (libxml global state entity loader bypass)
+ if (!extension_loaded('libxml')) die('skip libxml extension not available');
+ if (!extension_loaded('xmlreader')) die('skip xmlreader extension not available');
+ if (!extension_loaded('zend-test')) die('skip zend-test extension not available');
++if (!function_exists('zend_test_override_libxml_global_state')) die('skip not for Windows');
+ ?>
+ --FILE--
+ <?php
+@@ -15,11 +16,11 @@ libxml_use_internal_errors(true);
+ zend_test_override_libxml_global_state();
+
+ echo "--- String test ---\n";
+-$reader = XMLReader::xml($xml);
++$reader = @XMLReader::xml($xml);
+ $reader->read();
+ echo "--- File test ---\n";
+ file_put_contents("libxml_global_state_entity_loader_bypass.tmp", $xml);
+-$reader = XMLReader::open("libxml_global_state_entity_loader_bypass.tmp");
++$reader = @XMLReader::open("libxml_global_state_entity_loader_bypass.tmp");
+ $reader->read();
+
+ echo "Done\n";
+diff --git a/ext/zend_test/test.c b/ext/zend_test/test.c
+index 4a7fe540fb..611bc9b68c 100644
+--- a/ext/zend_test/test.c
++++ b/ext/zend_test/test.c
+@@ -25,6 +25,11 @@
+ #include "ext/standard/info.h"
+ #include "php_test.h"
+
++#if defined(HAVE_LIBXML) && !defined(PHP_WIN32)
++# include <libxml/globals.h>
++# include <libxml/parser.h>
++#endif
++
+ static zend_class_entry *zend_test_interface;
+ static zend_class_entry *zend_test_class;
+ static zend_class_entry *zend_test_trait;
+@@ -44,6 +49,20 @@ ZEND_BEGIN_ARG_INFO_EX(arginfo_zend_leak_variable, 0, 0, 1)
+ ZEND_ARG_INFO(0, variable)
+ ZEND_END_ARG_INFO()
+
++#if defined(HAVE_LIBXML) && !defined(PHP_WIN32)
++static ZEND_FUNCTION(zend_test_override_libxml_global_state)
++{
++ ZEND_PARSE_PARAMETERS_NONE();
++
++ xmlLoadExtDtdDefaultValue = 1;
++ xmlDoValidityCheckingDefaultValue = 1;
++ (void) xmlPedanticParserDefault(1);
++ (void) xmlSubstituteEntitiesDefault(1);
++ (void) xmlLineNumbersDefault(1);
++ (void) xmlKeepBlanksDefault(0);
++}
++#endif
++
+ ZEND_FUNCTION(zend_test_func)
+ {
+ /* dummy */
+@@ -251,6 +270,9 @@ const zend_function_entry zend_test_functions[] = {
+ ZEND_FE(zend_terminate_string, arginfo_zend_terminate_string)
+ ZEND_FE(zend_leak_bytes, NULL)
+ ZEND_FE(zend_leak_variable, arginfo_zend_leak_variable)
++#if defined(HAVE_LIBXML) && !defined(PHP_WIN32)
++ ZEND_FE(zend_test_override_libxml_global_state, NULL)
++#endif
+ ZEND_FE_END
+ };
+
+--
+2.41.0
+
+From 26d70866843598f4a2ed94b01999b38e1d7c0e97 Mon Sep 17 00:00:00 2001
+From: Remi Collet <remi@remirepo.net>
+Date: Tue, 1 Aug 2023 15:45:24 +0200
+Subject: [PATCH 4/5] adapt to 7.2
+
+---
+ ext/dom/tests/libxml_global_state_entity_loader_bypass.phpt | 1 -
+ .../tests/libxml_global_state_entity_loader_bypass.phpt | 1 -
+ .../tests/libxml_global_state_entity_loader_bypass.phpt | 1 -
+ ext/zend_test/test.c | 2 --
+ 4 files changed, 5 deletions(-)
+
+diff --git a/ext/dom/tests/libxml_global_state_entity_loader_bypass.phpt b/ext/dom/tests/libxml_global_state_entity_loader_bypass.phpt
+index 7fc2a249ac..25499006d9 100644
+--- a/ext/dom/tests/libxml_global_state_entity_loader_bypass.phpt
++++ b/ext/dom/tests/libxml_global_state_entity_loader_bypass.phpt
+@@ -4,7 +4,6 @@ GHSA-3qrf-m4j2-pcrr (libxml global state entity loader bypass)
+ <?php
+ if (!extension_loaded('libxml')) die('skip libxml extension not available');
+ if (!extension_loaded('dom')) die('skip dom extension not available');
+-if (!extension_loaded('zend-test')) die('skip zend-test extension not available');
+ if (!function_exists('zend_test_override_libxml_global_state')) die('skip not for Windows');
+ ?>
+ --FILE--
+diff --git a/ext/simplexml/tests/libxml_global_state_entity_loader_bypass.phpt b/ext/simplexml/tests/libxml_global_state_entity_loader_bypass.phpt
+index 54f9d4941e..e09ecb5d72 100644
+--- a/ext/simplexml/tests/libxml_global_state_entity_loader_bypass.phpt
++++ b/ext/simplexml/tests/libxml_global_state_entity_loader_bypass.phpt
+@@ -4,7 +4,6 @@ GHSA-3qrf-m4j2-pcrr (libxml global state entity loader bypass)
+ <?php
+ if (!extension_loaded('libxml')) die('skip libxml extension not available');
+ if (!extension_loaded('simplexml')) die('skip simplexml extension not available');
+-if (!extension_loaded('zend-test')) die('skip zend-test extension not available');
+ if (!function_exists('zend_test_override_libxml_global_state')) die('skip not for Windows');
+ ?>
+ --FILE--
+diff --git a/ext/xmlreader/tests/libxml_global_state_entity_loader_bypass.phpt b/ext/xmlreader/tests/libxml_global_state_entity_loader_bypass.phpt
+index b0120b325e..9824e10603 100644
+--- a/ext/xmlreader/tests/libxml_global_state_entity_loader_bypass.phpt
++++ b/ext/xmlreader/tests/libxml_global_state_entity_loader_bypass.phpt
+@@ -4,7 +4,6 @@ GHSA-3qrf-m4j2-pcrr (libxml global state entity loader bypass)
+ <?php
+ if (!extension_loaded('libxml')) die('skip libxml extension not available');
+ if (!extension_loaded('xmlreader')) die('skip xmlreader extension not available');
+-if (!extension_loaded('zend-test')) die('skip zend-test extension not available');
+ if (!function_exists('zend_test_override_libxml_global_state')) die('skip not for Windows');
+ ?>
+ --FILE--
+diff --git a/ext/zend_test/test.c b/ext/zend_test/test.c
+index 611bc9b68c..51818083d9 100644
+--- a/ext/zend_test/test.c
++++ b/ext/zend_test/test.c
+@@ -52,8 +52,6 @@ ZEND_END_ARG_INFO()
+ #if defined(HAVE_LIBXML) && !defined(PHP_WIN32)
+ static ZEND_FUNCTION(zend_test_override_libxml_global_state)
+ {
+- ZEND_PARSE_PARAMETERS_NONE();
+-
+ xmlLoadExtDtdDefaultValue = 1;
+ xmlDoValidityCheckingDefaultValue = 1;
+ (void) xmlPedanticParserDefault(1);
+--
+2.41.0
+
+From 79c0bf87711036b83f8ee1723c034ccc839d847b Mon Sep 17 00:00:00 2001
+From: Remi Collet <remi@remirepo.net>
+Date: Tue, 1 Aug 2023 07:22:33 +0200
+Subject: [PATCH 5/5] NEWS
+
+(cherry picked from commit ef1d507acf7be23d7624dc3c891683b2218feb51)
+(cherry picked from commit 3cf7c2b10e577136b267f2d90bfdff6743271c5c)
+---
+ NEWS | 10 ++++++++++
+ 1 file changed, 10 insertions(+)
+
+diff --git a/NEWS b/NEWS
+index 5f49a7ee04..286f3df0f4 100644
+--- a/NEWS
++++ b/NEWS
+@@ -1,6 +1,16 @@
+ PHP NEWS
+ |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
+
++Backported from 8.0.30
++
++- Libxml:
++ . Fixed bug GHSA-3qrf-m4j2-pcrr (Security issue with external entity loading
++ in XML without enabling it). (CVE-2023-3823) (nielsdos, ilutov)
++
++- Phar:
++ . Fixed bug GHSA-jqcx-ccgc-xwhv (Buffer mismanagement in phar_dir_read()).
++ (CVE-2023-3824) (nielsdos)
++
+ Backported from 8.0.29
+
+ - Soap:
+--
+2.41.0
+
diff --git a/php.spec b/php.spec
index 24c8856..32c9f52 100644
--- a/php.spec
+++ b/php.spec
@@ -126,7 +126,7 @@
Summary: PHP scripting language for creating dynamic web sites
Name: %{?scl_prefix}php
Version: %{upver}%{?rcver:~%{rcver}}
-Release: 18%{?dist}
+Release: 19%{?dist}
# All files licensed under PHP version 3.01, except
# Zend is licensed under Zend
# TSRM is licensed under BSD
@@ -210,6 +210,8 @@ Patch214: php-bug81744.patch
Patch215: php-bug81746.patch
Patch216: php-cve-2023-0662.patch
Patch217: php-cve-2023-3247.patch
+Patch218: php-cve-2023-3823.patch
+Patch219: php-cve-2023-3824.patch
# Fixes for tests (300+)
# Factory is droped from system tzdata
@@ -987,6 +989,8 @@ sed -e 's/php-devel/%{?scl_prefix}php-devel/' -i scripts/phpize.in
%patch -P215 -p1 -b .bug81746
%patch -P216 -p1 -b .cve0662
%patch -P217 -p1 -b .cve3247
+%patch -P218 -p1 -b .cve3823
+%patch -P219 -p1 -b .cve3824
# Fixes for tests
%patch -P300 -p1 -b .datetests
@@ -1514,8 +1518,8 @@ mv $RPM_BUILD_ROOT%{_sysconfdir}/php-fpm.d/www.conf.default .
%if %{with_systemd}
install -Dm 644 %{SOURCE6} $RPM_BUILD_ROOT%{_unitdir}/%{?scl_prefix}php-fpm.service
%if 0%{?fedora} >= 27 || 0%{?rhel} >= 8
-install -Dm 644 %{SOURCE12} $RPM_BUILD_ROOT%{_unitdir}/httpd.service.d/%{?scl_prefix}php-fpm.conf
-install -Dm 644 %{SOURCE12} $RPM_BUILD_ROOT%{_unitdir}/nginx.service.d/%{?scl_prefix}php-fpm.conf
+install -Dm 644 %{SOURCE12} $RPM_BUILD_ROOT%{_root_sysconfdir}/systemd/system/httpd.service.d/%{?scl_prefix}php-fpm.conf
+install -Dm 644 %{SOURCE12} $RPM_BUILD_ROOT%{_root_sysconfdir}/systemd/system/nginx.service.d/%{?scl_prefix}php-fpm.conf
%endif
sed -e 's:/run:%{_localstatedir}/run:' \
-e 's:/etc/sysconfig:%{_sysconfdir}/sysconfig:' \
@@ -1867,8 +1871,8 @@ EOF
%{_unitdir}/%{?scl_prefix}php-fpm.service
%dir %{_root_sysconfdir}/systemd/system/%{?scl_prefix}php-fpm.service.d
%if 0%{?fedora} >= 27 || 0%{?rhel} >= 8
-%{_unitdir}/httpd.service.d/%{?scl_prefix}php-fpm.conf
-%{_unitdir}/nginx.service.d/%{?scl_prefix}php-fpm.conf
+%config(noreplace) %{_root_sysconfdir}/systemd/system/httpd.service.d/%{?scl_prefix}php-fpm.conf
+%config(noreplace) %{_root_sysconfdir}/systemd/system/nginx.service.d/%{?scl_prefix}php-fpm.conf
%endif
%else
%{_root_initddir}/%{?scl_prefix}php-fpm
@@ -1957,6 +1961,13 @@ EOF
%changelog
+* Tue Aug 1 2023 Remi Collet <remi@remirepo.net> - 7.3.34-19
+- Fix Security issue with external entity loading in XML without enabling it
+ GHSA-3qrf-m4j2-pcrr CVE-2023-3823
+- Fix Buffer mismanagement in phar_dir_read()
+ GHSA-jqcx-ccgc-xwhv CVE-2023-3824
+- move httpd/nginx wants directive to config files in /etc
+
* Tue Jun 20 2023 Remi Collet <remi@remirepo.net> - 7.2.34-18
- fix possible buffer overflow in date
- define %%php72___phpize and %%php72___phpconfig