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

[email protected] (Ilia Alshanetsky)
Newsgroups php.cvs
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
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.