[php-src] master: Merge branch 'PHP-8.5'

Ilia Alshanetsky <[email protected]> Fri, 17 Jul 2026 19:54:51 +0000
Newsgroups gmane.comp.php.cvs.general
Message-ID <[email protected]>
Author: Ilia Alshanetsky (iliaal)
Date: 2026-07-17T15:54:01-04:00

Commit: https://github.com/php/php-src/commit/46a35fff758f70201ec6739f1e188416b9c24469
Raw diff: https://github.com/php/php-src/commit/46a35fff758f70201ec6739f1e188416b9c24469.diff

Merge branch 'PHP-8.5'

* PHP-8.5:
  ext/ftp: apply the connect timeout ceiling to the remaining entry points

Changed paths:
  A  ext/ftp/tests/gh20601_set_option.phpt
  A  ext/ftp/tests/gh20601_ssl.phpt
  M  ext/ftp/php_ftp.c


Diff:

diff --git a/ext/ftp/php_ftp.c b/ext/ftp/php_ftp.c
index 23bd69b5d63f..56938459bb33 100644
--- a/ext/ftp/php_ftp.c
+++ b/ext/ftp/php_ftp.c
@@ -34,6 +34,8 @@
 #include "ftp.h"
 #include "ftp_arginfo.h"
 
+#define PHP_FTP_TIMEOUT_SEC_MAX ((uint64_t)((double) PHP_TIMEOUT_ULL_MAX / 1000000.0))
+
 static zend_class_entry *php_ftp_ce = NULL;
 static zend_object_handlers ftp_object_handlers;
 
@@ -143,15 +145,13 @@ PHP_FUNCTION(ftp_connect)
 		RETURN_THROWS();
 	}
 
-	const uint64_t timeoutmax = (uint64_t)((double) PHP_TIMEOUT_ULL_MAX / 1000000.0);
-
 	if (timeout_sec <= 0) {
 		zend_argument_value_error(3, "must be greater than 0");
 		RETURN_THROWS();
 	}
 
-	if (timeout_sec >= timeoutmax) {
-		zend_argument_value_error(3, "must be less than " ZEND_ULONG_FMT, timeoutmax);
+	if (timeout_sec >= PHP_FTP_TIMEOUT_SEC_MAX) {
+		zend_argument_value_error(3, "must be less than " ZEND_ULONG_FMT, PHP_FTP_TIMEOUT_SEC_MAX);
 		RETURN_THROWS();
 	}
 
@@ -192,6 +192,11 @@ PHP_FUNCTION(ftp_ssl_connect)
 		RETURN_THROWS();
 	}
 
+	if (timeout_sec >= PHP_FTP_TIMEOUT_SEC_MAX) {
+		zend_argument_value_error(3, "must be less than " ZEND_ULONG_FMT, PHP_FTP_TIMEOUT_SEC_MAX);
+		RETURN_THROWS();
+	}
+
 	/* connect */
 	if (!(ftp = ftp_open(host, (short)port, timeout_sec))) {
 		RETURN_FALSE;
@@ -1284,6 +1289,10 @@ PHP_FUNCTION(ftp_set_option)
 				zend_argument_value_error(3, "must be greater than 0 for the FTP_TIMEOUT_SEC option");
 				RETURN_THROWS();
 			}
+			if ((uint64_t) Z_LVAL_P(z_value) >= PHP_FTP_TIMEOUT_SEC_MAX) {
+				zend_argument_value_error(3, "must be less than " ZEND_ULONG_FMT " for the FTP_TIMEOUT_SEC option", PHP_FTP_TIMEOUT_SEC_MAX);
+				RETURN_THROWS();
+			}
 			ftp->timeout_sec = Z_LVAL_P(z_value);
 			RETURN_TRUE;
 		case PHP_FTP_OPT_AUTOSEEK:
diff --git a/ext/ftp/tests/gh20601_set_option.phpt b/ext/ftp/tests/gh20601_set_option.phpt
new file mode 100644
index 000000000000..3317d4b5f2a0
--- /dev/null
+++ b/ext/ftp/tests/gh20601_set_option.phpt
@@ -0,0 +1,26 @@
+--TEST--
+GH-20601 (ftp_set_option FTP_TIMEOUT_SEC timeout overflow)
+--EXTENSIONS--
+ftp
+pcntl
+--SKIPIF--
+<?php
+if (PHP_INT_SIZE != 8) die("skip: 64-bit only");
+if (PHP_OS_FAMILY === 'Windows') die("skip not for windows");
+?>
+--FILE--
+<?php
+require 'server.inc';
+
+$ftp = ftp_connect('127.0.0.1', $port);
+$ftp or die("Couldn't connect to the server");
+ftp_login($ftp, 'user', 'pass');
+
+try {
+	ftp_set_option($ftp, FTP_TIMEOUT_SEC, PHP_INT_MAX);
+} catch (\ValueError $e) {
+	echo $e->getMessage();
+}
+?>
+--EXPECTF--
+ftp_set_option(): Argument #3 ($value) must be less than %d for the FTP_TIMEOUT_SEC option
diff --git a/ext/ftp/tests/gh20601_ssl.phpt b/ext/ftp/tests/gh20601_ssl.phpt
new file mode 100644
index 000000000000..005f866e18e0
--- /dev/null
+++ b/ext/ftp/tests/gh20601_ssl.phpt
@@ -0,0 +1,21 @@
+--TEST--
+GH-20601 (ftp_ssl_connect timeout overflow)
+--EXTENSIONS--
+ftp
+openssl
+--SKIPIF--
+<?php
+if (!function_exists("ftp_ssl_connect")) die("skip ftp_ssl is disabled");
+if (PHP_INT_SIZE != 8) die("skip: 64-bit only");
+if (PHP_OS_FAMILY === 'Windows') die("skip not for windows");
+?>
+--FILE--
+<?php
+try {
+	ftp_ssl_connect('127.0.0.1', 1024, PHP_INT_MAX);
+} catch (\ValueError $e) {
+	echo $e->getMessage();
+}
+?>
+--EXPECTF--
+ftp_ssl_connect(): Argument #3 ($timeout) must be less than %d