From 11b950e90bcd9f0f3a7906cd3f2ae0c2e323f860 Mon Sep 17 00:00:00 2001 From: Remi Collet Date: Wed, 13 May 2026 16:31:07 +0200 Subject: Fix XSS within status endpoint CVE-2026-6735 Fix Stale SOAP_GLOBAL(ref_map) pointer with Apache Map CVE-2026-6722 Fix Use-after-free after header parsing failure with SOAP_PERSISTENCE_SESSION CVE-2026-7261 Fix Broken Apache map value NULL check CVE-2026-7262 Fix Signed integer overflow of char array offset CVE-2026-7568 --- failed.txt | 2 +- php-cve-2026-6722.patch | 113 ++ php-cve-2026-6735.patch | 2697 +++++++++++++++++++++++++++++++++++++++++++++++ php-cve-2026-7261.patch | 122 +++ php-cve-2026-7262.patch | 83 ++ php-cve-2026-7568.patch | 90 ++ php-fpm.service | 2 +- php.spec | 29 +- 8 files changed, 3133 insertions(+), 5 deletions(-) create mode 100644 php-cve-2026-6722.patch create mode 100644 php-cve-2026-6735.patch create mode 100644 php-cve-2026-7261.patch create mode 100644 php-cve-2026-7262.patch create mode 100644 php-cve-2026-7568.patch diff --git a/failed.txt b/failed.txt index 4346c5f..b0f41a8 100644 --- a/failed.txt +++ b/failed.txt @@ -1,4 +1,4 @@ -===== 7.0.33-45 (2024-11-26) +===== 7.0.33-46 (2026-05-13) $ grep -r 'Tests failed' /var/lib/mock/scl70*/build.log diff --git a/php-cve-2026-6722.patch b/php-cve-2026-6722.patch new file mode 100644 index 0000000..735ee1d --- /dev/null +++ b/php-cve-2026-6722.patch @@ -0,0 +1,113 @@ +From 237b2b14cad370a25ddec00be93a9710003b5048 Mon Sep 17 00:00:00 2001 +From: Ilija Tovilo +Date: Sun, 3 May 2026 19:56:53 +0200 +Subject: [PATCH 1/6] GHSA-85c2-q967-79q5: [soap] Fix stale + SOAP_GLOBAL(ref_map) pointer with Apache Map + +Fixes GHSA-85c2-q967-79q5 +Fixes CVE-2026-6722 + +(cherry picked from commit aee3b3ac9b816b0def1c462695b483b49a83148e) +(cherry picked from commit 15064460d6682766f91c1a841d27cdfbc38907e8) +(cherry picked from commit bbc1be3fc763b81707ccaa91a4cd1d439b753b12) +(cherry picked from commit 6c4b67ca091afea4f436202d7f9db38a129106dc) +(cherry picked from commit 017843d76d595ae97cb97eba4affd69501244571) +(cherry picked from commit 8fc3ed35cf67234da5201f64051e2ffa96d70f86) +(cherry picked from commit 7151aacadf978a14d06e09dd5899e8727f232056) +--- + ext/soap/php_encoding.c | 3 +- + ext/soap/tests/GHSA-85c2-q967-79q5.phpt | 61 +++++++++++++++++++++++++ + 2 files changed, 63 insertions(+), 1 deletion(-) + create mode 100644 ext/soap/tests/GHSA-85c2-q967-79q5.phpt + +diff --git a/ext/soap/php_encoding.c b/ext/soap/php_encoding.c +index 47afe2703c7..40fba95980a 100644 +--- a/ext/soap/php_encoding.c ++++ b/ext/soap/php_encoding.c +@@ -381,6 +381,7 @@ static zend_bool soap_check_xml_ref(zval *data, xmlNodePtr node) + static void soap_add_xml_ref(zval *data, xmlNodePtr node) + { + if (SOAP_GLOBAL(ref_map)) { ++ Z_TRY_ADDREF_P(data); + zend_hash_index_update(SOAP_GLOBAL(ref_map), (zend_ulong)node, data); + } + } +@@ -3472,7 +3473,7 @@ void encode_reset_ns() + } else { + SOAP_GLOBAL(ref_map) = emalloc(sizeof(HashTable)); + } +- zend_hash_init(SOAP_GLOBAL(ref_map), 0, NULL, NULL, 0); ++ zend_hash_init(SOAP_GLOBAL(ref_map), 0, NULL, ZVAL_PTR_DTOR, 0); + } + + void encode_finish() +diff --git a/ext/soap/tests/GHSA-85c2-q967-79q5.phpt b/ext/soap/tests/GHSA-85c2-q967-79q5.phpt +new file mode 100644 +index 00000000000..8bcac26ad18 +--- /dev/null ++++ b/ext/soap/tests/GHSA-85c2-q967-79q5.phpt +@@ -0,0 +1,61 @@ ++--TEST-- ++GHSA-85c2-q967-79q5: Stale SOAP_GLOBAL(ref_map) pointer with Apache Map ++--CREDITS-- ++brettgervasoni ++--EXTENSIONS-- ++soap ++--FILE-- ++ ++ ++ ++ ++ ++ ++ ++ foo ++ bar ++ ++ ++ foo ++ baz ++ ++ ++ ++ ++ ++ ++XML; ++ ++$s = new SoapServer(null, ['uri' => 'urn:a']); ++$s->setClass(Handler::class); ++$s->handle($envelope); ++var_dump($result); ++ ++?> ++--EXPECTF-- ++ ++ ++array(2) { ++ [0]=> ++ array(1) { ++ ["foo"]=> ++ string(3) "baz" ++ } ++ [1]=> ++ object(stdClass)#%d (1) { ++ ["object"]=> ++ string(3) "bar" ++ } ++} +-- +2.54.0 + diff --git a/php-cve-2026-6735.patch b/php-cve-2026-6735.patch new file mode 100644 index 0000000..844bccb --- /dev/null +++ b/php-cve-2026-6735.patch @@ -0,0 +1,2697 @@ +From 382b1902c46ae1965b7973b801d007366a7851c2 Mon Sep 17 00:00:00 2001 +From: Jakub Zelenka +Date: Sun, 3 May 2026 20:01:41 +0200 +Subject: [PATCH 5/6] GHSA-7qg2-v9fj-4mwv: [fpm] XSS within status endpoint + +Fixes GHSA-7qg2-v9fj-4mwv +Fixes CVE-2026-6735 + +(cherry picked from commit 99a5ad7441de9914246c7863adb6997396008b9d) +(cherry picked from commit cc2960e782eb5cc262d7bd572a7d18979a811954) +(cherry picked from commit 62daef7b73108ceda2545862cde0673f252ba2d2) +(cherry picked from commit aeaf48ca0bceba42b9595dff30d9e96029c54613) + +backport some new FPM tester features + +(cherry picked from commit 8b1746466f9fcf248f9879fabfa356106d365da0) +(cherry picked from commit 8e0efa0f20484c8bbfdb8671d61b232b70e2bd0a) +(cherry picked from commit 9be5be215954c010087134964d4fd3ab907cdbda) +--- + sapi/fpm/fpm/fpm_status.c | 31 +- + sapi/fpm/tests/fcgi.inc | 192 ++- + .../tests/ghsa-7qg2-v9fj-4mwv-status-xss.phpt | 48 + + sapi/fpm/tests/logtool.inc | 476 ++++++ + sapi/fpm/tests/response.inc | 281 ++++ + sapi/fpm/tests/tester.inc | 1279 +++++++++++++++++ + 6 files changed, 2244 insertions(+), 63 deletions(-) + create mode 100644 sapi/fpm/tests/ghsa-7qg2-v9fj-4mwv-status-xss.phpt + create mode 100644 sapi/fpm/tests/logtool.inc + create mode 100644 sapi/fpm/tests/response.inc + create mode 100644 sapi/fpm/tests/tester.inc + +diff --git a/sapi/fpm/fpm/fpm_status.c b/sapi/fpm/fpm/fpm_status.c +index 666a1d9aba5..9693f0342ae 100644 +--- a/sapi/fpm/fpm/fpm_status.c ++++ b/sapi/fpm/fpm/fpm_status.c +@@ -386,9 +386,10 @@ int fpm_status_handle_request(void) /* {{{ */ + + /* no need to test the var 'full' */ + if (full_syntax) { +- int i, first; +- zend_string *tmp_query_string; +- char *query_string; ++ unsigned int i; ++ int first; ++ zend_string *tmp_query_string, *tmp_request_uri_string; ++ char *query_string, *request_uri_string; + struct timeval duration, now; + #ifdef HAVE_FPM_LQ + float cpu; +@@ -415,13 +416,30 @@ int fpm_status_handle_request(void) /* {{{ */ + } + } + ++ request_uri_string = NULL; ++ tmp_request_uri_string = NULL; ++ if (proc.request_uri[0] != '\0') { ++ if (encode) { ++ tmp_request_uri_string = php_escape_html_entities_ex( ++ (unsigned char*)proc.request_uri, ++ strlen(proc.request_uri), 1, ENT_DISALLOWED | ENT_HTML_DOC_XML1 | ENT_COMPAT, ++ NULL, /* double_encode */ 1); ++ request_uri_string = ZSTR_VAL(tmp_request_uri_string); ++ } else { ++ request_uri_string = proc.request_uri; ++ } ++ } ++ + query_string = NULL; + tmp_query_string = NULL; + if (proc.query_string[0] != '\0') { + if (!encode) { + query_string = proc.query_string; + } else { +- tmp_query_string = php_escape_html_entities_ex((unsigned char *)proc.query_string, strlen(proc.query_string), 1, ENT_HTML_IGNORE_ERRORS & ENT_COMPAT, NULL, 1); ++ tmp_query_string = php_escape_html_entities_ex( ++ (unsigned char*)proc.query_string, ++ strlen(proc.query_string), 1, ENT_DISALLOWED | ENT_HTML_DOC_XML1 | ENT_COMPAT, ++ NULL, /* double_encode */ 1); + query_string = ZSTR_VAL(tmp_query_string); + } + } +@@ -449,7 +467,7 @@ int fpm_status_handle_request(void) /* {{{ */ + proc.requests, + duration.tv_sec * 1000000UL + duration.tv_usec, + proc.request_method[0] != '\0' ? proc.request_method : "-", +- proc.request_uri[0] != '\0' ? proc.request_uri : "-", ++ request_uri_string ? request_uri_string : "-", + query_string ? "?" : "", + query_string ? query_string : "", + proc.content_length, +@@ -462,6 +480,9 @@ int fpm_status_handle_request(void) /* {{{ */ + PUTS(buffer); + efree(buffer); + ++ if (tmp_request_uri_string) { ++ zend_string_free(tmp_request_uri_string); ++ } + if (tmp_query_string) { + zend_string_free(tmp_query_string); + } +diff --git a/sapi/fpm/tests/fcgi.inc b/sapi/fpm/tests/fcgi.inc +index b31676260da..07603a808e0 100644 +--- a/sapi/fpm/tests/fcgi.inc ++++ b/sapi/fpm/tests/fcgi.inc +@@ -72,25 +72,25 @@ class Client + + /** + * Socket +- * @var Resource ++ * @var resource + */ + private $_sock = null; + + /** + * Host +- * @var String ++ * @var string + */ + private $_host = null; + + /** + * Port +- * @var Integer ++ * @var int + */ + private $_port = null; + + /** + * Keep Alive +- * @var Boolean ++ * @var bool + */ + private $_keepAlive = false; + +@@ -110,27 +110,27 @@ class Client + + /** + * Use persistent sockets to connect to backend +- * @var Boolean ++ * @var bool + */ + private $_persistentSocket = false; + + /** + * Connect timeout in milliseconds +- * @var Integer ++ * @var int + */ + private $_connectTimeout = 5000; + + /** + * Read/Write timeout in milliseconds +- * @var Integer ++ * @var int + */ + private $_readWriteTimeout = 5000; + + /** + * Constructor + * +- * @param String $host Host of the FastCGI application +- * @param Integer $port Port of the FastCGI application ++ * @param string $host Host of the FastCGI application ++ * @param int $port Port of the FastCGI application + */ + public function __construct($host, $port) + { +@@ -138,15 +138,25 @@ class Client + $this->_port = $port; + } + ++ /** ++ * Get host. ++ * ++ * @return string ++ */ ++ public function getHost() ++ { ++ return $this->_host; ++ } ++ + /** + * Define whether or not the FastCGI application should keep the connection + * alive at the end of a request + * +- * @param Boolean $b true if the connection should stay alive, false otherwise ++ * @param bool $b true if the connection should stay alive, false otherwise + */ + public function setKeepAlive($b) + { +- $this->_keepAlive = (boolean)$b; ++ $this->_keepAlive = (bool)$b; + if (!$this->_keepAlive && $this->_sock) { + fclose($this->_sock); + } +@@ -155,7 +165,7 @@ class Client + /** + * Get the keep alive status + * +- * @return Boolean true if the connection should stay alive, false otherwise ++ * @return bool true if the connection should stay alive, false otherwise + */ + public function getKeepAlive() + { +@@ -166,12 +176,12 @@ class Client + * Define whether or not PHP should attempt to re-use sockets opened by previous + * request for efficiency + * +- * @param Boolean $b true if persistent socket should be used, false otherwise ++ * @param bool $b true if persistent socket should be used, false otherwise + */ + public function setPersistentSocket($b) + { + $was_persistent = ($this->_sock && $this->_persistentSocket); +- $this->_persistentSocket = (boolean)$b; ++ $this->_persistentSocket = (bool)$b; + if (!$this->_persistentSocket && $was_persistent) { + fclose($this->_sock); + } +@@ -180,7 +190,7 @@ class Client + /** + * Get the pesistent socket status + * +- * @return Boolean true if the socket should be persistent, false otherwise ++ * @return bool true if the socket should be persistent, false otherwise + */ + public function getPersistentSocket() + { +@@ -191,7 +201,7 @@ class Client + /** + * Set the connect timeout + * +- * @param Integer number of milliseconds before connect will timeout ++ * @param int number of milliseconds before connect will timeout + */ + public function setConnectTimeout($timeoutMs) + { +@@ -201,7 +211,7 @@ class Client + /** + * Get the connect timeout + * +- * @return Integer number of milliseconds before connect will timeout ++ * @return int number of milliseconds before connect will timeout + */ + public function getConnectTimeout() + { +@@ -211,7 +221,7 @@ class Client + /** + * Set the read/write timeout + * +- * @param Integer number of milliseconds before read or write call will timeout ++ * @param int number of milliseconds before read or write call will timeout + */ + public function setReadWriteTimeout($timeoutMs) + { +@@ -222,7 +232,7 @@ class Client + /** + * Get the read timeout + * +- * @return Integer number of milliseconds before read will timeout ++ * @return int number of milliseconds before read will timeout + */ + public function getReadWriteTimeout() + { +@@ -232,14 +242,18 @@ class Client + /** + * Helper to avoid duplicating milliseconds to secs/usecs in a few places + * +- * @param Integer millisecond timeout +- * @return Boolean ++ * @param int millisecond timeout ++ * @return bool + */ + private function set_ms_timeout($timeoutMs) { + if (!$this->_sock) { + return false; + } +- return stream_set_timeout($this->_sock, floor($timeoutMs / 1000), ($timeoutMs % 1000) * 1000); ++ return stream_set_timeout( ++ $this->_sock, ++ floor($timeoutMs / 1000), ++ ($timeoutMs % 1000) * 1000 ++ ); + } + + +@@ -250,9 +264,21 @@ class Client + { + if (!$this->_sock) { + if ($this->_persistentSocket) { +- $this->_sock = pfsockopen($this->_host, $this->_port, $errno, $errstr, $this->_connectTimeout/1000); ++ $this->_sock = pfsockopen( ++ $this->_host, ++ $this->_port, ++ $errno, ++ $errstr, ++ $this->_connectTimeout/1000 ++ ); + } else { +- $this->_sock = fsockopen($this->_host, $this->_port, $errno, $errstr, $this->_connectTimeout/1000); ++ $this->_sock = fsockopen( ++ $this->_host, ++ $this->_port, ++ $errno, ++ $errstr, ++ $this->_connectTimeout/1000 ++ ); + } + + if (!$this->_sock) { +@@ -268,9 +294,10 @@ class Client + /** + * Build a FastCGI packet + * +- * @param Integer $type Type of the packet +- * @param String $content Content of the packet +- * @param Integer $requestId RequestId ++ * @param int $type Type of the packet ++ * @param string $content Content of the packet ++ * @param int $requestId RequestId ++ * @return string + */ + private function buildPacket($type, $content, $requestId = 1) + { +@@ -289,9 +316,9 @@ class Client + /** + * Build an FastCGI Name value pair + * +- * @param String $name Name +- * @param String $value Value +- * @return String FastCGI Name value pair ++ * @param string $name Name ++ * @param string $value Value ++ * @return string FastCGI Name value pair + */ + private function buildNvpair($name, $value) + { +@@ -302,14 +329,16 @@ class Client + $nvpair = chr($nlen); + } else { + /* nameLengthB3 & nameLengthB2 & nameLengthB1 & nameLengthB0 */ +- $nvpair = chr(($nlen >> 24) | 0x80) . chr(($nlen >> 16) & 0xFF) . chr(($nlen >> 8) & 0xFF) . chr($nlen & 0xFF); ++ $nvpair = chr(($nlen >> 24) | 0x80) . chr(($nlen >> 16) & 0xFF) ++ . chr(($nlen >> 8) & 0xFF) . chr($nlen & 0xFF); + } + if ($vlen < 128) { + /* valueLengthB0 */ + $nvpair .= chr($vlen); + } else { + /* valueLengthB3 & valueLengthB2 & valueLengthB1 & valueLengthB0 */ +- $nvpair .= chr(($vlen >> 24) | 0x80) . chr(($vlen >> 16) & 0xFF) . chr(($vlen >> 8) & 0xFF) . chr($vlen & 0xFF); ++ $nvpair .= chr(($vlen >> 24) | 0x80) . chr(($vlen >> 16) & 0xFF) ++ . chr(($vlen >> 8) & 0xFF) . chr($vlen & 0xFF); + } + /* nameData & valueData */ + return $nvpair . $name . $value; +@@ -318,7 +347,7 @@ class Client + /** + * Read a set of FastCGI Name value pairs + * +- * @param String $data Data containing the set of FastCGI NVPair ++ * @param string $data Data containing the set of FastCGI NVPair + * @return array of NVPair + */ + private function readNvpair($data, $length = null) +@@ -357,7 +386,7 @@ class Client + /** + * Decode a FastCGI Packet + * +- * @param String $data String containing all the packet ++ * @param string $data string containing all the packet + * @return array + */ + private function decodePacketHeader($data) +@@ -403,6 +432,7 @@ class Client + * + * @param array $requestedInfo information to retrieve + * @return array ++ * @throws \Exception + */ + public function getValues(array $requestedInfo) + { +@@ -423,11 +453,14 @@ class Client + } + + /** +- * Execute a request to the FastCGI application ++ * Execute a request to the FastCGI application and return response body + * + * @param array $params Array of parameters +- * @param String $stdin Content +- * @return String ++ * @param string $stdin Content ++ * @return string ++ * @throws ForbiddenException ++ * @throws TimedOutException ++ * @throws \Exception + */ + public function request(array $params, $stdin) + { +@@ -435,20 +468,38 @@ class Client + return $this->wait_for_response($id); + } + ++ /** ++ * Execute a request to the FastCGI application and return request data ++ * ++ * @param array $params Array of parameters ++ * @param string $stdin Content ++ * @return array ++ * @throws ForbiddenException ++ * @throws TimedOutException ++ * @throws \Exception ++ */ ++ public function request_data(array $params, $stdin) ++ { ++ $id = $this->async_request($params, $stdin); ++ return $this->wait_for_response_data($id); ++ } ++ + /** + * Execute a request to the FastCGI application asyncronously +- * ++ * + * This sends request to application and returns the assigned ID for that request. + * + * You should keep this id for later use with wait_for_response(). Ids are chosen randomly +- * rather than seqentially to guard against false-positives when using persistent sockets. +- * In that case it is possible that a delayed response to a request made by a previous script +- * invocation comes back on this socket and is mistaken for response to request made with same ID +- * during this request. ++ * rather than sequentially to guard against false-positives when using persistent sockets. ++ * In that case it is possible that a delayed response to a request made by a previous script ++ * invocation comes back on this socket and is mistaken for response to request made with same ++ * ID during this request. + * + * @param array $params Array of parameters +- * @param String $stdin Content +- * @return Integer ++ * @param string $stdin Content ++ * @return int ++ * @throws TimedOutException ++ * @throws \Exception + */ + public function async_request(array $params, $stdin) + { +@@ -460,10 +511,12 @@ class Client + // Using persistent sockets implies you want them keept alive by server! + $keepAlive = intval($this->_keepAlive || $this->_persistentSocket); + +- $request = $this->buildPacket(self::BEGIN_REQUEST +- ,chr(0) . chr(self::RESPONDER) . chr($keepAlive) . str_repeat(chr(0), 5) +- ,$id +- ); ++ $request = $this->buildPacket( ++ self::BEGIN_REQUEST, ++ chr(0) . chr(self::RESPONDER) . chr($keepAlive) ++ . str_repeat(chr(0), 5), ++ $id ++ ); + + $paramsRequest = ''; + foreach ($params as $key => $value) { +@@ -494,21 +547,26 @@ class Client + + $this->_requests[$id] = array( + 'state' => self::REQ_STATE_WRITTEN, +- 'response' => null ++ 'response' => null, ++ 'err_response' => null, ++ 'out_response' => null, + ); + + return $id; + } + + /** +- * Blocking call that waits for response to specific request +- * +- * @param Integer $requestId +- * @param Integer $timeoutMs [optional] the number of milliseconds to wait. Defaults to the ReadWriteTimeout value set. +- * @return string response body ++ * Blocking call that waits for response data of the specific request ++ * ++ * @param int $requestId ++ * @param int $timeoutMs [optional] the number of milliseconds to wait. ++ * @return array response data ++ * @throws ForbiddenException ++ * @throws TimedOutException ++ * @throws \Exception + */ +- public function wait_for_response($requestId, $timeoutMs = 0) { +- ++ public function wait_for_response_data($requestId, $timeoutMs = 0) ++ { + if (!isset($this->_requests[$requestId])) { + throw new \Exception('Invalid request id given'); + } +@@ -537,12 +595,15 @@ class Client + if ($resp['type'] == self::STDOUT || $resp['type'] == self::STDERR) { + if ($resp['type'] == self::STDERR) { + $this->_requests[$resp['requestId']]['state'] = self::REQ_STATE_ERR; ++ $this->_requests[$resp['requestId']]['err_response'] .= $resp['content']; ++ } else { ++ $this->_requests[$resp['requestId']]['out_response'] .= $resp['content']; + } + $this->_requests[$resp['requestId']]['response'] .= $resp['content']; + } + if ($resp['type'] == self::END_REQUEST) { + $this->_requests[$resp['requestId']]['state'] = self::REQ_STATE_OK; +- if ($resp['requestId'] == $requestId) { ++ if ($resp['requestId'] == $requestId) { + break; + } + } +@@ -586,7 +647,22 @@ class Client + throw new \Exception('Role value not known [UNKNOWN_ROLE]'); + break; + case self::REQUEST_COMPLETE: +- return $this->_requests[$requestId]['response']; ++ return $this->_requests[$requestId]; + } + } ++ ++ /** ++ * Blocking call that waits for response to specific request ++ * ++ * @param int $requestId ++ * @param int $timeoutMs [optional] the number of milliseconds to wait. ++ * @return string The response content. ++ * @throws ForbiddenException ++ * @throws TimedOutException ++ * @throws \Exception ++ */ ++ public function wait_for_response($requestId, $timeoutMs = 0) ++ { ++ return $this->wait_for_response_data($requestId, $timeoutMs)['response']; ++ } + } +diff --git a/sapi/fpm/tests/ghsa-7qg2-v9fj-4mwv-status-xss.phpt b/sapi/fpm/tests/ghsa-7qg2-v9fj-4mwv-status-xss.phpt +new file mode 100644 +index 00000000000..475bc130a42 +--- /dev/null ++++ b/sapi/fpm/tests/ghsa-7qg2-v9fj-4mwv-status-xss.phpt +@@ -0,0 +1,48 @@ ++--TEST-- ++FPM: GHSA-7qg2-v9fj-4mwv - status xss ++--SKIPIF-- ++ ++--FILE-- ++start(); ++$tester->expectLogStartNotices(); ++$responses = $tester ++ ->multiRequest([ ++ ['uri' => '/', 'query' => ''], ++ ['uri' => '/status', 'query' => 'full&html', 'delay' => 100000], ++ ]); ++var_dump(strpos($responses[1]->getBody(), '