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
Fix soap memory corruption
Fix ldap_set_option() not applied on different ldap connections

Resolves: RHEL-116532
Resolves: RHEL-80383
Resolves: RHEL-82078
This commit is contained in:
Remi Collet 2025-10-03 09:15:09 +02:00
parent a52e0350ba
commit 24221dbf44
6 changed files with 1521 additions and 1 deletions

655
php-8.0.30-ldap.patch Normal file
View File

@ -0,0 +1,655 @@
From 8f69248f7631cbd0515604c5d7e68fc6ee0c5b97 Mon Sep 17 00:00:00 2001
From: Remi Collet <remi@remirepo.net>
Date: Wed, 26 Feb 2025 17:26:08 +0100
Subject: [PATCH 1/4] Fix #17776 LDAP_OPT_X_TLS_REQUIRE_CERT can't be
overridden
(cherry picked from commit 389de7c6bf59e14145e932f4d3c0171803a3ba97)
---
ext/ldap/ldap.c | 89 ++++++++++++++++++------
ext/ldap/php_ldap.h | 1 +
ext/ldap/tests/ldap_start_tls_basic.phpt | 21 +++++-
ext/ldap/tests/ldaps_basic.phpt | 55 +++++++++++++++
4 files changed, 141 insertions(+), 25 deletions(-)
create mode 100644 ext/ldap/tests/ldaps_basic.phpt
diff --git a/ext/ldap/ldap.c b/ext/ldap/ldap.c
index 6661310d055..299cd6c855b 100644
--- a/ext/ldap/ldap.c
+++ b/ext/ldap/ldap.c
@@ -715,6 +715,21 @@ static PHP_GINIT_FUNCTION(ldap)
}
/* }}} */
+/* {{{ PHP_RINIT_FUNCTION */
+static PHP_RINIT_FUNCTION(ldap)
+{
+#if defined(COMPILE_DL_LDAP) && defined(ZTS)
+ ZEND_TSRMLS_CACHE_UPDATE();
+#endif
+
+ /* needed before first connect and after TLS option changes */
+ LDAPG(tls_newctx) = true;
+
+ return SUCCESS;
+}
+/* }}} */
+
+
/* {{{ PHP_MINIT_FUNCTION */
PHP_MINIT_FUNCTION(ldap)
{
@@ -1037,6 +1052,20 @@ PHP_FUNCTION(ldap_connect)
snprintf( url, urllen, "ldap://%s:" ZEND_LONG_FMT, host, port );
}
+#ifdef LDAP_OPT_X_TLS_NEWCTX
+ if (LDAPG(tls_newctx) && url && !strncmp(url, "ldaps:", 6)) {
+ int val = 0;
+
+ /* ensure all pending TLS options are applied in a new context */
+ if (ldap_set_option(NULL, LDAP_OPT_X_TLS_NEWCTX, &val) != LDAP_OPT_SUCCESS) {
+ zval_ptr_dtor(return_value);
+ php_error_docref(NULL, E_WARNING, "Could not create new security context");
+ RETURN_FALSE;
+ }
+ LDAPG(tls_newctx) = false;
+ }
+#endif
+
#ifdef LDAP_API_FEATURE_X_OPENLDAP
/* ldap_init() is deprecated, use ldap_initialize() instead.
*/
@@ -3135,15 +3164,7 @@ PHP_FUNCTION(ldap_set_option)
}
switch (option) {
- /* options with int value */
- case LDAP_OPT_DEREF:
- case LDAP_OPT_SIZELIMIT:
- case LDAP_OPT_TIMELIMIT:
- case LDAP_OPT_PROTOCOL_VERSION:
- case LDAP_OPT_ERROR_NUMBER:
-#ifdef LDAP_OPT_DEBUG_LEVEL
- case LDAP_OPT_DEBUG_LEVEL:
-#endif
+ /* TLS options with int value */
#ifdef LDAP_OPT_X_TLS_REQUIRE_CERT
case LDAP_OPT_X_TLS_REQUIRE_CERT:
#endif
@@ -3152,6 +3173,18 @@ PHP_FUNCTION(ldap_set_option)
#endif
#ifdef LDAP_OPT_X_TLS_PROTOCOL_MIN
case LDAP_OPT_X_TLS_PROTOCOL_MIN:
+#endif
+ /* TLS option change requires resetting TLS context */
+ LDAPG(tls_newctx) = true;
+ ZEND_FALLTHROUGH;
+ /* other options with int value */
+ case LDAP_OPT_DEREF:
+ case LDAP_OPT_SIZELIMIT:
+ case LDAP_OPT_TIMELIMIT:
+ case LDAP_OPT_PROTOCOL_VERSION:
+ case LDAP_OPT_ERROR_NUMBER:
+#ifdef LDAP_OPT_DEBUG_LEVEL
+ case LDAP_OPT_DEBUG_LEVEL:
#endif
#ifdef LDAP_OPT_X_KEEPALIVE_IDLE
case LDAP_OPT_X_KEEPALIVE_IDLE:
@@ -3208,17 +3241,7 @@ PHP_FUNCTION(ldap_set_option)
}
} break;
#endif
- /* options with string value */
- case LDAP_OPT_ERROR_STRING:
-#ifdef LDAP_OPT_HOST_NAME
- case LDAP_OPT_HOST_NAME:
-#endif
-#ifdef HAVE_LDAP_SASL
- case LDAP_OPT_X_SASL_MECH:
- case LDAP_OPT_X_SASL_REALM:
- case LDAP_OPT_X_SASL_AUTHCID:
- case LDAP_OPT_X_SASL_AUTHZID:
-#endif
+ /* TLS options with string value */
#if (LDAP_API_VERSION > 2000)
case LDAP_OPT_X_TLS_CACERTDIR:
case LDAP_OPT_X_TLS_CACERTFILE:
@@ -3232,6 +3255,20 @@ PHP_FUNCTION(ldap_set_option)
#endif
#ifdef LDAP_OPT_X_TLS_DHFILE
case LDAP_OPT_X_TLS_DHFILE:
+#endif
+ /* TLS option change requires resetting TLS context */
+ LDAPG(tls_newctx) = true;
+ ZEND_FALLTHROUGH;
+ /* other options with string value */
+ case LDAP_OPT_ERROR_STRING:
+#ifdef LDAP_OPT_HOST_NAME
+ case LDAP_OPT_HOST_NAME:
+#endif
+#ifdef HAVE_LDAP_SASL
+ case LDAP_OPT_X_SASL_MECH:
+ case LDAP_OPT_X_SASL_REALM:
+ case LDAP_OPT_X_SASL_AUTHCID:
+ case LDAP_OPT_X_SASL_AUTHZID:
#endif
#ifdef LDAP_OPT_MATCHED_DN
case LDAP_OPT_MATCHED_DN:
@@ -3658,6 +3695,9 @@ PHP_FUNCTION(ldap_start_tls)
zval *link;
ldap_linkdata *ld;
int rc, protocol = LDAP_VERSION3;
+#ifdef LDAP_OPT_X_TLS_NEWCTX
+ int val = 0;
+#endif
if (zend_parse_parameters(ZEND_NUM_ARGS(), "r", &link) != SUCCESS) {
RETURN_THROWS();
@@ -3668,13 +3708,16 @@ PHP_FUNCTION(ldap_start_tls)
}
if (((rc = ldap_set_option(ld->link, LDAP_OPT_PROTOCOL_VERSION, &protocol)) != LDAP_SUCCESS) ||
+#ifdef LDAP_OPT_X_TLS_NEWCTX
+ (LDAPG(tls_newctx) && (rc = ldap_set_option(ld->link, LDAP_OPT_X_TLS_NEWCTX, &val)) != LDAP_OPT_SUCCESS) ||
+#endif
((rc = ldap_start_tls_s(ld->link, NULL, NULL)) != LDAP_SUCCESS)
) {
php_error_docref(NULL, E_WARNING,"Unable to start TLS: %s", ldap_err2string(rc));
RETURN_FALSE;
- } else {
- RETURN_TRUE;
}
+ LDAPG(tls_newctx) = false;
+ RETURN_TRUE;
}
/* }}} */
#endif
@@ -4178,7 +4221,7 @@ zend_module_entry ldap_module_entry = { /* {{{ */
ext_functions,
PHP_MINIT(ldap),
PHP_MSHUTDOWN(ldap),
- NULL,
+ PHP_RINIT(ldap),
NULL,
PHP_MINFO(ldap),
PHP_LDAP_VERSION,
diff --git a/ext/ldap/php_ldap.h b/ext/ldap/php_ldap.h
index fcda55e92fb..36941d952bf 100644
--- a/ext/ldap/php_ldap.h
+++ b/ext/ldap/php_ldap.h
@@ -39,6 +39,7 @@ PHP_MINFO_FUNCTION(ldap);
ZEND_BEGIN_MODULE_GLOBALS(ldap)
zend_long num_links;
zend_long max_links;
+ bool tls_newctx; /* create new TLS context before connect */
ZEND_END_MODULE_GLOBALS(ldap)
#if defined(ZTS) && defined(COMPILE_DL_LDAP)
diff --git a/ext/ldap/tests/ldap_start_tls_basic.phpt b/ext/ldap/tests/ldap_start_tls_basic.phpt
index a9e3c6c97ce..94d9e8e9552 100644
--- a/ext/ldap/tests/ldap_start_tls_basic.phpt
+++ b/ext/ldap/tests/ldap_start_tls_basic.phpt
@@ -8,11 +8,28 @@ Patrick Allaert <patrickallaert@php.net>
<?php require_once __DIR__ .'/skipifbindfailure.inc'; ?>
--FILE--
<?php
-require "connect.inc";
+require_once "connect.inc";
+// CI uses self signed certificate
+
+// No cert option - fails
+$link = ldap_connect($host, $port);
+ldap_set_option($link, LDAP_OPT_PROTOCOL_VERSION, $protocol_version);
+var_dump(@ldap_start_tls($link));
+
+// No cert check - passes
+$link = ldap_connect($host, $port);
+ldap_set_option($link, LDAP_OPT_PROTOCOL_VERSION, $protocol_version);
+ldap_set_option($link, LDAP_OPT_X_TLS_REQUIRE_CERT, LDAP_OPT_X_TLS_NEVER);
+var_dump(@ldap_start_tls($link));
+
+// With cert check - fails
$link = ldap_connect($host, $port);
ldap_set_option($link, LDAP_OPT_PROTOCOL_VERSION, $protocol_version);
-var_dump(ldap_start_tls($link));
+ldap_set_option($link, LDAP_OPT_X_TLS_REQUIRE_CERT, LDAP_OPT_X_TLS_DEMAND);
+var_dump(@ldap_start_tls($link));
?>
--EXPECT--
+bool(false)
bool(true)
+bool(false)
diff --git a/ext/ldap/tests/ldaps_basic.phpt b/ext/ldap/tests/ldaps_basic.phpt
new file mode 100644
index 00000000000..7a1a1383436
--- /dev/null
+++ b/ext/ldap/tests/ldaps_basic.phpt
@@ -0,0 +1,55 @@
+--TEST--
+ldap_connect() - Basic ldaps test
+--EXTENSIONS--
+ldap
+--XFAIL--
+Passes locally but fails on CI - need investigation (configuration ?)
+--SKIPIF--
+<?php require_once __DIR__ .'/skipifbindfailure.inc'; ?>
+--FILE--
+<?php
+require_once "connect.inc";
+
+$uri = "ldaps://$host:636";
+
+// CI uses self signed certificate
+
+// No cert option - fails
+$link = ldap_connect($uri);
+ldap_set_option($link, LDAP_OPT_PROTOCOL_VERSION, $protocol_version);
+var_dump(@ldap_bind($link, $user, $passwd));
+ldap_unbind($link);
+
+// No cert check - passes
+ldap_set_option(null, LDAP_OPT_X_TLS_REQUIRE_CERT, LDAP_OPT_X_TLS_ALLOW);
+$link = ldap_connect($uri);
+ldap_set_option($link, LDAP_OPT_PROTOCOL_VERSION, $protocol_version);
+var_dump(@ldap_bind($link, $user, $passwd));
+ldap_unbind($link);
+
+// No change to TLS options
+$link = ldap_connect($uri);
+ldap_set_option($link, LDAP_OPT_PROTOCOL_VERSION, $protocol_version);
+var_dump(@ldap_bind($link, $user, $passwd));
+ldap_unbind($link);
+
+// With cert check - fails
+ldap_set_option(null, LDAP_OPT_X_TLS_REQUIRE_CERT, LDAP_OPT_X_TLS_DEMAND);
+$link = ldap_connect($uri);
+ldap_set_option($link, LDAP_OPT_PROTOCOL_VERSION, $protocol_version);
+var_dump(@ldap_bind($link, $user, $passwd));
+ldap_unbind($link);
+
+// No change to TLS options
+$link = ldap_connect($uri);
+ldap_set_option($link, LDAP_OPT_PROTOCOL_VERSION, $protocol_version);
+var_dump(@ldap_bind($link, $user, $passwd));
+ldap_unbind($link);
+
+?>
+--EXPECT--
+bool(false)
+bool(true)
+bool(true)
+bool(false)
+bool(false)
--
2.43.7
From 398430a2ee29c30aad76fea54358e3d7c53c3357 Mon Sep 17 00:00:00 2001
From: Remi Collet <remi@remirepo.net>
Date: Tue, 13 May 2025 16:00:32 +0200
Subject: [PATCH 2/4] Fix GH-18529: ldap no longer respects TLS_CACERT from
ldaprc in ldap_start_tls() Regresion introduced in fix for GH-17776
- ensure TLS string options are properly inherited
workaround to openldap issue https://bugs.openldap.org/show_bug.cgi?id=10337
- fix ldaps/start_tls tests using LDAPNOINIT in ldaps/tls tests
(cherry picked from commit 2760a3ef9719dac2e53baf3dc2d8a3dd1227d88b)
---
ext/ldap/ldap.c | 49 ++++++++++++++++++++++--
ext/ldap/tests/ldap_start_tls_basic.phpt | 2 +
ext/ldap/tests/ldaps_basic.phpt | 4 +-
3 files changed, 49 insertions(+), 6 deletions(-)
diff --git a/ext/ldap/ldap.c b/ext/ldap/ldap.c
index 299cd6c855b..6d576b84bd4 100644
--- a/ext/ldap/ldap.c
+++ b/ext/ldap/ldap.c
@@ -3689,15 +3689,56 @@ PHP_FUNCTION(ldap_rename_ext)
/* }}} */
#ifdef HAVE_LDAP_START_TLS_S
+/*
+ Force new tls context creation with string options inherited from global
+ Workaround to https://bugs.openldap.org/show_bug.cgi?id=10337
+ */
+static int _php_ldap_tls_newctx(LDAP *ld)
+{
+ int val = 0, i, opts[] = {
+#if (LDAP_API_VERSION > 2000)
+ LDAP_OPT_X_TLS_CACERTDIR,
+ LDAP_OPT_X_TLS_CACERTFILE,
+ LDAP_OPT_X_TLS_CERTFILE,
+ LDAP_OPT_X_TLS_CIPHER_SUITE,
+ LDAP_OPT_X_TLS_KEYFILE,
+ LDAP_OPT_X_TLS_RANDOM_FILE,
+#endif
+#ifdef LDAP_OPT_X_TLS_CRLFILE
+ LDAP_OPT_X_TLS_CRLFILE,
+#endif
+#ifdef LDAP_OPT_X_TLS_DHFILE
+ LDAP_OPT_X_TLS_DHFILE,
+#endif
+#ifdef LDAP_OPT_X_TLS_ECNAME
+ LDAP_OPT_X_TLS_ECNAME,
+#endif
+ 0};
+
+ for (i=0 ; opts[i] ; i++) {
+ char *path = NULL;
+
+ ldap_get_option(ld, opts[i], &path);
+ if (path) { /* already set locally */
+ ldap_memfree(path);
+ } else {
+ ldap_get_option(NULL, opts[i], &path);
+ if (path) { /* set globally, inherit */
+ ldap_set_option(ld, opts[i], path);
+ ldap_memfree(path);
+ }
+ }
+ }
+
+ return ldap_set_option(ld, LDAP_OPT_X_TLS_NEWCTX, &val);
+}
+
/* {{{ Start TLS */
PHP_FUNCTION(ldap_start_tls)
{
zval *link;
ldap_linkdata *ld;
int rc, protocol = LDAP_VERSION3;
-#ifdef LDAP_OPT_X_TLS_NEWCTX
- int val = 0;
-#endif
if (zend_parse_parameters(ZEND_NUM_ARGS(), "r", &link) != SUCCESS) {
RETURN_THROWS();
@@ -3709,7 +3750,7 @@ PHP_FUNCTION(ldap_start_tls)
if (((rc = ldap_set_option(ld->link, LDAP_OPT_PROTOCOL_VERSION, &protocol)) != LDAP_SUCCESS) ||
#ifdef LDAP_OPT_X_TLS_NEWCTX
- (LDAPG(tls_newctx) && (rc = ldap_set_option(ld->link, LDAP_OPT_X_TLS_NEWCTX, &val)) != LDAP_OPT_SUCCESS) ||
+ (LDAPG(tls_newctx) && (rc = _php_ldap_tls_newctx(ld->link)) != LDAP_OPT_SUCCESS) ||
#endif
((rc = ldap_start_tls_s(ld->link, NULL, NULL)) != LDAP_SUCCESS)
) {
diff --git a/ext/ldap/tests/ldap_start_tls_basic.phpt b/ext/ldap/tests/ldap_start_tls_basic.phpt
index 94d9e8e9552..14720b57286 100644
--- a/ext/ldap/tests/ldap_start_tls_basic.phpt
+++ b/ext/ldap/tests/ldap_start_tls_basic.phpt
@@ -3,6 +3,8 @@ ldap_start_tls() - Basic ldap_start_tls test
--CREDITS--
Patrick Allaert <patrickallaert@php.net>
# Belgian PHP Testfest 2009
+--ENV--
+LDAPNOINIT=1
--SKIPIF--
<?php require_once __DIR__ .'/skipif.inc'; ?>
<?php require_once __DIR__ .'/skipifbindfailure.inc'; ?>
diff --git a/ext/ldap/tests/ldaps_basic.phpt b/ext/ldap/tests/ldaps_basic.phpt
index 7a1a1383436..9fa49a6ce79 100644
--- a/ext/ldap/tests/ldaps_basic.phpt
+++ b/ext/ldap/tests/ldaps_basic.phpt
@@ -2,8 +2,8 @@
ldap_connect() - Basic ldaps test
--EXTENSIONS--
ldap
---XFAIL--
-Passes locally but fails on CI - need investigation (configuration ?)
+--ENV--
+LDAPNOINIT=1
--SKIPIF--
<?php require_once __DIR__ .'/skipifbindfailure.inc'; ?>
--FILE--
--
2.43.7
From d79c713b18d67495dad3d8199ec093dd93404eac Mon Sep 17 00:00:00 2001
From: Jakub Zelenka <bukka@php.net>
Date: Tue, 27 May 2025 19:03:56 +0200
Subject: [PATCH 3/4] Fix GH-18529: additional inheriting of TLS int options
This is for LDAP_OPT_X_TLS_PROTOCOL_MIN and LDAP_OPT_X_TLS_PROTOCOL_MAX
It also adds a test that uses LDAPCONF with TLS max version lower than
the minimum TLS server version so it should always fail. However it
does not fial for the second case without this change which confirms
that the change works as expected.
Closes GH-18676
(cherry picked from commit eade5c17ead650b0735295214bbec84872465e87)
---
.github/scripts/setup-slapd.sh | 3 ++
ext/ldap/ldap.c | 32 ++++++++++++---
.../tests/ldap_start_tls_rc_max_version.conf | 1 +
.../tests/ldap_start_tls_rc_max_version.phpt | 41 +++++++++++++++++++
ext/ldap/tests/skipifbindfailure.inc | 33 +++++++++++++++
5 files changed, 105 insertions(+), 5 deletions(-)
create mode 100644 ext/ldap/tests/ldap_start_tls_rc_max_version.conf
create mode 100644 ext/ldap/tests/ldap_start_tls_rc_max_version.phpt
diff --git a/.github/scripts/setup-slapd.sh b/.github/scripts/setup-slapd.sh
index b9cb1a4ff7a..f2eb1269d1f 100755
--- a/.github/scripts/setup-slapd.sh
+++ b/.github/scripts/setup-slapd.sh
@@ -72,6 +72,9 @@ olcTLSCertificateKeyFile: /etc/ldap/ssl/server.key
add: olcTLSVerifyClient
olcTLSVerifyClient: never
-
+add: olcTLSProtocolMin
+olcTLSProtocolMin: 3.3
+-
add: olcAuthzRegexp
olcAuthzRegexp: uid=usera,cn=digest-md5,cn=auth cn=usera,dc=my-domain,dc=com
-
diff --git a/ext/ldap/ldap.c b/ext/ldap/ldap.c
index 6d576b84bd4..8da103fd632 100644
--- a/ext/ldap/ldap.c
+++ b/ext/ldap/ldap.c
@@ -3695,7 +3695,8 @@ PHP_FUNCTION(ldap_rename_ext)
*/
static int _php_ldap_tls_newctx(LDAP *ld)
{
- int val = 0, i, opts[] = {
+ int val = 0, i;
+ int str_opts[] = {
#if (LDAP_API_VERSION > 2000)
LDAP_OPT_X_TLS_CACERTDIR,
LDAP_OPT_X_TLS_CACERTFILE,
@@ -3715,21 +3716,42 @@ static int _php_ldap_tls_newctx(LDAP *ld)
#endif
0};
- for (i=0 ; opts[i] ; i++) {
+ for (i=0 ; str_opts[i] ; i++) {
char *path = NULL;
- ldap_get_option(ld, opts[i], &path);
+ ldap_get_option(ld, str_opts[i], &path);
if (path) { /* already set locally */
ldap_memfree(path);
} else {
- ldap_get_option(NULL, opts[i], &path);
+ ldap_get_option(NULL, str_opts[i], &path);
if (path) { /* set globally, inherit */
- ldap_set_option(ld, opts[i], path);
+ ldap_set_option(ld, str_opts[i], path);
ldap_memfree(path);
}
}
}
+#ifdef LDAP_OPT_X_TLS_PROTOCOL_MIN
+ int int_opts[] = {
+ LDAP_OPT_X_TLS_PROTOCOL_MIN,
+#ifdef LDAP_OPT_X_TLS_PROTOCOL_MAX
+ LDAP_OPT_X_TLS_PROTOCOL_MAX,
+#endif
+ 0
+ };
+ for (i=0 ; int_opts[i] ; i++) {
+ int value = 0;
+
+ ldap_get_option(ld, int_opts[i], &value);
+ if (value <= 0) { /* if value is not set already */
+ ldap_get_option(NULL, int_opts[i], &value);
+ if (value > 0) { /* set globally, inherit */
+ ldap_set_option(ld, int_opts[i], &value);
+ }
+ }
+ }
+#endif
+
return ldap_set_option(ld, LDAP_OPT_X_TLS_NEWCTX, &val);
}
diff --git a/ext/ldap/tests/ldap_start_tls_rc_max_version.conf b/ext/ldap/tests/ldap_start_tls_rc_max_version.conf
new file mode 100644
index 00000000000..0cd03f8b8e1
--- /dev/null
+++ b/ext/ldap/tests/ldap_start_tls_rc_max_version.conf
@@ -0,0 +1 @@
+TLS_PROTOCOL_MAX 3.2
\ No newline at end of file
diff --git a/ext/ldap/tests/ldap_start_tls_rc_max_version.phpt b/ext/ldap/tests/ldap_start_tls_rc_max_version.phpt
new file mode 100644
index 00000000000..359785f8b5a
--- /dev/null
+++ b/ext/ldap/tests/ldap_start_tls_rc_max_version.phpt
@@ -0,0 +1,41 @@
+--TEST--
+ldap_start_tls() - Basic ldap_start_tls test
+--EXTENSIONS--
+ldap
+--ENV--
+LDAPCONF={PWD}/ldap_start_tls_rc_max_version.conf
+--SKIPIF--
+<?php
+$require_vendor = [
+ "name" => "OpenLDAP",
+ "min_version" => 20600,
+];
+require_once __DIR__ .'/skipifbindfailure.inc';
+?>
+--FILE--
+<?php
+require_once "connect.inc";
+
+// CI uses self signed certificate
+
+// No cert option - fails
+$link = ldap_connect($uri);
+ldap_set_option($link, LDAP_OPT_PROTOCOL_VERSION, $protocol_version);
+var_dump(@ldap_start_tls($link));
+
+// No cert check - should pass but due to ldaps check, it fails as well
+$link = ldap_connect($uri);
+ldap_set_option($link, LDAP_OPT_PROTOCOL_VERSION, $protocol_version);
+ldap_set_option($link, LDAP_OPT_X_TLS_REQUIRE_CERT, LDAP_OPT_X_TLS_NEVER);
+var_dump(@ldap_start_tls($link));
+
+// With cert check - fails
+$link = ldap_connect($uri);
+ldap_set_option($link, LDAP_OPT_PROTOCOL_VERSION, $protocol_version);
+ldap_set_option($link, LDAP_OPT_X_TLS_REQUIRE_CERT, LDAP_OPT_X_TLS_DEMAND);
+var_dump(@ldap_start_tls($link));
+?>
+--EXPECT--
+bool(false)
+bool(false)
+bool(false)
diff --git a/ext/ldap/tests/skipifbindfailure.inc b/ext/ldap/tests/skipifbindfailure.inc
index 8f66c6cb968..ce24cd78f1e 100644
--- a/ext/ldap/tests/skipifbindfailure.inc
+++ b/ext/ldap/tests/skipifbindfailure.inc
@@ -10,4 +10,37 @@ if ($skip_on_bind_failure) {
ldap_unbind($link);
}
+
+if (isset($require_vendor)) {
+ ob_start();
+ phpinfo(INFO_MODULES);
+ $phpinfo = ob_get_clean();
+
+ // Extract the LDAP section specifically
+ if (preg_match('/^ldap\s*$(.*?)^[a-z_]+\s*$/ims', $phpinfo, $ldap_section_match)) {
+ $ldap_section = $ldap_section_match[1];
+
+ // Extract vendor info from the LDAP section only
+ if (preg_match('/Vendor Name\s*=>\s*(.+)/i', $ldap_section, $name_match) &&
+ preg_match('/Vendor Version\s*=>\s*(\d+)/i', $ldap_section, $version_match)) {
+
+ $vendor_name = trim($name_match[1]);
+ $vendor_version = (int)$version_match[1];
+
+ // Check vendor name if specified
+ if (isset($require_vendor['name']) && $vendor_name !== $require_vendor['name']) {
+ die("skip Requires {$require_vendor['name']} (detected: $vendor_name)");
+ }
+
+ // Check minimum version if specified
+ if (isset($require_vendor['min_version']) && $vendor_version < $require_vendor['min_version']) {
+ die("skip Requires minimum version {$require_vendor['min_version']} (detected: $vendor_version)");
+ }
+ } else {
+ die("skip Cannot determine LDAP vendor information");
+ }
+ } else {
+ die("skip LDAP extension information not found");
+ }
+}
?>
--
2.43.7
From bb559b2ecaa9359c79e336e6ff20bbe61bec9f6c Mon Sep 17 00:00:00 2001
From: Remi Collet <remi@remirepo.net>
Date: Fri, 3 Oct 2025 08:31:29 +0200
Subject: [PATCH 4/4] adapt for 8.0
---
ext/ldap/ldap.c | 2 --
ext/ldap/tests/ldap_start_tls_rc_max_version.phpt | 2 ++
2 files changed, 2 insertions(+), 2 deletions(-)
diff --git a/ext/ldap/ldap.c b/ext/ldap/ldap.c
index 8da103fd632..9a4589565cb 100644
--- a/ext/ldap/ldap.c
+++ b/ext/ldap/ldap.c
@@ -3176,7 +3176,6 @@ PHP_FUNCTION(ldap_set_option)
#endif
/* TLS option change requires resetting TLS context */
LDAPG(tls_newctx) = true;
- ZEND_FALLTHROUGH;
/* other options with int value */
case LDAP_OPT_DEREF:
case LDAP_OPT_SIZELIMIT:
@@ -3258,7 +3257,6 @@ PHP_FUNCTION(ldap_set_option)
#endif
/* TLS option change requires resetting TLS context */
LDAPG(tls_newctx) = true;
- ZEND_FALLTHROUGH;
/* other options with string value */
case LDAP_OPT_ERROR_STRING:
#ifdef LDAP_OPT_HOST_NAME
diff --git a/ext/ldap/tests/ldap_start_tls_rc_max_version.phpt b/ext/ldap/tests/ldap_start_tls_rc_max_version.phpt
index 359785f8b5a..fcdf276af9c 100644
--- a/ext/ldap/tests/ldap_start_tls_rc_max_version.phpt
+++ b/ext/ldap/tests/ldap_start_tls_rc_max_version.phpt
@@ -16,6 +16,8 @@ require_once __DIR__ .'/skipifbindfailure.inc';
<?php
require_once "connect.inc";
+$uri = 'ldap://localhost:389';
+
// CI uses self signed certificate
// No cert option - fails
--
2.43.7

98
php-8.0.30-soap.patch Normal file
View File

@ -0,0 +1,98 @@
From 60e7e11e8b93662b3eb62d0405e3f4a8a4928ef9 Mon Sep 17 00:00:00 2001
From: Remi Collet <remi@remirepo.net>
Date: Thu, 20 Mar 2025 09:26:26 +0100
Subject: [PATCH] Fix #66049 Typemap can break parsing in parse_packet_soap
leading to a segfault
(cherry picked from commit 209f4c296ec6a08c721afdf17d787db4b5fd37d0)
---
ext/soap/php_packet_soap.c | 3 ++
ext/soap/tests/bugs/bug66049.phpt | 48 +++++++++++++++++++++++++++++++
2 files changed, 51 insertions(+)
create mode 100644 ext/soap/tests/bugs/bug66049.phpt
diff --git a/ext/soap/php_packet_soap.c b/ext/soap/php_packet_soap.c
index 0e3a5197373..ab3dbf6b1e4 100644
--- a/ext/soap/php_packet_soap.c
+++ b/ext/soap/php_packet_soap.c
@@ -192,6 +192,7 @@ int parse_packet_soap(zval *this_ptr, char *buffer, int buffer_size, sdlFunction
if (tmp != NULL && tmp->children != NULL) {
zval zv;
master_to_zval(&zv, get_conversion(IS_STRING), tmp);
+ convert_to_string(&zv)
faultstring = Z_STR(zv);
}
@@ -199,6 +200,7 @@ int parse_packet_soap(zval *this_ptr, char *buffer, int buffer_size, sdlFunction
if (tmp != NULL && tmp->children != NULL) {
zval zv;
master_to_zval(&zv, get_conversion(IS_STRING), tmp);
+ convert_to_string(&zv)
faultactor = Z_STR(zv);
}
@@ -222,6 +224,7 @@ int parse_packet_soap(zval *this_ptr, char *buffer, int buffer_size, sdlFunction
if (tmp != NULL && tmp->children != NULL) {
zval zv;
master_to_zval(&zv, get_conversion(IS_STRING), tmp);
+ convert_to_string(&zv)
faultstring = Z_STR(zv);
}
}
diff --git a/ext/soap/tests/bugs/bug66049.phpt b/ext/soap/tests/bugs/bug66049.phpt
new file mode 100644
index 00000000000..e48845a8a14
--- /dev/null
+++ b/ext/soap/tests/bugs/bug66049.phpt
@@ -0,0 +1,48 @@
+--TEST--
+Fix #66049 Typemap can break parsing in parse_packet_soap leading to a segfault
+--EXTENSIONS--
+soap
+--INI--
+soap.wsdl_cache_enabled=0
+--FILE--
+<?php
+function soap_string_from_xml($str)
+ {
+ echo "soap_string_from_xml\n";
+
+ // Should return an string
+ return 2.3;
+ }
+
+class TestSoapClient extends SoapClient {
+ function __doRequest($request, $location, $action, $version, $one_way = 0): ?string {
+ $res='<?xml version="1.0" encoding="UTF-8"?>
+ <SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/">
+ <SOAP-ENV:Body>
+ <SOAP-ENV:Fault><faultcode>SOAP-ENV:Server</faultcode><faultstring>not present</faultstring>
+ </SOAP-ENV:Fault></SOAP-ENV:Body></SOAP-ENV:Envelope>';
+ return $res;
+ }
+}
+
+try {
+ $client=new TestSoapClient(null, [
+ 'uri' => 'test://',
+ 'location' => 'test://',
+ 'typemap' => [[
+ "type_ns" => "http://www.w3.org/2001/XMLSchema",
+ "type_name" => "string",
+ "from_xml" => "soap_string_from_xml"
+ ]]]);
+ $client->Mist("");
+} catch (SoapFault $e) {
+ var_dump($e->faultstring);
+ var_dump($e->faultcode);
+}
+?>
+Done
+--EXPECT--
+soap_string_from_xml
+string(3) "2.3"
+string(15) "SOAP-ENV:Server"
+Done
--
2.43.7

153
php-cve-2025-1220.patch Normal file
View File

@ -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

490
php-cve-2025-1735.patch Normal file
View File

@ -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

102
php-cve-2025-6491.patch Normal file
View File

@ -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

View File

@ -62,7 +62,7 @@
Summary: PHP scripting language for creating dynamic web sites
Name: php
Version: %{upver}%{?rcver:~%{rcver}}
Release: 3%{?dist}
Release: 4%{?dist}
# All files licensed under PHP version 3.01, except
# Zend is licensed under Zend
# TSRM is licensed under BSD
@ -121,6 +121,10 @@ Patch49: php-8.0.10-phar-sha.patch
Patch50: php-8.0.21-openssl3.patch
# use system libxcrypt
Patch51: php-8.0.13-crypt.patch
# soap fixes from 8.3
Patch52: php-8.0.30-soap.patch
# ldap fixes from 8.3
Patch53: php-8.0.30-ldap.patch
# Upstream fixes (100+)
@ -144,6 +148,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
@ -742,6 +749,8 @@ in pure PHP.
%patch -P50 -p1 -b .openssl3
rm ext/openssl/tests/p12_with_extra_certs.p12
%patch -P51 -p1 -b .libxcrypt
%patch -P52 -p1 -b .soapbackports
%patch -P53 -p1 -b .ldapbackports
# upstream patches
@ -764,6 +773,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
@ -1572,6 +1584,16 @@ systemctl try-restart php-fpm.service >/dev/null 2>&1 || :
%changelog
* Fri Oct 3 2025 Remi Collet <rcollet@redhat.com> - 8.0.30-4
- 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
- Fix soap memory corruption
- Fix ldap_set_option() not applied on different ldap connections
* Thu Mar 13 2025 Remi Collet <rcollet@redhat.com> - 8.0.30-3
- Fix libxml streams use wrong `content-type` header when requesting a redirected resource
CVE-2025-1219