Skip to content
Closed
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 19 additions & 0 deletions ext/ftp/tests/gh20601.phpt
Original file line number Diff line number Diff line change
@@ -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");
?>
Comment on lines +5 to +9
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This an issue that cannot appear on 32 bit nor Windows?

Copy link
Member Author

@devnexen devnexen Nov 29, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yes this test "failed" for both earlier.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Alright

--FILE--
<?php
try {
ftp_connect('127.0.0.1', 1024, PHP_INT_MAX);
} catch (\ValueError $e) {
echo $e->getMessage();
}
?>
--EXPECT--
timeout value overflow
21 changes: 18 additions & 3 deletions main/network.c
Original file line number Diff line number Diff line change
Expand Up @@ -315,16 +315,24 @@ static inline void sub_times(struct timeval a, struct timeval b, struct timeval
}
}

static inline void php_network_set_limit_time(struct timeval *limit_time,
static inline zend_result 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;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm just checking the whole logic here and I wonder why PHP_TIMEOUT_ULL_MAX is unsigned int 64? The time_t is signed int 64 (or 32 on 32bit arch) if I'm not mistakend. And in this case this is later converted to int (see php_pollfd_for and mainly php_tvtoto). So what am I missing?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes it s unsigned but my sense since it s that way because this constant is never used directly but converted from microseconds value to seconds (so yes unsigned int64 > double > time_t or int).

Copy link
Member

@bukka bukka Nov 30, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

But how is this covering the lower type overflows? I mean if it gets converted in php_tvtoto, then it will still overflow on conversion to int, right?


if (limit_time->tv_sec >= (timeoutmax - timeout->tv_sec)) {
zend_value_error("timeout value overflow");
return FAILURE;
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would rather have this be an assertion and make it the responsibility of the calling code to ensure this is respected. So move the check to ftp_connect(), especially as you get better error messages and you can pin point to the exact argument is causing an issue.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ok done, note that I purposely kept separated the check for <= 0 and the new one since it s stable branch.


limit_time->tv_sec += timeout->tv_sec;
limit_time->tv_usec += timeout->tv_usec;
if (limit_time->tv_usec >= 1000000) {
limit_time->tv_usec -= 1000000;
limit_time->tv_sec++;
}
return SUCCESS;
}
#endif

Expand Down Expand Up @@ -391,7 +399,11 @@ PHPAPI int php_network_connect_socket(php_socket_t sockfd,
if (timeout) {
memcpy(&working_timeout, timeout, sizeof(working_timeout));
#if HAVE_GETTIMEOFDAY
php_network_set_limit_time(&limit_time, &working_timeout);
if (UNEXPECTED(php_network_set_limit_time(&limit_time, &working_timeout) == FAILURE)) {
error = ERANGE;
ret = -1;
goto ok;
}
#endif
Comment on lines 395 to 397
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ditto

}

Expand Down Expand Up @@ -849,7 +861,10 @@ php_socket_t php_network_connect_socket_to_host(const char *host, unsigned short
if (timeout) {
memcpy(&working_timeout, timeout, sizeof(working_timeout));
#if HAVE_GETTIMEOFDAY
php_network_set_limit_time(&limit_time, &working_timeout);
if (UNEXPECTED(php_network_set_limit_time(&limit_time, &working_timeout) == FAILURE)) {
php_network_freeaddresses(psal);
return -1;
}
#endif
Comment on lines 853 to 855
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ditto

}

Expand Down
Loading