summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--failed.txt2
-rw-r--r--php-cve-2025-1220.patch153
-rw-r--r--php-cve-2025-1735.patch490
-rw-r--r--php-cve-2025-6491.patch102
-rw-r--r--php-fpm.service2
-rw-r--r--php.spec18
6 files changed, 763 insertions, 4 deletions
diff --git a/failed.txt b/failed.txt
index c75ec72..36f704e 100644
--- a/failed.txt
+++ b/failed.txt
@@ -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
diff --git a/php.spec b/php.spec
index a41ff6c..af8d8f2 100644
--- a/php.spec
+++ b/php.spec
@@ -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