[php-src] PHP-8.5: ext/ftp: apply the connect timeout ceiling to the remaining entry points

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

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

ext/ftp: apply the connect timeout ceiling to the remaining entry points

GH-20601 capped the timeout in ftp_connect() so a value that overflows the
timeval conversion cannot reach php_network_set_limit_time(). ftp_ssl_connect()
and ftp_set_option(FTP_TIMEOUT_SEC) kept rejecting only non-positive values, so
PHP_INT_MAX still reached ftp_open() and my_poll() through them.

Apply the same bound to both, factored into a shared PHP_FTP_TIMEOUT_SEC_MAX
macro. ftp_ssl_connect() with PHP_INT_MAX hung before this rather than
reporting the bad argument.

Closes GH-22767

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 432270cff190..5cb7fff4886e 100644
--- a/ext/ftp/php_ftp.c
+++ b/ext/ftp/php_ftp.c
@@ -36,6 +36,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;
 
@@ -147,15 +149,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();
 	}
 
@@ -196,6 +196,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;
@@ -1275,6 +1280,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;
 			break;
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