Commit 4312a446d0a for php.net
commit 4312a446d0a101ffa7fa5d4c624e487a32706b59
Author: David Carlier <devnexen@gmail.com>
Date: Thu Nov 27 19:37:01 2025 +0000
Fix GH-20601: ftp_connect() timeout argument overflow.
close GH-20603
diff --git a/NEWS b/NEWS
index 214f1105b5c..3ec3f8c096d 100644
--- a/NEWS
+++ b/NEWS
@@ -24,6 +24,9 @@ PHP NEWS
. Fixed bug GH-20483 (ASAN stack overflow with fiber.stack_size INI
small value). (David Carlier)
+- FTP:
+ . Fixed bug GH-20601 (ftp_connect overflow on timeout). (David Carlier)
+
- GD:
. Fixed bug GH-20511 (imagegammacorrect out of range input/output values).
(David Carlier)
diff --git a/ext/ftp/php_ftp.c b/ext/ftp/php_ftp.c
index 4d33e4d8253..0df3aa6e755 100644
--- a/ext/ftp/php_ftp.c
+++ b/ext/ftp/php_ftp.c
@@ -158,11 +158,18 @@ PHP_FUNCTION(ftp_connect)
RETURN_THROWS();
}
+ const zend_long timeoutmax = (zend_long)((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_LONG_FMT, timeoutmax);
+ RETURN_THROWS();
+ }
+
/* connect */
if (!(ftp = ftp_open(host, (short)port, timeout_sec))) {
RETURN_FALSE;
diff --git a/ext/ftp/tests/gh20601.phpt b/ext/ftp/tests/gh20601.phpt
new file mode 100644
index 00000000000..3ece7736c3a
--- /dev/null
+++ b/ext/ftp/tests/gh20601.phpt
@@ -0,0 +1,19 @@
+--TEST--
+GH-20601 (ftp_connect timeout overflow)
+--EXTENSIONS--
+ftp
+--SKIPIF--
+<?php
+if (PHP_INT_SIZE != 8) die("skip: 64-bit only");
+if (PHP_OS_FAMILY === 'Windows') die("skip not for windows");
+?>
+--FILE--
+<?php
+try {
+ ftp_connect('127.0.0.1', 1024, PHP_INT_MAX);
+} catch (\ValueError $e) {
+ echo $e->getMessage();
+}
+?>
+--EXPECTF--
+ftp_connect(): Argument #3 ($timeout) must be less than %d
diff --git a/main/network.c b/main/network.c
index fecec0545e8..7b69614d533 100644
--- a/main/network.c
+++ b/main/network.c
@@ -319,6 +319,8 @@ static inline void php_network_set_limit_time(struct timeval *limit_time,
struct timeval *timeout)
{
gettimeofday(limit_time, NULL);
+ const double timeoutmax = (double) PHP_TIMEOUT_ULL_MAX / 1000000.0;
+ ZEND_ASSERT(limit_time->tv_sec < (timeoutmax - timeout->tv_sec));
limit_time->tv_sec += timeout->tv_sec;
limit_time->tv_usec += timeout->tv_usec;
if (limit_time->tv_usec >= 1000000) {