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) {