diff --git a/ext/mysqli/tests/gh9590.phpt b/ext/mysqli/tests/gh9590.phpt new file mode 100644 index 0000000000000..be4069ce29bac --- /dev/null +++ b/ext/mysqli/tests/gh9590.phpt @@ -0,0 +1,71 @@ +--TEST-- +Bug GH-9602 (stream_select does not abort upon exception or empty valid fd set) +--SKIPIF-- + +--FILE-- + +--EXPECTF-- +bool(true) +bool(true) + +Warning: mysqli_poll(): You MUST recompile PHP with a larger value of FD_SETSIZE. +It is set to 1024, but you have descriptors numbered at least as high as %d. + --enable-fd-setsize=%d is recommended, but you may want to set it +to equal the maximum number of open files supported by your system, +in order to avoid seeing this error again at a later date. in %s on line %d +bool(false) +done! +--CLEAN-- + diff --git a/ext/mysqlnd/mysqlnd_connection.c b/ext/mysqlnd/mysqlnd_connection.c index bfd68681717ef..f2529d9d1d4cd 100644 --- a/ext/mysqlnd/mysqlnd_connection.c +++ b/ext/mysqlnd/mysqlnd_connection.c @@ -2615,7 +2615,9 @@ mysqlnd_poll(MYSQLND **r_array, MYSQLND **e_array, MYSQLND ***dont_poll, long se DBG_RETURN(FAIL); } - PHP_SAFE_MAX_FD(max_fd, max_set_count); + if (!PHP_SAFE_MAX_FD(max_fd, max_set_count)) { + DBG_RETURN(FAIL); + } /* Solaris + BSD do not like microsecond values which are >= 1 sec */ if (usec > 999999) { diff --git a/ext/sockets/sockets.c b/ext/sockets/sockets.c index 7976c9cc758a9..889d31b882801 100644 --- a/ext/sockets/sockets.c +++ b/ext/sockets/sockets.c @@ -764,7 +764,9 @@ PHP_FUNCTION(socket_select) RETURN_THROWS(); } - PHP_SAFE_MAX_FD(max_fd, 0); /* someone needs to make this look more like stream_socket_select */ + if (!PHP_SAFE_MAX_FD(max_fd, 0)) { + RETURN_FALSE; + } /* If seconds is not set to null, build the timeval, else we wait indefinitely */ if (!sec_is_null) { diff --git a/ext/standard/streamsfuncs.c b/ext/standard/streamsfuncs.c index b44bf6f8ca2c9..64e7c7a0d4ca5 100644 --- a/ext/standard/streamsfuncs.c +++ b/ext/standard/streamsfuncs.c @@ -798,7 +798,9 @@ PHP_FUNCTION(stream_select) RETURN_THROWS(); } - PHP_SAFE_MAX_FD(max_fd, max_set_count); + if (!PHP_SAFE_MAX_FD(max_fd, max_set_count)) { + RETURN_FALSE; + } /* If seconds is not set to null, build the timeval, else we wait indefinitely */ if (!secnull) { diff --git a/ext/standard/tests/streams/gh9590-001.phpt b/ext/standard/tests/streams/gh9590-001.phpt new file mode 100644 index 0000000000000..965c0ca4dc8bb --- /dev/null +++ b/ext/standard/tests/streams/gh9590-001.phpt @@ -0,0 +1,38 @@ +--TEST-- +Bug GH-9590 001 (stream_select does not abort upon exception or empty valid fd set) +--SKIPIF-- + +--FILE-- + +--EXPECTF-- +Warning: stream_select(): You MUST recompile PHP with a larger value of FD_SETSIZE. +It is set to 1024, but you have descriptors numbered at least as high as %d. + --enable-fd-setsize=%d is recommended, but you may want to set it +to equal the maximum number of open files supported by your system, +in order to avoid seeing this error again at a later date. in %s on line %d +bool(false) +--CLEAN-- + diff --git a/ext/standard/tests/streams/gh9590-002.phpt b/ext/standard/tests/streams/gh9590-002.phpt new file mode 100644 index 0000000000000..9b6be0e24682c --- /dev/null +++ b/ext/standard/tests/streams/gh9590-002.phpt @@ -0,0 +1,40 @@ +--TEST-- +Bug GH-9590 002 (stream_select does not abort upon exception or empty valid fd set) +--SKIPIF-- + +--FILE-- + +--EXPECTF-- +Fatal error: Uncaught Exception: stream_select(): You MUST recompile PHP with a larger value of FD_SETSIZE. +It is set to 1024, but you have descriptors numbered at least as high as %d. + --enable-fd-setsize=%d is recommended, but you may want to set it +to equal the maximum number of open files supported by your system, +in order to avoid seeing this error again at a later date. in %s:%d +Stack trace:%a +--CLEAN-- + diff --git a/main/network.c b/main/network.c index 2c504952b2dd1..a433ad9cb8fdc 100644 --- a/main/network.c +++ b/main/network.c @@ -1196,7 +1196,14 @@ PHPAPI int php_poll2(php_pollfd *ufds, unsigned int nfds, int timeout) } #endif - PHP_SAFE_MAX_FD(max_fd, nfds + 1); + if (!PHP_SAFE_MAX_FD(max_fd, nfds + 1)) { +#ifdef PHP_WIN32 + WSASetLastError(WSAEINVAL); +#else + errno = ERANGE; +#endif + return -1; + } FD_ZERO(&rset); FD_ZERO(&wset); diff --git a/main/php_network.h b/main/php_network.h index 437069b4fccdd..e7892a73df290 100644 --- a/main/php_network.h +++ b/main/php_network.h @@ -204,18 +204,35 @@ static inline int php_pollfd_for_ms(php_socket_t fd, int events, int timeout) /* emit warning and suggestion for unsafe select(2) usage */ PHPAPI void _php_emit_fd_setsize_warning(int max_fd); +static inline bool _php_check_fd_setsize(php_socket_t *max_fd, int setsize) +{ +#ifdef PHP_WIN32 + if (setsize + 1 >= FD_SETSIZE) { + _php_emit_fd_setsize_warning(setsize); + return false; + } +#else + if (*max_fd >= FD_SETSIZE) { + _php_emit_fd_setsize_warning(*max_fd); + *max_fd = FD_SETSIZE - 1; + return false; + } +#endif + return true; +} + #ifdef PHP_WIN32 /* it is safe to FD_SET too many fd's under win32; the macro will simply ignore * descriptors that go beyond the default FD_SETSIZE */ # define PHP_SAFE_FD_SET(fd, set) FD_SET(fd, set) # define PHP_SAFE_FD_CLR(fd, set) FD_CLR(fd, set) # define PHP_SAFE_FD_ISSET(fd, set) FD_ISSET(fd, set) -# define PHP_SAFE_MAX_FD(m, n) do { if (n + 1 >= FD_SETSIZE) { _php_emit_fd_setsize_warning(n); }} while(0) +# define PHP_SAFE_MAX_FD(m, n) _php_check_fd_setsize(&m, n) #else # define PHP_SAFE_FD_SET(fd, set) do { if (fd < FD_SETSIZE) FD_SET(fd, set); } while(0) # define PHP_SAFE_FD_CLR(fd, set) do { if (fd < FD_SETSIZE) FD_CLR(fd, set); } while(0) # define PHP_SAFE_FD_ISSET(fd, set) ((fd < FD_SETSIZE) && FD_ISSET(fd, set)) -# define PHP_SAFE_MAX_FD(m, n) do { if (m >= FD_SETSIZE) { _php_emit_fd_setsize_warning(m); m = FD_SETSIZE - 1; }} while(0) +# define PHP_SAFE_MAX_FD(m, n) _php_check_fd_setsize(&m, n) #endif