diff options
| author | Remi Collet <remi@remirepo.net> | 2025-07-03 10:13:58 +0200 | 
|---|---|---|
| committer | Remi Collet <remi@php.net> | 2025-07-03 10:13:58 +0200 | 
| commit | 0d81a871be1ee44feb37705fee67fd4ee6235d12 (patch) | |
| tree | 2a4ae58b6d32a50823fa2ce42a061eca2bb5281b | |
| parent | 6e7731500aec13a717379932940501d43e49d1bb (diff) | |
Fix pgsql extension does not check for errors during escaping
  CVE-2025-1735
Fix NULL Pointer Dereference in PHP SOAP Extension via Large XML Namespace Prefix
  CVE-2025-6491
Fix Null byte termination in hostnames
  CVE-2025-1220
| -rw-r--r-- | failed.txt | 2 | ||||
| -rw-r--r-- | php-cve-2025-1220.patch | 153 | ||||
| -rw-r--r-- | php-cve-2025-1735.patch | 490 | ||||
| -rw-r--r-- | php-cve-2025-6491.patch | 102 | ||||
| -rw-r--r-- | php-fpm.service | 2 | ||||
| -rw-r--r-- | php.spec | 18 | 
6 files changed, 763 insertions, 4 deletions
@@ -1,4 +1,4 @@ -===== 8.0.30-13 (2025-03-13) +===== 8.0.30-14 (2025-07-03)  $ grep -ar 'Tests failed' /var/lib/mock/*/build.log diff --git a/php-cve-2025-1220.patch b/php-cve-2025-1220.patch new file mode 100644 index 0000000..dad45e0 --- /dev/null +++ b/php-cve-2025-1220.patch @@ -0,0 +1,153 @@ +From 36150278addd8686a9899559241296094bd57282 Mon Sep 17 00:00:00 2001 +From: Jakub Zelenka <bukka@php.net> +Date: Thu, 10 Apr 2025 15:15:36 +0200 +Subject: [PATCH 2/4] Fix GHSA-3cr5-j632-f35r: Null byte in hostnames + +This fixes stream_socket_client() and fsockopen(). + +Specifically it adds a check to parse_ip_address_ex and it also makes +sure that the \0 is not ignored in fsockopen() hostname formatting. + +(cherry picked from commit cac8f7f1cf4939f55f06b68120040f057682d89c) +--- + ext/standard/fsock.c                          | 27 +++++++++++++++++-- + .../tests/network/ghsa-3cr5-j632-f35r.phpt    | 21 +++++++++++++++ + .../tests/streams/ghsa-3cr5-j632-f35r.phpt    | 26 ++++++++++++++++++ + main/streams/xp_socket.c                      |  9 ++++--- + 4 files changed, 78 insertions(+), 5 deletions(-) + create mode 100644 ext/standard/tests/network/ghsa-3cr5-j632-f35r.phpt + create mode 100644 ext/standard/tests/streams/ghsa-3cr5-j632-f35r.phpt + +diff --git a/ext/standard/fsock.c b/ext/standard/fsock.c +index a9c3cb0bf5d..636dbb6e359 100644 +--- a/ext/standard/fsock.c ++++ b/ext/standard/fsock.c +@@ -23,6 +23,28 @@ + #include "php_network.h" + #include "file.h" +  ++static size_t php_fsockopen_format_host_port(char **message, const char *prefix, size_t prefix_len, ++	const char *host, size_t host_len, zend_long port) ++{ ++    char portbuf[32]; ++    int portlen = snprintf(portbuf, sizeof(portbuf), ":" ZEND_LONG_FMT, port); ++    size_t total_len = prefix_len + host_len + portlen; ++ ++    char *result = emalloc(total_len + 1);  ++ ++	if (prefix_len > 0) { ++    	memcpy(result, prefix, prefix_len); ++	} ++    memcpy(result + prefix_len, host, host_len); ++    memcpy(result + prefix_len + host_len, portbuf, portlen); ++ ++    result[total_len] = '\0'; ++ ++    *message = result; ++ ++	return total_len; ++} ++ + /* {{{ php_fsockopen() */ +  + static void php_fsockopen_stream(INTERNAL_FUNCTION_PARAMETERS, int persistent) +@@ -62,11 +84,12 @@ static void php_fsockopen_stream(INTERNAL_FUNCTION_PARAMETERS, int persistent) + 	} +  + 	if (persistent) { +-		spprintf(&hashkey, 0, "pfsockopen__%s:" ZEND_LONG_FMT, host, port); ++		php_fsockopen_format_host_port(&hashkey, "pfsockopen__", strlen("pfsockopen__"), host, ++				host_len, port); + 	} +  + 	if (port > 0) { +-		hostname_len = spprintf(&hostname, 0, "%s:" ZEND_LONG_FMT, host, port); ++		hostname_len = php_fsockopen_format_host_port(&hostname, "", 0, host, host_len, port); + 	} else { + 		hostname_len = host_len; + 		hostname = host; +diff --git a/ext/standard/tests/network/ghsa-3cr5-j632-f35r.phpt b/ext/standard/tests/network/ghsa-3cr5-j632-f35r.phpt +new file mode 100644 +index 00000000000..7556c3be94c +--- /dev/null ++++ b/ext/standard/tests/network/ghsa-3cr5-j632-f35r.phpt +@@ -0,0 +1,21 @@ ++--TEST-- ++GHSA-3cr5-j632-f35r: Null byte termination in fsockopen()  ++--FILE-- ++<?php ++ ++$server = stream_socket_server("tcp://localhost:0"); ++ ++if (preg_match('/:(\d+)$/', stream_socket_get_name($server, false), $m)) { ++    $client = fsockopen("localhost\0.example.com", intval($m[1])); ++    var_dump($client); ++    if ($client) { ++        fclose($client); ++    } ++} ++fclose($server); ++ ++?> ++--EXPECTF-- ++ ++Warning: fsockopen(): Unable to connect to localhost:%d (The hostname must not contain null bytes) in %s ++bool(false) +diff --git a/ext/standard/tests/streams/ghsa-3cr5-j632-f35r.phpt b/ext/standard/tests/streams/ghsa-3cr5-j632-f35r.phpt +new file mode 100644 +index 00000000000..52f9263c99a +--- /dev/null ++++ b/ext/standard/tests/streams/ghsa-3cr5-j632-f35r.phpt +@@ -0,0 +1,26 @@ ++--TEST-- ++GHSA-3cr5-j632-f35r: Null byte termination in stream_socket_client()  ++--FILE-- ++<?php ++ ++$server = stream_socket_server("tcp://localhost:0"); ++$socket_name = stream_socket_get_name($server, false); ++ ++if (preg_match('/:(\d+)$/', $socket_name, $m)) { ++    $port = $m[1]; ++    $client = stream_socket_client("tcp://localhost\0.example.com:$port"); ++    var_dump($client); ++    if ($client) { ++        fclose($client); ++    } ++} else { ++    echo "Could not extract port from socket name: $socket_name\n"; ++} ++ ++fclose($server); ++ ++?> ++--EXPECTF-- ++ ++Warning: stream_socket_client(): Unable to connect to tcp://localhost\0.example.com:%d (The hostname must not contain null bytes) in %s ++bool(false) +diff --git a/main/streams/xp_socket.c b/main/streams/xp_socket.c +index 68df3366340..47f1bdd4b1d 100644 +--- a/main/streams/xp_socket.c ++++ b/main/streams/xp_socket.c +@@ -580,12 +580,15 @@ static inline char *parse_ip_address_ex(const char *str, size_t str_len, int *po + 	char *colon; + 	char *host = NULL; +  +-#ifdef HAVE_IPV6 +-	char *p; ++	if (memchr(str, '\0', str_len)) { ++		*err = strpprintf(0, "The hostname must not contain null bytes"); ++		return NULL; ++	} +  ++#ifdef HAVE_IPV6 + 	if (*(str) == '[' && str_len > 1) { + 		/* IPV6 notation to specify raw address with port (i.e. [fe80::1]:80) */ +-		p = memchr(str + 1, ']', str_len - 2); ++		char *p = memchr(str + 1, ']', str_len - 2); + 		if (!p || *(p + 1) != ':') { + 			if (get_err) { + 				*err = strpprintf(0, "Failed to parse IPv6 address \"%s\"", str); +--  +2.50.0 + diff --git a/php-cve-2025-1735.patch b/php-cve-2025-1735.patch new file mode 100644 index 0000000..60e7e2e --- /dev/null +++ b/php-cve-2025-1735.patch @@ -0,0 +1,490 @@ +From 7633d987cc11ee2601223e73cfdb8b31fed5980f Mon Sep 17 00:00:00 2001 +From: Jakub Zelenka <bukka@php.net> +Date: Tue, 4 Mar 2025 17:23:01 +0100 +Subject: [PATCH 3/4] Fix GHSA-hrwm-9436-5mv3: pgsql escaping no error checks + +This adds error checks for escape function is pgsql and pdo_pgsql +extensions. It prevents possibility of storing not properly escaped +data which could potentially lead to some security issues. + +(cherry picked from commit 9376aeef9f8ff81f2705b8016237ec3e30bdee44) +--- + ext/pdo_pgsql/pgsql_driver.c                 |  10 +- + ext/pdo_pgsql/tests/ghsa-hrwm-9436-5mv3.phpt |  22 ++++ + ext/pgsql/pgsql.c                            | 129 +++++++++++++++---- + ext/pgsql/tests/ghsa-hrwm-9436-5mv3.phpt     |  64 +++++++++ + 4 files changed, 202 insertions(+), 23 deletions(-) + create mode 100644 ext/pdo_pgsql/tests/ghsa-hrwm-9436-5mv3.phpt + create mode 100644 ext/pgsql/tests/ghsa-hrwm-9436-5mv3.phpt + +diff --git a/ext/pdo_pgsql/pgsql_driver.c b/ext/pdo_pgsql/pgsql_driver.c +index c90ef468907..218a306fa3c 100644 +--- a/ext/pdo_pgsql/pgsql_driver.c ++++ b/ext/pdo_pgsql/pgsql_driver.c +@@ -354,11 +354,15 @@ static int pgsql_handle_quoter(pdo_dbh_t *dbh, const char *unquoted, size_t unqu + 	unsigned char *escaped; + 	pdo_pgsql_db_handle *H = (pdo_pgsql_db_handle *)dbh->driver_data; + 	size_t tmp_len; ++	int err; +  + 	switch (paramtype) { + 		case PDO_PARAM_LOB: + 			/* escapedlen returned by PQescapeBytea() accounts for trailing 0 */ + 			escaped = PQescapeByteaConn(H->server, (unsigned char *)unquoted, unquotedlen, &tmp_len); ++			if (escaped == NULL) { ++				return 0; ++			} + 			*quotedlen = tmp_len + 1; + 			*quoted = emalloc(*quotedlen + 1); + 			memcpy((*quoted)+1, escaped, *quotedlen-2); +@@ -370,7 +374,11 @@ static int pgsql_handle_quoter(pdo_dbh_t *dbh, const char *unquoted, size_t unqu + 		default: + 			*quoted = safe_emalloc(2, unquotedlen, 3); + 			(*quoted)[0] = '\''; +-			*quotedlen = PQescapeStringConn(H->server, *quoted + 1, unquoted, unquotedlen, NULL); ++			*quotedlen = PQescapeStringConn(H->server, *quoted + 1, unquoted, unquotedlen, &err); ++			if (err) { ++				efree(*quoted); ++				return 0; ++			} + 			(*quoted)[*quotedlen + 1] = '\''; + 			(*quoted)[*quotedlen + 2] = '\0'; + 			*quotedlen += 2; +diff --git a/ext/pdo_pgsql/tests/ghsa-hrwm-9436-5mv3.phpt b/ext/pdo_pgsql/tests/ghsa-hrwm-9436-5mv3.phpt +new file mode 100644 +index 00000000000..60e13613d04 +--- /dev/null ++++ b/ext/pdo_pgsql/tests/ghsa-hrwm-9436-5mv3.phpt +@@ -0,0 +1,22 @@ ++--TEST-- ++#GHSA-hrwm-9436-5mv3: pdo_pgsql extension does not check for errors during escaping ++--SKIPIF-- ++<?php ++if (!extension_loaded('pdo') || !extension_loaded('pdo_pgsql')) die('skip not loaded'); ++require_once dirname(__FILE__) . '/../../../ext/pdo/tests/pdo_test.inc'; ++require_once dirname(__FILE__) . '/config.inc'; ++PDOTest::skip(); ++?> ++--FILE-- ++<?php ++require_once dirname(__FILE__) . '/../../../ext/pdo/tests/pdo_test.inc'; ++require_once dirname(__FILE__) . '/config.inc'; ++$db = PDOTest::test_factory(dirname(__FILE__) . '/common.phpt'); ++$db->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); ++ ++$invalid = "ABC\xff\x30';"; ++var_dump($db->quote($invalid)); ++ ++?> ++--EXPECT-- ++bool(false) +diff --git a/ext/pgsql/pgsql.c b/ext/pgsql/pgsql.c +index 588f481a498..e9a68a8555f 100644 +--- a/ext/pgsql/pgsql.c ++++ b/ext/pgsql/pgsql.c +@@ -3298,10 +3298,16 @@ PHP_FUNCTION(pg_escape_string) +  + 	to = zend_string_safe_alloc(ZSTR_LEN(from), 2, 0, 0); + 	if (link) { ++		int err; + 		if ((pgsql = (PGconn *)zend_fetch_resource2(link, "PostgreSQL link", le_link, le_plink)) == NULL) { + 			RETURN_THROWS(); + 		} +-		ZSTR_LEN(to) = PQescapeStringConn(pgsql, ZSTR_VAL(to), ZSTR_VAL(from), ZSTR_LEN(from), NULL); ++		ZSTR_LEN(to) = PQescapeStringConn(pgsql, ZSTR_VAL(to), ZSTR_VAL(from), ZSTR_LEN(from), &err); ++		if (err) { ++			zend_argument_value_error(ZEND_NUM_ARGS(), "Escaping string failed"); ++			zend_string_efree(to); ++			RETURN_THROWS(); ++		} + 	} else + 	{ + 		ZSTR_LEN(to) = PQescapeString(ZSTR_VAL(to), ZSTR_VAL(from), ZSTR_LEN(from)); +@@ -3344,6 +3350,10 @@ PHP_FUNCTION(pg_escape_bytea) + 		to = (char *)PQescapeByteaConn(pgsql, (unsigned char *)from, (size_t)from_len, &to_len); + 	} else + 		to = (char *)PQescapeBytea((unsigned char*)from, from_len, &to_len); ++	if (to == NULL) { ++		zend_argument_value_error(ZEND_NUM_ARGS(), "Escape failure"); ++		RETURN_THROWS(); ++	} +  + 	RETVAL_STRINGL(to, to_len-1); /* to_len includes additional '\0' */ + 	PQfreemem(to); +@@ -4251,7 +4261,7 @@ PHP_PGSQL_API int php_pgsql_meta_data(PGconn *pg_link, const char *table_name, z + 	char *escaped; + 	smart_str querystr = {0}; + 	size_t new_len; +-	int i, num_rows; ++	int i, num_rows, err; + 	zval elem; +  + 	ZEND_ASSERT(*table_name); +@@ -4290,7 +4300,14 @@ PHP_PGSQL_API int php_pgsql_meta_data(PGconn *pg_link, const char *table_name, z + 						  "WHERE a.attnum > 0 AND c.relname = '"); + 	} + 	escaped = (char *)safe_emalloc(strlen(tmp_name2), 2, 1); +-	new_len = PQescapeStringConn(pg_link, escaped, tmp_name2, strlen(tmp_name2), NULL); ++	new_len = PQescapeStringConn(pg_link, escaped, tmp_name2, strlen(tmp_name2), &err); ++	if (err) { ++		php_error_docref(NULL, E_WARNING, "Escaping table name '%s' failed", table_name); ++		efree(src); ++		efree(escaped); ++		smart_str_free(&querystr); ++		return FAILURE; ++	} + 	if (new_len) { + 		smart_str_appendl(&querystr, escaped, new_len); + 	} +@@ -4298,7 +4315,14 @@ PHP_PGSQL_API int php_pgsql_meta_data(PGconn *pg_link, const char *table_name, z +  + 	smart_str_appends(&querystr, "' AND n.nspname = '"); + 	escaped = (char *)safe_emalloc(strlen(tmp_name), 2, 1); +-	new_len = PQescapeStringConn(pg_link, escaped, tmp_name, strlen(tmp_name), NULL); ++	new_len = PQescapeStringConn(pg_link, escaped, tmp_name, strlen(tmp_name), &err); ++	if (err) { ++		php_error_docref(NULL, E_WARNING, "Escaping table namespace '%s' failed", table_name); ++		efree(src); ++		efree(escaped); ++		smart_str_free(&querystr); ++		return FAILURE; ++	} + 	if (new_len) { + 		smart_str_appendl(&querystr, escaped, new_len); + 	} +@@ -4575,7 +4599,7 @@ PHP_PGSQL_API int php_pgsql_convert(PGconn *pg_link, const char *table_name, con + { + 	zend_string *field = NULL; + 	zval meta, *def, *type, *not_null, *has_default, *is_enum, *val, new_val; +-	int err = 0, skip_field; ++	int err = 0, escape_err = 0, skip_field; + 	php_pgsql_data_type data_type; +  + 	ZEND_ASSERT(pg_link != NULL); +@@ -4829,10 +4853,14 @@ PHP_PGSQL_API int php_pgsql_convert(PGconn *pg_link, const char *table_name, con + 							/* PostgreSQL ignores \0 */ + 							str = zend_string_alloc(Z_STRLEN_P(val) * 2, 0); + 							/* better to use PGSQLescapeLiteral since PGescapeStringConn does not handle special \ */ +-							ZSTR_LEN(str) = PQescapeStringConn(pg_link, ZSTR_VAL(str), Z_STRVAL_P(val), Z_STRLEN_P(val), NULL); +-							str = zend_string_truncate(str, ZSTR_LEN(str), 0); +-							ZVAL_NEW_STR(&new_val, str); +-							php_pgsql_add_quotes(&new_val, 1); ++							ZSTR_LEN(str) = PQescapeStringConn(pg_link, ZSTR_VAL(str), Z_STRVAL_P(val), Z_STRLEN_P(val), &escape_err); ++							if (escape_err) { ++								err = 1; ++							} else { ++								str = zend_string_truncate(str, ZSTR_LEN(str), 0); ++								ZVAL_NEW_STR(&new_val, str); ++								php_pgsql_add_quotes(&new_val, 1); ++							} + 						} + 						break; +  +@@ -4854,7 +4882,15 @@ PHP_PGSQL_API int php_pgsql_convert(PGconn *pg_link, const char *table_name, con + 				} + 				PGSQL_CONV_CHECK_IGNORE(); + 				if (err) { +-					php_error_docref(NULL, E_NOTICE, "Expects NULL, string, long or double value for PostgreSQL '%s' (%s)", Z_STRVAL_P(type), ZSTR_VAL(field)); ++					if (escape_err) { ++						php_error_docref(NULL, E_NOTICE,  ++							"String value escaping failed for PostgreSQL '%s' (%s)", ++							Z_STRVAL_P(type), ZSTR_VAL(field)); ++					} else { ++						php_error_docref(NULL, E_NOTICE,  ++							"Expects NULL, string, long or double value for PostgreSQL '%s' (%s)", ++							Z_STRVAL_P(type), ZSTR_VAL(field)); ++					} + 				} + 				break; +  +@@ -5129,6 +5165,11 @@ PHP_PGSQL_API int php_pgsql_convert(PGconn *pg_link, const char *table_name, con + 							size_t to_len; + 							smart_str s = {0}; + 							tmp = PQescapeByteaConn(pg_link, (unsigned char *)Z_STRVAL_P(val), Z_STRLEN_P(val), &to_len); ++							if (tmp == NULL) { ++								php_error_docref(NULL, E_NOTICE, "Escaping value failed for %s field (%s)", Z_STRVAL_P(type), ZSTR_VAL(field)); ++								err = 1; ++								break; ++							} + 							ZVAL_STRINGL(&new_val, (char *)tmp, to_len - 1); /* PQescapeBytea's to_len includes additional '\0' */ + 							PQfreemem(tmp); + 							php_pgsql_add_quotes(&new_val, 1); +@@ -5210,6 +5251,12 @@ PHP_PGSQL_API int php_pgsql_convert(PGconn *pg_link, const char *table_name, con + 				zend_hash_update(Z_ARRVAL_P(result), field, &new_val); + 			} else { + 				char *escaped = PQescapeIdentifier(pg_link, ZSTR_VAL(field), ZSTR_LEN(field)); ++				if (escaped == NULL) { ++					/* This cannot fail because of invalid string but only due to failed memory allocation */ ++					php_error_docref(NULL, E_NOTICE, "Escaping field '%s' failed", ZSTR_VAL(field)); ++					err = 1; ++					break; ++				} + 				add_assoc_zval(result, escaped, &new_val); + 				PQfreemem(escaped); + 			} +@@ -5290,7 +5337,7 @@ static int do_exec(smart_str *querystr, ExecStatusType expect, PGconn *pg_link, + } + /* }}} */ +  +-static inline void build_tablename(smart_str *querystr, PGconn *pg_link, const char *table) /* {{{ */ ++static inline zend_result build_tablename(smart_str *querystr, PGconn *pg_link, const char *table) /* {{{ */ + { + 	size_t table_len = strlen(table); +  +@@ -5301,6 +5348,10 @@ static inline void build_tablename(smart_str *querystr, PGconn *pg_link, const c + 		smart_str_appendl(querystr, table, len); + 	} else { + 		char *escaped = PQescapeIdentifier(pg_link, table, len); ++		if (escaped == NULL) { ++			php_error_docref(NULL, E_NOTICE, "Failed to escape table name '%s'", table); ++			return FAILURE; ++		} + 		smart_str_appends(querystr, escaped); + 		PQfreemem(escaped); + 	} +@@ -5313,11 +5364,17 @@ static inline void build_tablename(smart_str *querystr, PGconn *pg_link, const c + 			smart_str_appendl(querystr, after_dot, len); + 		} else { + 			char *escaped = PQescapeIdentifier(pg_link, after_dot, len); ++			if (escaped == NULL) { ++				php_error_docref(NULL, E_NOTICE, "Failed to escape table name '%s'", table); ++				return FAILURE; ++			} + 			smart_str_appendc(querystr, '.'); + 			smart_str_appends(querystr, escaped); + 			PQfreemem(escaped); + 		} + 	} ++ ++	return SUCCESS; + } + /* }}} */ +  +@@ -5338,7 +5395,9 @@ PHP_PGSQL_API int php_pgsql_insert(PGconn *pg_link, const char *table, zval *var + 	ZVAL_UNDEF(&converted); + 	if (zend_hash_num_elements(Z_ARRVAL_P(var_array)) == 0) { + 		smart_str_appends(&querystr, "INSERT INTO "); +-		build_tablename(&querystr, pg_link, table); ++		if (build_tablename(&querystr, pg_link, table) == FAILURE) { ++			goto cleanup; ++		} + 		smart_str_appends(&querystr, " DEFAULT VALUES"); +  + 		goto no_values; +@@ -5354,7 +5413,9 @@ PHP_PGSQL_API int php_pgsql_insert(PGconn *pg_link, const char *table, zval *var + 	} +  + 	smart_str_appends(&querystr, "INSERT INTO "); +-	build_tablename(&querystr, pg_link, table); ++	if (build_tablename(&querystr, pg_link, table) == FAILURE) { ++		goto cleanup; ++	} + 	smart_str_appends(&querystr, " ("); +  + 	ZEND_HASH_FOREACH_STR_KEY(Z_ARRVAL_P(var_array), fld) { +@@ -5364,6 +5425,10 @@ PHP_PGSQL_API int php_pgsql_insert(PGconn *pg_link, const char *table, zval *var + 		} + 		if (opt & PGSQL_DML_ESCAPE) { + 			tmp = PQescapeIdentifier(pg_link, ZSTR_VAL(fld), ZSTR_LEN(fld) + 1); ++			if (tmp == NULL) { ++				php_error_docref(NULL, E_NOTICE, "Failed to escape field '%s'", ZSTR_VAL(fld)); ++				goto cleanup; ++			} + 			smart_str_appends(&querystr, tmp); + 			PQfreemem(tmp); + 		} else { +@@ -5375,15 +5440,19 @@ PHP_PGSQL_API int php_pgsql_insert(PGconn *pg_link, const char *table, zval *var + 	smart_str_appends(&querystr, ") VALUES ("); +  + 	/* make values string */ +-	ZEND_HASH_FOREACH_VAL(Z_ARRVAL_P(var_array), val) { ++	ZEND_HASH_FOREACH_STR_KEY_VAL(Z_ARRVAL_P(var_array), fld, val) { + 		/* we can avoid the key_type check here, because we tested it in the other loop */ + 		switch (Z_TYPE_P(val)) { + 			case IS_STRING: + 				if (opt & PGSQL_DML_ESCAPE) { +-					size_t new_len; +-					char *tmp; +-					tmp = (char *)safe_emalloc(Z_STRLEN_P(val), 2, 1); +-					new_len = PQescapeStringConn(pg_link, tmp, Z_STRVAL_P(val), Z_STRLEN_P(val), NULL); ++					int error; ++					char *tmp = safe_emalloc(Z_STRLEN_P(val), 2, 1); ++					size_t new_len = PQescapeStringConn(pg_link, tmp, Z_STRVAL_P(val), Z_STRLEN_P(val), &error); ++					if (error) { ++						php_error_docref(NULL, E_NOTICE, "Failed to escape field '%s' value", ZSTR_VAL(fld)); ++						efree(tmp); ++						goto cleanup; ++					} + 					smart_str_appendc(&querystr, '\''); + 					smart_str_appendl(&querystr, tmp, new_len); + 					smart_str_appendc(&querystr, '\''); +@@ -5537,6 +5606,10 @@ static inline int build_assignment_string(PGconn *pg_link, smart_str *querystr, + 		} + 		if (opt & PGSQL_DML_ESCAPE) { + 			char *tmp = PQescapeIdentifier(pg_link, ZSTR_VAL(fld), ZSTR_LEN(fld) + 1); ++			if (tmp == NULL) { ++				php_error_docref(NULL, E_NOTICE, "Failed to escape field '%s'", ZSTR_VAL(fld)); ++				return -1; ++			} + 			smart_str_appends(querystr, tmp); + 			PQfreemem(tmp); + 		} else { +@@ -5551,8 +5624,14 @@ static inline int build_assignment_string(PGconn *pg_link, smart_str *querystr, + 		switch (Z_TYPE_P(val)) { + 			case IS_STRING: + 				if (opt & PGSQL_DML_ESCAPE) { ++					int error; + 					char *tmp = (char *)safe_emalloc(Z_STRLEN_P(val), 2, 1); +-					size_t new_len = PQescapeStringConn(pg_link, tmp, Z_STRVAL_P(val), Z_STRLEN_P(val), NULL); ++					size_t new_len = PQescapeStringConn(pg_link, tmp, Z_STRVAL_P(val), Z_STRLEN_P(val), &error); ++					if (error) { ++						php_error_docref(NULL, E_NOTICE, "Failed to escape field '%s' value", ZSTR_VAL(fld)); ++						efree(tmp); ++						return -1; ++					} + 					smart_str_appendc(querystr, '\''); + 					smart_str_appendl(querystr, tmp, new_len); + 					smart_str_appendc(querystr, '\''); +@@ -5620,7 +5699,9 @@ PHP_PGSQL_API int php_pgsql_update(PGconn *pg_link, const char *table, zval *var + 	} +  + 	smart_str_appends(&querystr, "UPDATE "); +-	build_tablename(&querystr, pg_link, table); ++	if (build_tablename(&querystr, pg_link, table) == FAILURE) { ++		goto cleanup; ++	} + 	smart_str_appends(&querystr, " SET "); +  + 	if (build_assignment_string(pg_link, &querystr, Z_ARRVAL_P(var_array), 0, ",", 1, opt)) +@@ -5722,7 +5803,9 @@ PHP_PGSQL_API int php_pgsql_delete(PGconn *pg_link, const char *table, zval *ids + 	} +  + 	smart_str_appends(&querystr, "DELETE FROM "); +-	build_tablename(&querystr, pg_link, table); ++	if (build_tablename(&querystr, pg_link, table) == FAILURE) { ++		goto cleanup; ++	} + 	smart_str_appends(&querystr, " WHERE "); +  + 	if (build_assignment_string(pg_link, &querystr, Z_ARRVAL_P(ids_array), 1, " AND ", sizeof(" AND ")-1, opt)) +@@ -5860,7 +5943,9 @@ PHP_PGSQL_API void php_pgsql_result2array(PGresult *pg_result, zval *ret_array, + 	} +  + 	smart_str_appends(&querystr, "SELECT * FROM "); +-	build_tablename(&querystr, pg_link, table); ++	if (build_tablename(&querystr, pg_link, table) == FAILURE) { ++		goto cleanup; ++	} + 	smart_str_appends(&querystr, " WHERE "); +  + 	if (build_assignment_string(pg_link, &querystr, Z_ARRVAL_P(ids_array), 1, " AND ", sizeof(" AND ")-1, opt)) +diff --git a/ext/pgsql/tests/ghsa-hrwm-9436-5mv3.phpt b/ext/pgsql/tests/ghsa-hrwm-9436-5mv3.phpt +new file mode 100644 +index 00000000000..c1c5e05dce6 +--- /dev/null ++++ b/ext/pgsql/tests/ghsa-hrwm-9436-5mv3.phpt +@@ -0,0 +1,64 @@ ++--TEST-- ++#GHSA-hrwm-9436-5mv3: pgsql extension does not check for errors during escaping ++--EXTENSIONS-- ++pgsql ++--SKIPIF-- ++<?php include("skipif.inc"); ?> ++--FILE-- ++<?php ++ ++include 'config.inc'; ++define('FILE_NAME', __DIR__ . '/php.gif'); ++ ++$db = pg_connect($conn_str); ++pg_query($db, "DROP TABLE IF EXISTS ghsa_hrmw_9436_5mv3"); ++pg_query($db, "CREATE TABLE ghsa_hrmw_9436_5mv3 (bar text);"); ++ ++// pg_escape_literal/pg_escape_identifier ++ ++$invalid = "ABC\xff\x30';"; ++$flags = PGSQL_DML_NO_CONV | PGSQL_DML_ESCAPE; ++ ++var_dump(pg_insert($db, $invalid, ['bar' => 'test'])); // table name str escape in php_pgsql_meta_data ++var_dump(pg_insert($db, "$invalid.tbl", ['bar' => 'test'])); // schema name str escape in php_pgsql_meta_data ++var_dump(pg_insert($db, 'ghsa_hrmw_9436_5mv3', ['bar' => $invalid])); // converted value str escape in php_pgsql_convert ++var_dump(pg_insert($db, $invalid, [])); // ident escape in build_tablename ++var_dump(pg_insert($db, 'ghsa_hrmw_9436_5mv3', [$invalid => 'foo'], $flags)); // ident escape for field php_pgsql_insert ++var_dump(pg_insert($db, 'ghsa_hrmw_9436_5mv3', ['bar' => $invalid], $flags)); // str escape for field value in php_pgsql_insert ++var_dump(pg_update($db, 'ghsa_hrmw_9436_5mv3', ['bar' => 'val'], [$invalid => 'test'], $flags)); // ident escape in build_assignment_string ++var_dump(pg_update($db, 'ghsa_hrmw_9436_5mv3', ['bar' => 'val'], ['bar' => $invalid], $flags)); // invalid str escape in build_assignment_string ++var_dump(pg_escape_literal($db, $invalid)); // pg_escape_literal escape ++var_dump(pg_escape_identifier($db, $invalid)); // pg_escape_identifier escape ++ ++?> ++--EXPECTF-- ++ ++Warning: pg_insert(): Escaping table name 'ABC%s';' failed in %s on line %d ++bool(false) ++ ++Warning: pg_insert(): Escaping table namespace 'ABC%s';.tbl' failed in %s on line %d ++bool(false) ++ ++Notice: pg_insert(): String value escaping failed for PostgreSQL 'text' (bar) in %s on line %d ++bool(false) ++ ++Notice: pg_insert(): Failed to escape table name 'ABC%s';' in %s on line %d ++bool(false) ++ ++Notice: pg_insert(): Failed to escape field 'ABC%s';' in %s on line %d ++bool(false) ++ ++Notice: pg_insert(): Failed to escape field 'bar' value in %s on line %d ++bool(false) ++ ++Notice: pg_update(): Failed to escape field 'ABC%s';' in %s on line %d ++bool(false) ++ ++Notice: pg_update(): Failed to escape field 'bar' value in %s on line %d ++bool(false) ++ ++Warning: pg_escape_literal(): Failed to escape in %s on line %d ++bool(false) ++ ++Warning: pg_escape_identifier(): Failed to escape in %s on line %d ++bool(false) +--  +2.50.0 + +From 970548b94b7f23be32154d05a9545b10c98bfd62 Mon Sep 17 00:00:00 2001 +From: Remi Collet <remi@remirepo.net> +Date: Thu, 3 Jul 2025 09:32:25 +0200 +Subject: [PATCH 4/4] NEWS + +--- + NEWS | 14 ++++++++++++++ + 1 file changed, 14 insertions(+) + +diff --git a/NEWS b/NEWS +index 7db6f2660d2..c813f4f357a 100644 +--- a/NEWS ++++ b/NEWS +@@ -1,6 +1,20 @@ + PHP                                                                        NEWS + ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||| +  ++Backported from 8.1.33 ++ ++- PGSQL: ++  . Fixed GHSA-hrwm-9436-5mv3 (pgsql extension does not check for errors during ++    escaping). (CVE-2025-1735) (Jakub Zelenka) ++ ++- SOAP: ++  . Fixed GHSA-453j-q27h-5p8x (NULL Pointer Dereference in PHP SOAP Extension ++    via Large XML Namespace Prefix). (CVE-2025-6491) (Lekssays, nielsdos) ++ ++- Standard: ++  . Fixed GHSA-3cr5-j632-f35r (Null byte termination in hostnames). ++    (CVE-2025-1220) (Jakub Zelenka) ++ + Backported from 8.1.32 +  + - LibXML: +--  +2.50.0 + diff --git a/php-cve-2025-6491.patch b/php-cve-2025-6491.patch new file mode 100644 index 0000000..d4e4f36 --- /dev/null +++ b/php-cve-2025-6491.patch @@ -0,0 +1,102 @@ +From 1b7410a57f8a5fd1dd43854bcf7b9200517c9fd2 Mon Sep 17 00:00:00 2001 +From: Ahmed Lekssays <lekssaysahmed@gmail.com> +Date: Tue, 3 Jun 2025 09:00:55 +0000 +Subject: [PATCH 1/4] Fix GHSA-453j-q27h-5p8x + +Libxml versions prior to 2.13 cannot correctly handle a call to +xmlNodeSetName() with a name longer than 2G. It will leave the node +object in an invalid state with a NULL name. This later causes a NULL +pointer dereference when using the name during message serialization. + +To solve this, implement a workaround that resets the name to the +sentinel name if this situation arises. + +Versions of libxml of 2.13 and higher are not affected. + +This can be exploited if a SoapVar is created with a fully qualified +name that is longer than 2G. This would be possible if some application +code uses a namespace prefix from an untrusted source like from a remote +SOAP service. + +Co-authored-by: Niels Dossche <7771979+nielsdos@users.noreply.github.com> +(cherry picked from commit 9cb3d8d200f0c822b17bda35a2a67a97b039d3e1) +--- + ext/soap/soap.c                      |  6 ++-- + ext/soap/tests/soap_qname_crash.phpt | 48 ++++++++++++++++++++++++++++ + 2 files changed, 52 insertions(+), 2 deletions(-) + create mode 100644 ext/soap/tests/soap_qname_crash.phpt + +diff --git a/ext/soap/soap.c b/ext/soap/soap.c +index a8df136d665..08d6f285d28 100644 +--- a/ext/soap/soap.c ++++ b/ext/soap/soap.c +@@ -4143,8 +4143,10 @@ static xmlNodePtr serialize_zval(zval *val, sdlParamPtr param, char *paramName, + 	} + 	xmlParam = master_to_xml(enc, val, style, parent); + 	zval_ptr_dtor(&defval); +-	if (!strcmp((char*)xmlParam->name, "BOGUS")) { +-		xmlNodeSetName(xmlParam, BAD_CAST(paramName)); ++	if (xmlParam != NULL) {  ++		if (xmlParam->name == NULL || strcmp((char*)xmlParam->name, "BOGUS") == 0) { ++			xmlNodeSetName(xmlParam, BAD_CAST(paramName)); ++		} + 	} + 	return xmlParam; + } +diff --git a/ext/soap/tests/soap_qname_crash.phpt b/ext/soap/tests/soap_qname_crash.phpt +new file mode 100644 +index 00000000000..52177577788 +--- /dev/null ++++ b/ext/soap/tests/soap_qname_crash.phpt +@@ -0,0 +1,48 @@ ++--TEST-- ++Test SoapClient with excessively large QName prefix in SoapVar ++--EXTENSIONS-- ++soap ++--SKIPIF-- ++<?php ++if (PHP_INT_SIZE != 8) die("skip: 64-bit only"); ++?> ++--INI-- ++memory_limit=8G ++--FILE-- ++<?php ++ ++class TestSoapClient extends SoapClient { ++    public function __doRequest( ++        $request, ++        $location, ++        $action, ++        $version, ++        $one_way = false, ++    ): ?string { ++        die($request); ++    } ++} ++ ++$prefix = str_repeat("A", 2 * 1024 * 1024 * 1024); ++$qname = "{$prefix}:tag"; ++ ++echo "Attempting to create SoapVar with very large QName\n"; ++ ++$var = new SoapVar("value", XSD_QNAME, null, null, $qname); ++ ++echo "Attempting encoding\n"; ++ ++$options = [ ++    'location' => 'http://127.0.0.1/', ++    'uri' => 'urn:dummy', ++    'trace' => 1, ++    'exceptions' => true, ++]; ++$client = new TestSoapClient(null, $options); ++$client->__soapCall("DummyFunction", [$var]); ++?> ++--EXPECT-- ++Attempting to create SoapVar with very large QName ++Attempting encoding ++<?xml version="1.0" encoding="UTF-8"?> ++<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/" xmlns:ns1="urn:dummy" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:SOAP-ENC="http://schemas.xmlsoap.org/soap/encoding/" SOAP-ENV:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"><SOAP-ENV:Body><ns1:DummyFunction><param0 xsi:type="xsd:QName">value</param0></ns1:DummyFunction></SOAP-ENV:Body></SOAP-ENV:Envelope> +--  +2.50.0 + diff --git a/php-fpm.service b/php-fpm.service index 687dfc0..0712a11 100644 --- a/php-fpm.service +++ b/php-fpm.service @@ -4,7 +4,7 @@  [Unit]  Description=The PHP FastCGI Process Manager -After=syslog.target network.target +After=network.target  [Service]  Type=notify @@ -49,7 +49,7 @@  %global mysql_sock %(mysql_config --socket  2>/dev/null || echo /var/lib/mysql/mysql.sock) -%global oraclever 23.7 +%global oraclever 23.8  %global oraclemax 24  %global oraclelib 23.1  %global oracledir 23 @@ -125,7 +125,7 @@  Summary: PHP scripting language for creating dynamic web sites  Name:    %{?scl_prefix}php  Version: %{upver}%{?rcver:~%{rcver}}%{?gh_date:.%{gh_date}} -Release: 13%{?dist} +Release: 14%{?dist}  # All files licensed under PHP version 3.01, except  # Zend is licensed under Zend  # TSRM is licensed under BSD @@ -222,6 +222,9 @@ Patch214: php-cve-2025-1734.patch  Patch215: php-cve-2025-1861.patch  Patch216: php-cve-2025-1736.patch  Patch217: php-cve-2025-1219.patch +Patch218: php-cve-2025-6491.patch +Patch219: php-cve-2025-1220.patch +Patch220: php-cve-2025-1735.patch  # Fixes for tests (300+)  # Factory is droped from system tzdata @@ -1019,6 +1022,9 @@ rm ext/openssl/tests/p12_with_extra_certs.p12  %patch -P215 -p1 -b .cve1861  %patch -P216 -p1 -b .cve1736  %patch -P217 -p1 -b .cve1219 +%patch -P218 -p1 -b .cve6491 +%patch -P219 -p1 -b .cve1220 +%patch -P220 -p1 -b .cve1735  # Fixes for tests  %patch -P300 -p1 -b .datetests @@ -1915,6 +1921,14 @@ EOF  %changelog +* Thu Jul  3 2025 Remi Collet <remi@remirepo.net> - 8.0.30-14 +- Fix pgsql extension does not check for errors during escaping +  CVE-2025-1735 +- Fix NULL Pointer Dereference in PHP SOAP Extension via Large XML Namespace Prefix +  CVE-2025-6491 +- Fix Null byte termination in hostnames +  CVE-2025-1220 +  * Thu Mar 13 2025 Remi Collet <remi@remirepo.net> - 8.0.30-13  - Fix libxml streams use wrong `content-type` header when requesting a redirected resource    CVE-2025-1219  | 
