From 3c8801449cf4404bd1447bc6a767c7864cc33dc5 Mon Sep 17 00:00:00 2001 From: Ilia Alshanetsky Date: Thu, 16 Jul 2026 08:45:52 -0400 Subject: [PATCH] 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 --- ext/ftp/php_ftp.c | 17 +++++++++++++---- ext/ftp/tests/gh20601_set_option.phpt | 26 ++++++++++++++++++++++++++ ext/ftp/tests/gh20601_ssl.phpt | 21 +++++++++++++++++++++ 3 files changed, 60 insertions(+), 4 deletions(-) create mode 100644 ext/ftp/tests/gh20601_set_option.phpt create mode 100644 ext/ftp/tests/gh20601_ssl.phpt 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-- + +--FILE-- +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-- + +--FILE-- +getMessage(); +} +?> +--EXPECTF-- +ftp_ssl_connect(): Argument #3 ($timeout) must be less than %d