Version in base suite: 7.4.33-1+deb11u4 Base version: php7.4_7.4.33-1+deb11u4 Target version: php7.4_7.4.33-1+deb11u5 Base file: /srv/ftp-master.debian.org/ftp/pool/main/p/php7.4/php7.4_7.4.33-1+deb11u4.dsc Target file: /srv/ftp-master.debian.org/policy/pool/main/p/php7.4/php7.4_7.4.33-1+deb11u5.dsc changelog | 17 patches/0068-add-cve.patch | 22 patches/0069-Fix-buffer-mismanagement-in-phar_dir_read.patch | 85 + patches/0070-Sanitize-libxml2-globals-before-parsing.patch | 494 ++++++++++ patches/0071-NEWS.patch | 29 patches/0072-backport-zend_test-changes-zend_test_override_libxml.patch | 108 ++ patches/0075-Fix-GHSA-wpj3-hf5j-x4v4-__Host-__Secure-cookie-bypas.patch | 159 +++ patches/0076-NEWS.patch | 26 patches/0077-Fix-bug-GHSA-q6x7-frmf-grcw-password_verify-can-erro.patch | 51 + patches/0078-NEWS.patch | 22 patches/0079-Add-proc_open-escaping-for-cmd-file-execution-Backpo.patch | 251 +++++ patches/0080-NEWS.patch | 22 patches/series | 11 13 files changed, 1297 insertions(+) diff -Nru php7.4-7.4.33/debian/changelog php7.4-7.4.33/debian/changelog --- php7.4-7.4.33/debian/changelog 2023-06-09 16:51:37.000000000 +0000 +++ php7.4-7.4.33/debian/changelog 2024-04-12 00:02:16.000000000 +0000 @@ -1,3 +1,20 @@ +php7.4 (7.4.33-1+deb11u5) bullseye-security; urgency=high + + * Backported from 8.0.30 + + CVE-2023-3823: Fixed bug GHSA-3qrf-m4j2-pcrr (Security issue with + external entity loading in XML without enabling it). + + CVE-2023-3824: Fixed bug GHSA-jqcx-ccgc-xwhv (Buffer mismanagement in + phar_dir_read()). + * Backported from 8.1.28 + + CVE-2024-1874: Fixed bug GHSA-pc52-254m-w9w7 (Command injection via + array-ish $command parameter of proc_open). + + CVE-2024-2756: Fixed bug GHSA-wpj3-hf5j-x4v4 (__Host-/__Secure- + cookie bypass due to partial CVE-2022-31629 fix). + + CVE-2024-3096: Fixed bug GHSA-h746-cjrr-wfmr (password_verify can + erroneously return true, opening ATO risk). + + -- Ondřej Surý Fri, 12 Apr 2024 02:02:16 +0200 + php7.4 (7.4.33-1+deb11u4) bullseye-security; urgency=high * Backported from 8.0.29 diff -Nru php7.4-7.4.33/debian/patches/0068-add-cve.patch php7.4-7.4.33/debian/patches/0068-add-cve.patch --- php7.4-7.4.33/debian/patches/0068-add-cve.patch 1970-01-01 00:00:00.000000000 +0000 +++ php7.4-7.4.33/debian/patches/0068-add-cve.patch 2024-04-12 00:02:16.000000000 +0000 @@ -0,0 +1,22 @@ +From: Remi Collet +Date: Thu, 15 Jun 2023 08:47:55 +0200 +Subject: add cve + +--- + NEWS | 3 ++- + 1 file changed, 2 insertions(+), 1 deletion(-) + +diff --git a/NEWS b/NEWS +index 7c07635..899644b 100644 +--- a/NEWS ++++ b/NEWS +@@ -5,7 +5,8 @@ Backported from 8.0.29 + + - Soap: + . Fixed bug GHSA-76gg-c692-v2mw (Missing error check and insufficient random +- bytes in HTTP Digest authentication for SOAP). (nielsdos, timwolla) ++ bytes in HTTP Digest authentication for SOAP). ++ (CVE-2023-3247) (nielsdos, timwolla) + + Backported from 8.0.28 + diff -Nru php7.4-7.4.33/debian/patches/0069-Fix-buffer-mismanagement-in-phar_dir_read.patch php7.4-7.4.33/debian/patches/0069-Fix-buffer-mismanagement-in-phar_dir_read.patch --- php7.4-7.4.33/debian/patches/0069-Fix-buffer-mismanagement-in-phar_dir_read.patch 1970-01-01 00:00:00.000000000 +0000 +++ php7.4-7.4.33/debian/patches/0069-Fix-buffer-mismanagement-in-phar_dir_read.patch 2024-04-12 00:02:16.000000000 +0000 @@ -0,0 +1,85 @@ +From: Niels Dossche <7771979+nielsdos@users.noreply.github.com> +Date: Mon, 10 Jul 2023 13:25:34 +0200 +Subject: Fix buffer mismanagement in phar_dir_read() + +Fixes GHSA-jqcx-ccgc-xwhv. + +(cherry picked from commit 80316123f3e9dcce8ac419bd9dd43546e2ccb5ef) +--- + 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 4710703..490b145 100644 +--- a/ext/phar/dirstream.c ++++ b/ext/phar/dirstream.c +@@ -91,25 +91,28 @@ static int phar_dir_seek(php_stream *stream, zend_off_t offset, int whence, zend + */ + static ssize_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 0000000..4e12f05 +--- /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-- ++ ++--INI-- ++phar.readonly=0 ++--FILE-- ++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-- ++ ++--EXPECTF-- ++int(%d) ++bool(false) diff -Nru php7.4-7.4.33/debian/patches/0070-Sanitize-libxml2-globals-before-parsing.patch php7.4-7.4.33/debian/patches/0070-Sanitize-libxml2-globals-before-parsing.patch --- php7.4-7.4.33/debian/patches/0070-Sanitize-libxml2-globals-before-parsing.patch 1970-01-01 00:00:00.000000000 +0000 +++ php7.4-7.4.33/debian/patches/0070-Sanitize-libxml2-globals-before-parsing.patch 2024-04-12 00:02:16.000000000 +0000 @@ -0,0 +1,494 @@ +From: Niels Dossche <7771979+nielsdos@users.noreply.github.com> +Date: Sat, 15 Jul 2023 17:33:52 +0200 +Subject: 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) +--- + ext/dom/document.c | 15 +++++++++ + ext/dom/documentfragment.c | 2 ++ + .../libxml_global_state_entity_loader_bypass.phpt | 36 ++++++++++++++++++++++ + ext/libxml/php_libxml.h | 36 ++++++++++++++++++++++ + ext/simplexml/simplexml.c | 6 ++++ + .../libxml_global_state_entity_loader_bypass.phpt | 36 ++++++++++++++++++++++ + ext/soap/php_xml.c | 2 ++ + ext/xml/compat.c | 2 ++ + ext/xmlreader/php_xmlreader.c | 9 ++++++ + .../libxml_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 e683eb8..989b5b3 100644 +--- a/ext/dom/document.c ++++ b/ext/dom/document.c +@@ -1458,6 +1458,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; +@@ -1758,7 +1759,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 +@@ -1798,6 +1801,7 @@ PHP_FUNCTION(dom_document_validate) + + DOM_GET_OBJ(docp, id, xmlDocPtr, intern); + ++ PHP_LIBXML_SANITIZE_GLOBALS(validate); + cvp = xmlNewValidCtxt(); + + cvp->userData = NULL; +@@ -1809,6 +1813,7 @@ PHP_FUNCTION(dom_document_validate) + } else { + RETVAL_FALSE; + } ++ PHP_LIBXML_RESTORE_GLOBALS(validate); + + xmlFreeValidCtxt(cvp); + +@@ -1843,14 +1848,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; + } +@@ -1871,6 +1880,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; +@@ -1889,11 +1899,13 @@ static void _dom_document_schema_validate(INTERNAL_FUNCTION_PARAMETERS, int type + valid_opts |= XML_SCHEMA_VAL_VC_I_CREATE; + } + ++ 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; +@@ -1964,12 +1976,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; +@@ -2068,6 +2082,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 9b22258..711c42f 100644 +--- a/ext/dom/documentfragment.c ++++ b/ext/dom/documentfragment.c +@@ -131,7 +131,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 0000000..b28afd4 +--- /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-- ++ ++--FILE-- ++ %bork;]>"; ++ ++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 cf936e9..92028d5 100644 +--- a/ext/libxml/php_libxml.h ++++ b/ext/libxml/php_libxml.h +@@ -120,6 +120,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 2cdff0e..101a9d8 100644 +--- a/ext/simplexml/simplexml.c ++++ b/ext/simplexml/simplexml.c +@@ -2194,7 +2194,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; +@@ -2248,7 +2250,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; +@@ -2298,7 +2302,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 0000000..2152e01 +--- /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-- ++ ++--FILE-- ++ %bork;]>"; ++ ++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-- ++ ++--EXPECT-- ++--- String test --- ++--- Constructor test --- ++--- File test --- ++Done +diff --git a/ext/soap/php_xml.c b/ext/soap/php_xml.c +index 18a2661..1bb7fa0 100644 +--- a/ext/soap/php_xml.c ++++ b/ext/soap/php_xml.c +@@ -93,6 +93,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; +@@ -141,6 +142,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 fc45256..57eb00d 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; +@@ -471,6 +472,7 @@ XML_ParserCreate_MM(const XML_Char *encoding, const XML_Memory_Handling_Suite *m + return NULL; + } + ++ php_libxml_sanitize_parse_ctxt_options(parser->parser); + xmlCtxtUseOptions(parser->parser, XML_PARSE_OLDSAX); + + parser->parser->replaceEntities = 1; +diff --git a/ext/xmlreader/php_xmlreader.c b/ext/xmlreader/php_xmlreader.c +index ecc81ad..51d6bb9 100644 +--- a/ext/xmlreader/php_xmlreader.c ++++ b/ext/xmlreader/php_xmlreader.c +@@ -304,6 +304,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, +@@ -312,6 +313,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) { +@@ -958,7 +962,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; +@@ -1082,6 +1088,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) { +@@ -1100,9 +1107,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 0000000..e9ffb04 +--- /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-- ++ ++--FILE-- ++ %bork;]>"; ++ ++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-- ++ ++--EXPECT-- ++--- String test --- ++--- File test --- ++Done +diff --git a/ext/xsl/xsltprocessor.c b/ext/xsl/xsltprocessor.c +index 079920d..2d95b2f 100644 +--- a/ext/xsl/xsltprocessor.c ++++ b/ext/xsl/xsltprocessor.c +@@ -398,7 +398,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; + zval *cloneDocu, member, rv; + +@@ -421,13 +421,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); diff -Nru php7.4-7.4.33/debian/patches/0071-NEWS.patch php7.4-7.4.33/debian/patches/0071-NEWS.patch --- php7.4-7.4.33/debian/patches/0071-NEWS.patch 1970-01-01 00:00:00.000000000 +0000 +++ php7.4-7.4.33/debian/patches/0071-NEWS.patch 2024-04-12 00:02:16.000000000 +0000 @@ -0,0 +1,29 @@ +From: Remi Collet +Date: Tue, 1 Aug 2023 07:22:33 +0200 +Subject: NEWS + +--- + NEWS | 10 ++++++++++ + 1 file changed, 10 insertions(+) + +diff --git a/NEWS b/NEWS +index 899644b..4f88029 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: diff -Nru php7.4-7.4.33/debian/patches/0072-backport-zend_test-changes-zend_test_override_libxml.patch php7.4-7.4.33/debian/patches/0072-backport-zend_test-changes-zend_test_override_libxml.patch --- php7.4-7.4.33/debian/patches/0072-backport-zend_test-changes-zend_test_override_libxml.patch 1970-01-01 00:00:00.000000000 +0000 +++ php7.4-7.4.33/debian/patches/0072-backport-zend_test-changes-zend_test_override_libxml.patch 2024-04-12 00:02:16.000000000 +0000 @@ -0,0 +1,108 @@ +From: Remi Collet +Date: Tue, 1 Aug 2023 07:37:25 +0200 +Subject: backport zend_test changes (zend_test_override_libxml_global_state) + +--- + .../libxml_global_state_entity_loader_bypass.phpt | 1 + + .../libxml_global_state_entity_loader_bypass.phpt | 1 + + .../libxml_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 b28afd4..7fc2a24 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-- + + --FILE-- + + --FILE-- + 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 4f81adc..cdfc155 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 ++# include ++#endif ++ + static zend_class_entry *zend_test_interface; + static zend_class_entry *zend_test_class; + static zend_class_entry *zend_test_child_class; +@@ -48,6 +53,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 */ +@@ -297,6 +316,9 @@ static 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 + }; + diff -Nru php7.4-7.4.33/debian/patches/0075-Fix-GHSA-wpj3-hf5j-x4v4-__Host-__Secure-cookie-bypas.patch php7.4-7.4.33/debian/patches/0075-Fix-GHSA-wpj3-hf5j-x4v4-__Host-__Secure-cookie-bypas.patch --- php7.4-7.4.33/debian/patches/0075-Fix-GHSA-wpj3-hf5j-x4v4-__Host-__Secure-cookie-bypas.patch 1970-01-01 00:00:00.000000000 +0000 +++ php7.4-7.4.33/debian/patches/0075-Fix-GHSA-wpj3-hf5j-x4v4-__Host-__Secure-cookie-bypas.patch 2024-04-12 00:02:16.000000000 +0000 @@ -0,0 +1,159 @@ +From: Niels Dossche <7771979+nielsdos@users.noreply.github.com> +Date: Sun, 17 Mar 2024 21:04:47 +0100 +Subject: Fix GHSA-wpj3-hf5j-x4v4: __Host-/__Secure- cookie bypass due to + partial CVE-2022-31629 fix + +The check happened too early as later code paths may perform more +mangling rules. Move the check downwards right before adding the actual +variable. + +(cherry picked from commit 093c08af25fb323efa0c8e6154aa9fdeae3d3b53) +(cherry picked from commit 2e07a3acd7a6b53c55325b94bed97748d7697b53) +--- + ext/standard/tests/ghsa-wpj3-hf5j-x4v4.phpt | 63 +++++++++++++++++++++++++++++ + main/php_variables.c | 41 ++++++++++++------- + 2 files changed, 90 insertions(+), 14 deletions(-) + create mode 100644 ext/standard/tests/ghsa-wpj3-hf5j-x4v4.phpt + +diff --git a/ext/standard/tests/ghsa-wpj3-hf5j-x4v4.phpt b/ext/standard/tests/ghsa-wpj3-hf5j-x4v4.phpt +new file mode 100644 +index 0000000..77fcb68 +--- /dev/null ++++ b/ext/standard/tests/ghsa-wpj3-hf5j-x4v4.phpt +@@ -0,0 +1,63 @@ ++--TEST-- ++ghsa-wpj3-hf5j-x4v4 (__Host-/__Secure- cookie bypass due to partial CVE-2022-31629 fix) ++--COOKIE-- ++..Host-test=ignore_1; ++._Host-test=ignore_2; ++.[Host-test=ignore_3; ++_.Host-test=ignore_4; ++__Host-test=ignore_5; ++_[Host-test=ignore_6; ++[.Host-test=ignore_7; ++[_Host-test=ignore_8; ++[[Host-test=ignore_9; ++..Host-test[]=ignore_10; ++._Host-test[]=ignore_11; ++.[Host-test[]=ignore_12; ++_.Host-test[]=ignore_13; ++__Host-test[]=legitimate_14; ++_[Host-test[]=legitimate_15; ++[.Host-test[]=ignore_16; ++[_Host-test[]=ignore_17; ++[[Host-test[]=ignore_18; ++..Secure-test=ignore_1; ++._Secure-test=ignore_2; ++.[Secure-test=ignore_3; ++_.Secure-test=ignore_4; ++__Secure-test=ignore_5; ++_[Secure-test=ignore_6; ++[.Secure-test=ignore_7; ++[_Secure-test=ignore_8; ++[[Secure-test=ignore_9; ++..Secure-test[]=ignore_10; ++._Secure-test[]=ignore_11; ++.[Secure-test[]=ignore_12; ++_.Secure-test[]=ignore_13; ++__Secure-test[]=legitimate_14; ++_[Secure-test[]=legitimate_15; ++[.Secure-test[]=ignore_16; ++[_Secure-test[]=ignore_17; ++[[Secure-test[]=ignore_18; ++--FILE-- ++ ++--EXPECT-- ++array(3) { ++ ["__Host-test"]=> ++ array(1) { ++ [0]=> ++ string(13) "legitimate_14" ++ } ++ ["_"]=> ++ array(2) { ++ ["Host-test["]=> ++ string(13) "legitimate_15" ++ ["Secure-test["]=> ++ string(13) "legitimate_15" ++ } ++ ["__Secure-test"]=> ++ array(1) { ++ [0]=> ++ string(13) "legitimate_14" ++ } ++} +diff --git a/main/php_variables.c b/main/php_variables.c +index 18f6b65..e971d49 100644 +--- a/main/php_variables.c ++++ b/main/php_variables.c +@@ -65,6 +65,21 @@ static zend_always_inline void php_register_variable_quick(const char *name, siz + zend_string_release_ex(key, 0); + } + ++/* Discard variable if mangling made it start with __Host-, where pre-mangling it did not start with __Host- ++ * Discard variable if mangling made it start with __Secure-, where pre-mangling it did not start with __Secure- */ ++static zend_bool php_is_forbidden_variable_name(const char *mangled_name, size_t mangled_name_len, const char *pre_mangled_name) ++{ ++ if (mangled_name_len >= sizeof("__Host-")-1 && strncmp(mangled_name, "__Host-", sizeof("__Host-")-1) == 0 && strncmp(pre_mangled_name, "__Host-", sizeof("__Host-")-1) != 0) { ++ return 1; ++ } ++ ++ if (mangled_name_len >= sizeof("__Secure-")-1 && strncmp(mangled_name, "__Secure-", sizeof("__Secure-")-1) == 0 && strncmp(pre_mangled_name, "__Secure-", sizeof("__Secure-")-1) != 0) { ++ return 1; ++ } ++ ++ return 0; ++} ++ + PHPAPI void php_register_variable_ex(char *var_name, zval *val, zval *track_vars_array) + { + char *p = NULL; +@@ -115,20 +130,6 @@ PHPAPI void php_register_variable_ex(char *var_name, zval *val, zval *track_vars + } + var_len = p - var; + +- /* Discard variable if mangling made it start with __Host-, where pre-mangling it did not start with __Host- */ +- if (strncmp(var, "__Host-", sizeof("__Host-")-1) == 0 && strncmp(var_name, "__Host-", sizeof("__Host-")-1) != 0) { +- zval_ptr_dtor_nogc(val); +- free_alloca(var_orig, use_heap); +- return; +- } +- +- /* Discard variable if mangling made it start with __Secure-, where pre-mangling it did not start with __Secure- */ +- if (strncmp(var, "__Secure-", sizeof("__Secure-")-1) == 0 && strncmp(var_name, "__Secure-", sizeof("__Secure-")-1) != 0) { +- zval_ptr_dtor_nogc(val); +- free_alloca(var_orig, use_heap); +- return; +- } +- + if (var_len==0) { /* empty variable name, or variable name with a space in it */ + zval_ptr_dtor_nogc(val); + free_alloca(var_orig, use_heap); +@@ -226,6 +227,12 @@ PHPAPI void php_register_variable_ex(char *var_name, zval *val, zval *track_vars + return; + } + } else { ++ if (php_is_forbidden_variable_name(index, index_len, var_name)) { ++ zval_ptr_dtor_nogc(val); ++ free_alloca(var_orig, use_heap); ++ return; ++ } ++ + gpc_element_p = zend_symtable_str_find(symtable1, index, index_len); + if (!gpc_element_p) { + zval tmp; +@@ -263,6 +270,12 @@ plain_var: + zval_ptr_dtor_nogc(val); + } + } else { ++ if (php_is_forbidden_variable_name(index, index_len, var_name)) { ++ zval_ptr_dtor_nogc(val); ++ free_alloca(var_orig, use_heap); ++ return; ++ } ++ + zend_ulong idx; + + /* diff -Nru php7.4-7.4.33/debian/patches/0076-NEWS.patch php7.4-7.4.33/debian/patches/0076-NEWS.patch --- php7.4-7.4.33/debian/patches/0076-NEWS.patch 1970-01-01 00:00:00.000000000 +0000 +++ php7.4-7.4.33/debian/patches/0076-NEWS.patch 2024-04-12 00:02:16.000000000 +0000 @@ -0,0 +1,26 @@ +From: Remi Collet +Date: Wed, 10 Apr 2024 08:59:32 +0200 +Subject: NEWS + +(cherry picked from commit 366cc249b7d54707572beb7096e8f6c65ee79719) +--- + NEWS | 6 ++++++ + 1 file changed, 6 insertions(+) + +diff --git a/NEWS b/NEWS +index 4f88029..d63aadc 100644 +--- a/NEWS ++++ b/NEWS +@@ -1,6 +1,12 @@ + PHP NEWS + ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||| + ++Backported from 8.1.28 ++ ++- Standard: ++ . Fixed bug GHSA-wpj3-hf5j-x4v4 (__Host-/__Secure- cookie bypass due to ++ partial CVE-2022-31629 fix). (CVE-2024-2756) (nielsdos) ++ + Backported from 8.0.30 + + - Libxml: diff -Nru php7.4-7.4.33/debian/patches/0077-Fix-bug-GHSA-q6x7-frmf-grcw-password_verify-can-erro.patch php7.4-7.4.33/debian/patches/0077-Fix-bug-GHSA-q6x7-frmf-grcw-password_verify-can-erro.patch --- php7.4-7.4.33/debian/patches/0077-Fix-bug-GHSA-q6x7-frmf-grcw-password_verify-can-erro.patch 1970-01-01 00:00:00.000000000 +0000 +++ php7.4-7.4.33/debian/patches/0077-Fix-bug-GHSA-q6x7-frmf-grcw-password_verify-can-erro.patch 2024-04-12 00:02:16.000000000 +0000 @@ -0,0 +1,51 @@ +From: Jakub Zelenka +Date: Fri, 29 Mar 2024 15:27:59 +0000 +Subject: Fix bug GHSA-q6x7-frmf-grcw: password_verify can erroneously return + true + +Disallow null character in bcrypt password + +(cherry picked from commit 0ba5229a3f7572846e91c8f5382e87785f543826) +(cherry picked from commit 81794c73068d9a44bf109bbcc9793e7b56a1c051) +--- + ext/standard/password.c | 5 +++++ + ext/standard/tests/password/password_bcrypt_errors.phpt | 6 ++++++ + 2 files changed, 11 insertions(+) + +diff --git a/ext/standard/password.c b/ext/standard/password.c +index 9fe7fb1..af80670 100644 +--- a/ext/standard/password.c ++++ b/ext/standard/password.c +@@ -260,6 +260,11 @@ static zend_string* php_password_bcrypt_hash(const zend_string *password, zend_a + zval *zcost; + zend_long cost = PHP_PASSWORD_BCRYPT_COST; + ++ if (memchr(ZSTR_VAL(password), '\0', ZSTR_LEN(password))) { ++ php_error_docref(NULL, E_WARNING, "Bcrypt password must not contain null character"); ++ return NULL; ++ } ++ + if (options && (zcost = zend_hash_str_find(options, "cost", sizeof("cost")-1)) != NULL) { + cost = zval_get_long(zcost); + } +diff --git a/ext/standard/tests/password/password_bcrypt_errors.phpt b/ext/standard/tests/password/password_bcrypt_errors.phpt +index a082608..f95b726 100644 +--- a/ext/standard/tests/password/password_bcrypt_errors.phpt ++++ b/ext/standard/tests/password/password_bcrypt_errors.phpt +@@ -16,6 +16,8 @@ var_dump(password_hash("foo", PASSWORD_BCRYPT, array("salt" => 123))); + + var_dump(password_hash("foo", PASSWORD_BCRYPT, array("cost" => "foo"))); + ++var_dump(password_hash("null\0password", PASSWORD_BCRYPT)); ++ + ?> + --EXPECTF-- + Warning: password_hash(): Invalid bcrypt cost parameter specified: 3 in %s on line %d +@@ -41,3 +43,7 @@ NULL + + Warning: password_hash(): Invalid bcrypt cost parameter specified: 0 in %s on line %d + NULL ++ ++Warning: password_hash(): Bcrypt password must not contain null character in %s on line %d ++NULL ++ diff -Nru php7.4-7.4.33/debian/patches/0078-NEWS.patch php7.4-7.4.33/debian/patches/0078-NEWS.patch --- php7.4-7.4.33/debian/patches/0078-NEWS.patch 1970-01-01 00:00:00.000000000 +0000 +++ php7.4-7.4.33/debian/patches/0078-NEWS.patch 2024-04-12 00:02:16.000000000 +0000 @@ -0,0 +1,22 @@ +From: Remi Collet +Date: Wed, 10 Apr 2024 09:01:09 +0200 +Subject: NEWS + +(cherry picked from commit 24f77904ee2259d722559f129f96a1f145a2367b) +--- + NEWS | 2 ++ + 1 file changed, 2 insertions(+) + +diff --git a/NEWS b/NEWS +index d63aadc..96a33c2 100644 +--- a/NEWS ++++ b/NEWS +@@ -6,6 +6,8 @@ Backported from 8.1.28 + - Standard: + . Fixed bug GHSA-wpj3-hf5j-x4v4 (__Host-/__Secure- cookie bypass due to + partial CVE-2022-31629 fix). (CVE-2024-2756) (nielsdos) ++ . Fixed bug GHSA-h746-cjrr-wfmr (password_verify can erroneously return true, ++ opening ATO risk). (CVE-2024-3096) (Jakub Zelenka) + + Backported from 8.0.30 + diff -Nru php7.4-7.4.33/debian/patches/0079-Add-proc_open-escaping-for-cmd-file-execution-Backpo.patch php7.4-7.4.33/debian/patches/0079-Add-proc_open-escaping-for-cmd-file-execution-Backpo.patch --- php7.4-7.4.33/debian/patches/0079-Add-proc_open-escaping-for-cmd-file-execution-Backpo.patch 1970-01-01 00:00:00.000000000 +0000 +++ php7.4-7.4.33/debian/patches/0079-Add-proc_open-escaping-for-cmd-file-execution-Backpo.patch 2024-04-12 00:02:16.000000000 +0000 @@ -0,0 +1,251 @@ +From: Jan Ehrhardt +Date: Thu, 11 Apr 2024 14:29:17 +0200 +Subject: Add proc_open escaping for cmd file execution Backport + CVE-2024-1874: Command injection via array-ish $command parameter of + proc_open See https://github.com/remicollet/php-src-security/issues/14 + +--- + ext/standard/proc_open.c | 88 ++++++++++++++++++++-- + .../general_functions/ghsa-pc52-254m-w9w7_1.phpt | 29 +++++++ + .../general_functions/ghsa-pc52-254m-w9w7_2.phpt | 29 +++++++ + .../general_functions/ghsa-pc52-254m-w9w7_3.phpt | 29 +++++++ + 4 files changed, 170 insertions(+), 5 deletions(-) + create mode 100644 ext/standard/tests/general_functions/ghsa-pc52-254m-w9w7_1.phpt + create mode 100644 ext/standard/tests/general_functions/ghsa-pc52-254m-w9w7_2.phpt + create mode 100644 ext/standard/tests/general_functions/ghsa-pc52-254m-w9w7_3.phpt + +diff --git a/ext/standard/proc_open.c b/ext/standard/proc_open.c +index 93c47af..debb46f 100644 +--- a/ext/standard/proc_open.c ++++ b/ext/standard/proc_open.c +@@ -420,10 +420,33 @@ static void append_backslashes(smart_string *str, size_t num_bs) { + } + } + +-/* See https://docs.microsoft.com/en-us/cpp/cpp/parsing-cpp-command-line-arguments */ +-static void append_win_escaped_arg(smart_string *str, char *arg) { ++/* See https://docs.microsoft.com/en-us/cpp/cpp/parsing-cpp-command-line-arguments and ++ * https://learn.microsoft.com/en-us/archive/blogs/twistylittlepassagesallalike/everyone-quotes-command-line-arguments-the-wrong-way */ ++const char *special_chars = "()!^\"<>&|%"; ++ ++static BOOL is_special_character_present(const char *arg) ++{ ++ for (size_t i = 0; i < strlen(arg); ++i) { ++ if (strchr(special_chars, arg[i]) != NULL) { ++ return 1; ++ } ++ } ++ return 0; ++} ++ ++static void append_win_escaped_arg(smart_string *str, char *arg, BOOL is_cmd_argument) ++{ + char c; + size_t num_bs = 0; ++ BOOL has_special_character = 0; ++ ++ if (is_cmd_argument) { ++ has_special_character = is_special_character_present(arg); ++ if (has_special_character) { ++ /* Escape double quote with ^ if executed by cmd.exe. */ ++ smart_string_appendc(str, '^'); ++ } ++ } + smart_string_appendc(str, '"'); + while ((c = *arg)) { + if (c == '\\') { +@@ -434,19 +457,72 @@ static void append_win_escaped_arg(smart_string *str, char *arg) { + num_bs = num_bs * 2 + 1; + } + append_backslashes(str, num_bs); ++ if (has_special_character && strchr(special_chars, c) != NULL) { ++ /* Escape special chars with ^ if executed by cmd.exe. */ ++ smart_string_appendc(str, '^'); ++ } + smart_string_appendc(str, c); + num_bs = 0; + } + arg++; + } + append_backslashes(str, num_bs * 2); ++ if (has_special_character) { ++ /* Escape double quote with ^ if executed by cmd.exe. */ ++ smart_string_appendc(str, '^'); ++ } + smart_string_appendc(str, '"'); + } + ++static BOOL stricmp_end(const char* suffix, const char* str) { ++ size_t suffix_len = strlen(suffix); ++ size_t str_len = strlen(str); ++ ++ if (suffix_len > str_len) { ++ return -1; /* Suffix is longer than string, cannot match. */ ++ } ++ ++ /* Compare the end of the string with the suffix, ignoring case. */ ++ return _stricmp(str + (str_len - suffix_len), suffix); ++} ++ ++static BOOL is_executed_by_cmd(const char *prog_name) ++{ ++ /* If program name is cmd.exe, then return true. */ ++ if (_stricmp("cmd.exe", prog_name) == 0 || _stricmp("cmd", prog_name) == 0 ++ || stricmp_end("\\cmd.exe", prog_name) == 0 || stricmp_end("\\cmd", prog_name) == 0) { ++ return 1; ++ } ++ ++ /* Find the last occurrence of the directory separator (backslash or forward slash). */ ++ char *last_separator = strrchr(prog_name, '\\'); ++ char *last_separator_fwd = strrchr(prog_name, '/'); ++ if (last_separator_fwd && (!last_separator || last_separator < last_separator_fwd)) { ++ last_separator = last_separator_fwd; ++ } ++ ++ /* Find the last dot in the filename after the last directory separator. */ ++ char *extension = NULL; ++ if (last_separator != NULL) { ++ extension = strrchr(last_separator, '.'); ++ } else { ++ extension = strrchr(prog_name, '.'); ++ } ++ ++ if (extension == NULL || extension == prog_name) { ++ /* No file extension found, it is not batch file. */ ++ return 0; ++ } ++ ++ /* Check if the file extension is ".bat" or ".cmd" which is always executed by cmd.exe. */ ++ return (_stricmp(extension, ".bat") == 0 || _stricmp(extension, ".cmd") == 0) ? 1 : 0; ++} ++ + static char *create_win_command_from_args(HashTable *args) { + smart_string str = {0}; + zval *arg_zv; +- zend_bool is_prog_name = 1; ++ BOOL is_prog_name = 1; ++ BOOL is_cmd_execution = 0; + int elem_num = 0; + + ZEND_HASH_FOREACH_VAL(args, arg_zv) { +@@ -456,11 +532,13 @@ static char *create_win_command_from_args(HashTable *args) { + return NULL; + } + +- if (!is_prog_name) { ++ if (is_prog_name) { ++ is_cmd_execution = is_executed_by_cmd(ZSTR_VAL(arg_str)); ++ } else { + smart_string_appendc(&str, ' '); + } + +- append_win_escaped_arg(&str, ZSTR_VAL(arg_str)); ++ append_win_escaped_arg(&str, ZSTR_VAL(arg_str), !is_prog_name && is_cmd_execution); + + is_prog_name = 0; + zend_string_release(arg_str); +diff --git a/ext/standard/tests/general_functions/ghsa-pc52-254m-w9w7_1.phpt b/ext/standard/tests/general_functions/ghsa-pc52-254m-w9w7_1.phpt +new file mode 100644 +index 0000000..8d0939c +--- /dev/null ++++ b/ext/standard/tests/general_functions/ghsa-pc52-254m-w9w7_1.phpt +@@ -0,0 +1,29 @@ ++--TEST-- ++GHSA-54hq-v5wp-fqgv - proc_open does not correctly escape args for bat files ++--SKIPIF-- ++ ++--FILE-- ++ ++--EXPECT-- ++"¬epad.exe ++--CLEAN-- ++ +diff --git a/ext/standard/tests/general_functions/ghsa-pc52-254m-w9w7_2.phpt b/ext/standard/tests/general_functions/ghsa-pc52-254m-w9w7_2.phpt +new file mode 100644 +index 0000000..a1e39d7 +--- /dev/null ++++ b/ext/standard/tests/general_functions/ghsa-pc52-254m-w9w7_2.phpt +@@ -0,0 +1,29 @@ ++--TEST-- ++GHSA-54hq-v5wp-fqgv - proc_open does not correctly escape args for cmd files ++--SKIPIF-- ++ ++--FILE-- ++^()!.exe"], $descriptorspec, $pipes); ++proc_close($proc); ++ ++?> ++--EXPECT-- ++"¬epad<>^()!.exe ++--CLEAN-- ++ +diff --git a/ext/standard/tests/general_functions/ghsa-pc52-254m-w9w7_3.phpt b/ext/standard/tests/general_functions/ghsa-pc52-254m-w9w7_3.phpt +new file mode 100644 +index 0000000..69f12d7 +--- /dev/null ++++ b/ext/standard/tests/general_functions/ghsa-pc52-254m-w9w7_3.phpt +@@ -0,0 +1,29 @@ ++--TEST-- ++GHSA-54hq-v5wp-fqgv - proc_open does not correctly escape args for cmd executing batch files ++--SKIPIF-- ++ ++--FILE-- ++ ++--EXPECT-- ++"¬epad.exe ++--CLEAN-- ++ diff -Nru php7.4-7.4.33/debian/patches/0080-NEWS.patch php7.4-7.4.33/debian/patches/0080-NEWS.patch --- php7.4-7.4.33/debian/patches/0080-NEWS.patch 1970-01-01 00:00:00.000000000 +0000 +++ php7.4-7.4.33/debian/patches/0080-NEWS.patch 2024-04-12 00:02:16.000000000 +0000 @@ -0,0 +1,22 @@ +From: Remi Collet +Date: Wed, 10 Apr 2024 16:18:48 +0200 +Subject: NEWS + +(cherry picked from commit 293cd3b34a493a06772d28270dcad54572435d64) +--- + NEWS | 2 ++ + 1 file changed, 2 insertions(+) + +diff --git a/NEWS b/NEWS +index 96a33c2..8058eff 100644 +--- a/NEWS ++++ b/NEWS +@@ -8,6 +8,8 @@ Backported from 8.1.28 + partial CVE-2022-31629 fix). (CVE-2024-2756) (nielsdos) + . Fixed bug GHSA-h746-cjrr-wfmr (password_verify can erroneously return true, + opening ATO risk). (CVE-2024-3096) (Jakub Zelenka) ++ . Fixed bug GHSA-pc52-254m-w9w7 (Command injection via array-ish $command ++ parameter of proc_open). (CVE-2024-1874) (Jakub Zelenka) + + Backported from 8.0.30 + diff -Nru php7.4-7.4.33/debian/patches/series php7.4-7.4.33/debian/patches/series --- php7.4-7.4.33/debian/patches/series 2023-06-09 16:51:37.000000000 +0000 +++ php7.4-7.4.33/debian/patches/series 2024-04-12 00:02:16.000000000 +0000 @@ -58,3 +58,14 @@ 0058-fix-NEWS-not-FPM-specific.patch 0059-Fix-missing-randomness-check-and-insufficient-random.patch 0060-Fix-GH-11382-add-missing-hash-header-for-bin2hex.patch +0068-add-cve.patch +0069-Fix-buffer-mismanagement-in-phar_dir_read.patch +0070-Sanitize-libxml2-globals-before-parsing.patch +0071-NEWS.patch +0072-backport-zend_test-changes-zend_test_override_libxml.patch +0075-Fix-GHSA-wpj3-hf5j-x4v4-__Host-__Secure-cookie-bypas.patch +0076-NEWS.patch +0077-Fix-bug-GHSA-q6x7-frmf-grcw-password_verify-can-erro.patch +0078-NEWS.patch +0079-Add-proc_open-escaping-for-cmd-file-execution-Backpo.patch +0080-NEWS.patch