diff --git a/NEWS b/NEWS index b38484a71ac1..c0e3bc4542fc 100644 --- a/NEWS +++ b/NEWS @@ -81,6 +81,15 @@ PHP NEWS lookup, and an unchecked CreateFileA() failure. (Ilia Alshanetsky) . Fix persistent stream context lifetime during shutdown (Levi Morrison) . Fixed bug GH-23780 (unserialize('') fails silently). (timwolla) + . Fixed stream_select() aborting the whole call once any watched stream's + descriptor exceeded FD_SETSIZE; it no longer imposes that limit, so watching + many descriptors no longer requires rebuilding with --enable-fd-setsize. + (Frode Børli) + . Fixed stream_select() on Windows capping the number of watched sockets at + FD_SETSIZE; the winsock fd_set now grows on demand. (Frode Børli) + . Fixed stream_select() on Windows overflowing a fixed 64-entry stack array + (crashing with a stack buffer overrun) when passed more than 64 pipe or file + handles; it now fails gracefully instead. (Frode Børli) . Improved performance of array_splice() when inserting without removing elements. (mehmetcansahin) . Enforce max_filter_count: limit the number of filters that can be chained diff --git a/UPGRADING b/UPGRADING index 27dc06f12d2e..3483f2afcbb7 100644 --- a/UPGRADING +++ b/UPGRADING @@ -45,6 +45,14 @@ PHP 8.7 UPGRADE NOTES 5. Changed Functions ======================================== +- Standard: + . stream_select() no longer fails once a watched descriptor exceeds + FD_SETSIZE. The descriptor set now grows on demand on both POSIX (bitset) + and Windows (winsock fd_set), so code watching many streams works without + rebuilding PHP with --enable-fd-setsize. On Windows, selecting on more than + 64 pipe or file handles no longer crashes; it fails with a warning, as that + path remains bounded by WaitForMultipleObjects(). + ======================================== 6. New Functions ======================================== diff --git a/ext/standard/streamsfuncs.c b/ext/standard/streamsfuncs.c index 90341109210f..897e773ae0e2 100644 --- a/ext/standard/streamsfuncs.c +++ b/ext/standard/streamsfuncs.c @@ -648,7 +648,7 @@ PHP_FUNCTION(stream_clear_errors) } /* {{{ stream_select related functions */ -static int stream_array_to_fd_set(const HashTable *stream_array, fd_set *fds, php_socket_t *max_fd) +static int stream_array_to_fd_set(const HashTable *stream_array, php_growable_fd_set *fds, php_socket_t *max_fd) { zval *elem; php_stream *stream; @@ -672,7 +672,7 @@ static int stream_array_to_fd_set(const HashTable *stream_array, fd_set *fds, ph * */ if (SUCCESS == php_stream_cast(stream, PHP_STREAM_AS_FD_FOR_SELECT | PHP_STREAM_CAST_INTERNAL, (void*)&this_fd, 1) && this_fd != -1) { - PHP_SAFE_FD_SET(this_fd, fds); + php_growable_fd_set_add(fds, this_fd); if (this_fd > *max_fd) { *max_fd = this_fd; @@ -683,19 +683,14 @@ static int stream_array_to_fd_set(const HashTable *stream_array, fd_set *fds, ph return cnt ? 1 : 0; } -static int stream_array_from_fd_set(zval *stream_array, const fd_set *fds) +static int stream_array_from_fd_set(zval *stream_array, const php_growable_fd_set *fds) { - zval *elem, *dest_elem; - HashTable *ht; - php_stream *stream; - int ret = 0; - zend_string *key; - zend_ulong num_ind; - ZEND_ASSERT(Z_TYPE_P(stream_array) == IS_ARRAY); - ht = zend_new_array(zend_hash_num_elements(Z_ARRVAL_P(stream_array))); + HashTable *ht = zend_new_array(zend_hash_num_elements(Z_ARRVAL_P(stream_array))); + int ret = 0; - ZEND_HASH_FOREACH_KEY_VAL(Z_ARRVAL_P(stream_array), num_ind, key, elem) { + ZEND_HASH_FOREACH_KEY_VAL(Z_ARRVAL_P(stream_array), zend_ulong num_ind, zend_string *key, zval *elem) { + php_stream *stream; php_socket_t this_fd; ZVAL_DEREF(elem); @@ -709,7 +704,8 @@ static int stream_array_from_fd_set(zval *stream_array, const fd_set *fds) * is not displayed. */ if (SUCCESS == php_stream_cast(stream, PHP_STREAM_AS_FD_FOR_SELECT | PHP_STREAM_CAST_INTERNAL, (void*)&this_fd, 1) && this_fd != SOCK_ERR) { - if (PHP_SAFE_FD_ISSET(this_fd, fds)) { + if (php_growable_fd_set_isset(fds, this_fd)) { + zval *dest_elem; if (!key) { dest_elem = zend_hash_index_update(ht, num_ind, elem); } else { @@ -780,15 +776,9 @@ static int stream_array_emulate_read_fd_set(zval *stream_array) PHP_FUNCTION(stream_select) { zval *r_array, *w_array, *e_array, *zcontext = NULL; - struct timeval tv, *tv_p = NULL; - fd_set rfds, wfds, efds; - php_socket_t max_fd = 0; - int retval, sets = 0; zend_long sec, usec = 0; bool secnull; bool usecnull = 1; - int set_count, max_set_count = 0; - php_stream_context *context = NULL; ZEND_PARSE_PARAMETERS_START(4, 6) Z_PARAM_ARRAY_EX2(r_array, 1, 1, 0) @@ -800,62 +790,52 @@ PHP_FUNCTION(stream_select) Z_PARAM_RESOURCE_OR_NULL(zcontext) ZEND_PARSE_PARAMETERS_END(); - FD_ZERO(&rfds); - FD_ZERO(&wfds); - FD_ZERO(&efds); - php_stream_error_operation_begin(); - context = php_stream_context_from_zval(zcontext, 0); + php_stream_context *context = php_stream_context_from_zval(zcontext, 0); + + /* The sets grow as streams are added, past FD_SETSIZE */ + php_growable_fd_set rfds, wfds, efds; + php_growable_fd_set_init(&rfds, FD_SETSIZE); + php_growable_fd_set_init(&wfds, FD_SETSIZE); + php_growable_fd_set_init(&efds, FD_SETSIZE); + php_socket_t max_fd = 0; + int sets = 0; if (r_array != NULL) { - set_count = stream_array_to_fd_set(Z_ARR_P(r_array), &rfds, &max_fd); - if (set_count > max_set_count) - max_set_count = set_count; - sets += set_count; + sets += stream_array_to_fd_set(Z_ARR_P(r_array), &rfds, &max_fd); } - if (w_array != NULL) { - set_count = stream_array_to_fd_set(Z_ARR_P(w_array), &wfds, &max_fd); - if (set_count > max_set_count) - max_set_count = set_count; - sets += set_count; + sets += stream_array_to_fd_set(Z_ARR_P(w_array), &wfds, &max_fd); } - if (e_array != NULL) { - set_count = stream_array_to_fd_set(Z_ARR_P(e_array), &efds, &max_fd); - if (set_count > max_set_count) - max_set_count = set_count; - sets += set_count; + sets += stream_array_to_fd_set(Z_ARR_P(e_array), &efds, &max_fd); } if (!sets) { php_stream_error_operation_end(context); zend_value_error("No stream arrays were passed"); - RETURN_THROWS(); - } - - if (!PHP_SAFE_MAX_FD(max_fd, max_set_count)) { - RETURN_FALSE; + goto cleanup; } if (secnull && !usecnull) { if (usec != 0) { php_stream_error_operation_end(context); zend_argument_value_error(5, "must be null when argument #4 ($seconds) is null"); - RETURN_THROWS(); + goto cleanup; } } /* If seconds is not set to null, build the timeval, else we wait indefinitely */ + struct timeval tv, *tv_p = NULL; if (!secnull) { if (sec < 0) { php_stream_error_operation_end(context); zend_argument_value_error(4, "must be greater than or equal to 0"); - RETURN_THROWS(); + goto cleanup; } else if (usec < 0) { php_stream_error_operation_end(context); zend_argument_value_error(5, "must be greater than or equal to 0"); - RETURN_THROWS(); + goto cleanup; } /* Windows, Solaris and BSD do not like microsecond values which are >= 1 sec */ @@ -867,6 +847,7 @@ PHP_FUNCTION(stream_select) /* slight hack to support buffered data; if there is data sitting in the * read buffer of any of the streams in the read array, let's pretend * that we selected, but return only the readable sockets */ + int retval; if (r_array != NULL) { retval = stream_array_emulate_read_fd_set(r_array); if (retval > 0) { @@ -879,24 +860,37 @@ PHP_FUNCTION(stream_select) zval_ptr_dtor(e_array); ZVAL_EMPTY_ARRAY(e_array); } - RETURN_LONG(retval); + RETVAL_LONG(retval); + goto cleanup; } } - retval = php_select(max_fd+1, &rfds, &wfds, &efds, tv_p); +#ifndef PHP_WIN32 + /* select() reads max_fd + 1 bits of each set */ + php_growable_fd_set_reserve(&rfds, max_fd + 1); + php_growable_fd_set_reserve(&wfds, max_fd + 1); + php_growable_fd_set_reserve(&efds, max_fd + 1); +#endif + retval = php_select(max_fd + 1, rfds.set, wfds.set, efds.set, tv_p); php_stream_error_operation_end(context); if (retval == -1) { php_error_docref(NULL, E_WARNING, "Unable to select [%d]: %s (max_fd=" PHP_SOCKET_FMT ")", errno, strerror(errno), max_fd); - RETURN_FALSE; + RETVAL_FALSE; + goto cleanup; } if (r_array != NULL) stream_array_from_fd_set(r_array, &rfds); if (w_array != NULL) stream_array_from_fd_set(w_array, &wfds); if (e_array != NULL) stream_array_from_fd_set(e_array, &efds); - RETURN_LONG(retval); + RETVAL_LONG(retval); + +cleanup: + php_growable_fd_set_destroy(&rfds); + php_growable_fd_set_destroy(&wfds); + php_growable_fd_set_destroy(&efds); } /* }}} */ diff --git a/ext/standard/tests/streams/gh9590-001.phpt b/ext/standard/tests/streams/gh9590-001.phpt index 8f5691af1ade..e2eedbff8829 100644 --- a/ext/standard/tests/streams/gh9590-001.phpt +++ b/ext/standard/tests/streams/gh9590-001.phpt @@ -1,10 +1,13 @@ --TEST-- -Bug GH-9590 001 (stream_select does not abort upon exception or empty valid fd set) +Bug GH-9590 001 (stream_select works correctly, without warning, past the traditional +FD_SETSIZE descriptor limit on platforms where that limit has been lifted) --EXTENSIONS-- posix --SKIPIF-- ---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) +--EXPECT-- +int(1) +string(1) "x" --CLEAN-- ---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 +--EXPECT-- +int(1) +string(1) "x" --CLEAN-- +--FILE-- + $lo, 'hi' => $hi]; +$w = ['hi' => $hi]; +$e = ['hi' => $hi, 'lo' => $lo]; +var_dump(stream_select($r, $w, $e, 30)); +var_dump(array_keys($r), array_keys($w), $e); + +// A beyond-FD_SETSIZE stream in the write set alone. +$r = $e = null; +$w = [$hi]; +var_dump(stream_select($r, $w, $e, 30)); + +?> +--EXPECT-- +int(3) +array(2) { + [0]=> + string(2) "lo" + [1]=> + string(2) "hi" +} +array(1) { + [0]=> + string(2) "hi" +} +array(0) { +} +int(1) +--CLEAN-- + diff --git a/ext/standard/tests/streams/stream_select_win32_many_sockets.phpt b/ext/standard/tests/streams/stream_select_win32_many_sockets.phpt new file mode 100644 index 000000000000..1a37ed7977ed --- /dev/null +++ b/ext/standard/tests/streams/stream_select_win32_many_sockets.phpt @@ -0,0 +1,44 @@ +--TEST-- +stream_select(): more than FD_SETSIZE sockets on Windows (growable fd_set) +--SKIPIF-- + +--FILE-- + +--EXPECT-- +bool(true) +bool(true) +bool(true) diff --git a/ext/standard/tests/streams/stream_select_win32_max_handles.phpt b/ext/standard/tests/streams/stream_select_win32_max_handles.phpt new file mode 100644 index 000000000000..5d0ee495d2fc --- /dev/null +++ b/ext/standard/tests/streams/stream_select_win32_max_handles.phpt @@ -0,0 +1,38 @@ +--TEST-- +stream_select(): >64 non-socket handles fail gracefully on Windows (no stack overflow) +--SKIPIF-- + +--FILE-- + +--EXPECTF-- +Warning: stream_select(): Unable to select [%d]: %s (max_fd=%d) in %s on line %d +bool(false) +--CLEAN-- + diff --git a/ext/standard/tests/streams/stream_select_win32_multi_set.phpt b/ext/standard/tests/streams/stream_select_win32_multi_set.phpt new file mode 100644 index 000000000000..1f1e60a3850f --- /dev/null +++ b/ext/standard/tests/streams/stream_select_win32_multi_set.phpt @@ -0,0 +1,51 @@ +--TEST-- +stream_select(): more than FD_SETSIZE sockets in the write and except sets on Windows (each set grows) +--SKIPIF-- + +--FILE-- + +--EXPECT-- +bool(true) +bool(true) +bool(true) +bool(true) +int(0) +bool(true) diff --git a/main/php_network.h b/main/php_network.h index c93a519911f0..9e59a3029f5d 100644 --- a/main/php_network.h +++ b/main/php_network.h @@ -248,6 +248,117 @@ static inline bool _php_check_fd_setsize(php_socket_t *max_fd, int setsize) # define PHP_SAFE_MAX_FD(m, n) _php_check_fd_setsize(&m, n) #endif +/* A growable fd_set for stream_select(), without the FD_SETSIZE limit. + * + * On POSIX, fd_set is a bitset indexed by descriptor, and select() takes any nfds: the set + * is a bitset that grows to hold the highest descriptor added. + * On Windows, fd_set is an array of SOCKETs ({ u_int fd_count; SOCKET fd_array[]; }), and + * select() reads fd_count of them: the set is that array, grown to hold every socket added. + * Either way, `set` can be passed to select() as it is. */ +#ifdef PHP_WIN32 +typedef struct { + u_int capacity; /* SOCKET slots in set->fd_array */ + fd_set *set; +} php_growable_fd_set; + +# define PHP_GROWABLE_FD_SET_ALLOC_SIZE(cap) \ + (offsetof(fd_set, fd_array) + (size_t)(cap) * sizeof(SOCKET)) + +static zend_always_inline void php_growable_fd_set_init(php_growable_fd_set *s, u_int capacity) +{ + s->capacity = MAX(capacity, FD_SETSIZE); + s->set = (fd_set *) pemalloc(PHP_GROWABLE_FD_SET_ALLOC_SIZE(s->capacity), 1); + s->set->fd_count = 0; +} + +/* Makes room for capacity sockets */ +static zend_always_inline void php_growable_fd_set_reserve(php_growable_fd_set *s, u_int capacity) +{ + if (capacity > s->capacity) { + do { + s->capacity *= 2; + } while (capacity > s->capacity); + s->set = (fd_set *) perealloc(s->set, PHP_GROWABLE_FD_SET_ALLOC_SIZE(s->capacity), 1); + } +} + +static zend_always_inline void php_growable_fd_set_add(php_growable_fd_set *s, php_socket_t fd) +{ + /* Without the duplicate scan of winsock's FD_SET(): callers add each socket once */ + php_growable_fd_set_reserve(s, s->set->fd_count + 1); + s->set->fd_array[s->set->fd_count++] = fd; +} + +static zend_always_inline bool php_growable_fd_set_isset(const php_growable_fd_set *s, php_socket_t fd) +{ + return FD_ISSET(fd, s->set); +} + +static zend_always_inline void php_growable_fd_set_zero(php_growable_fd_set *s) +{ + s->set->fd_count = 0; +} + +static zend_always_inline void php_growable_fd_set_copy(php_growable_fd_set *dst, const php_growable_fd_set *src) +{ + php_growable_fd_set_reserve(dst, src->set->fd_count); + memcpy(dst->set, src->set, PHP_GROWABLE_FD_SET_ALLOC_SIZE(src->set->fd_count)); +} + +static zend_always_inline void php_growable_fd_set_destroy(php_growable_fd_set *s) +{ + if (s->set) { + pefree(s->set, 1); + s->set = NULL; + } + s->capacity = 0; +} +#else +typedef struct { + size_t size; /* bytes in set */ + fd_set *set; +} php_growable_fd_set; + +# define PHP_GROWABLE_FD_SET_WORD_BITS (CHAR_BIT * sizeof(unsigned long)) +/* select() operates on whole long-sized words so the size must be a multiple of sizeof(long) */ +# define PHP_GROWABLE_FD_SET_SIZE(nfds) (ZEND_MM_ALIGNED_SIZE_EX((size_t)(nfds), PHP_GROWABLE_FD_SET_WORD_BITS) / CHAR_BIT) + +static zend_always_inline void php_growable_fd_set_init(php_growable_fd_set *s, unsigned int capacity) +{ + s->size = PHP_GROWABLE_FD_SET_SIZE(capacity); + s->set = (fd_set *) ecalloc(1, s->size); +} + +/* Makes room for the descriptors below capacity */ +static zend_always_inline void php_growable_fd_set_reserve(php_growable_fd_set *s, unsigned int capacity) +{ + size_t size = PHP_GROWABLE_FD_SET_SIZE(capacity); + if (size > s->size) { + size_t old_size = s->size; + s->size = MAX(size, old_size * 2); + s->set = (fd_set *) erealloc(s->set, s->size); + memset((char *) s->set + old_size, 0, s->size - old_size); + } +} + +static zend_always_inline void php_growable_fd_set_add(php_growable_fd_set *s, php_socket_t fd) +{ + php_growable_fd_set_reserve(s, fd + 1); + ((unsigned long *) s->set)[fd / PHP_GROWABLE_FD_SET_WORD_BITS] |= 1UL << (fd % PHP_GROWABLE_FD_SET_WORD_BITS); +} + +static zend_always_inline bool php_growable_fd_set_isset(const php_growable_fd_set *s, php_socket_t fd) +{ + return (size_t) fd < s->size * CHAR_BIT + && (((const unsigned long *) s->set)[fd / PHP_GROWABLE_FD_SET_WORD_BITS] >> (fd % PHP_GROWABLE_FD_SET_WORD_BITS)) & 1; +} + +static zend_always_inline void php_growable_fd_set_destroy(php_growable_fd_set *s) +{ + efree(s->set); +} +#endif + #define PHP_SOCK_CHUNK_SIZE 8192 diff --git a/win32/select.c b/win32/select.c index 988f037d7a4c..2dde8db37d27 100644 --- a/win32/select.c +++ b/win32/select.c @@ -36,11 +36,11 @@ PHPAPI int php_select(php_socket_t max_fd, fd_set *rfds, fd_set *wfds, fd_set *e int handle_slot_to_fd[MAXIMUM_WAIT_OBJECTS]; int n_handles = 0, i; int num_read_pipes = 0; - fd_set sock_read, sock_write, sock_except; - fd_set aread, awrite, aexcept; + php_growable_fd_set sock_read = {0}, sock_write = {0}, sock_except = {0}; + php_growable_fd_set aread = {0}, awrite = {0}, aexcept = {0}; int sock_max_fd = -1; struct timeval tvslice; - int retcode; + int retcode = -1; /* As max_fd is unsigned, non socket might overflow. */ if (max_fd > (php_socket_t)INT_MAX) { @@ -57,9 +57,9 @@ PHPAPI int php_select(php_socket_t max_fd, fd_set *rfds, fd_set *wfds, fd_set *e ms_total += tv->tv_usec / 1000; } - FD_ZERO(&sock_read); - FD_ZERO(&sock_write); - FD_ZERO(&sock_except); + php_growable_fd_set_init(&sock_read, FD_SETSIZE); + php_growable_fd_set_init(&sock_write, FD_SETSIZE); + php_growable_fd_set_init(&sock_except, FD_SETSIZE); /* build an array of handles for non-sockets */ for (i = 0; (uint32_t)i < max_fd; i++) { @@ -70,21 +70,32 @@ PHPAPI int php_select(php_socket_t max_fd, fd_set *rfds, fd_set *wfds, fd_set *e if (getsockopt((SOCKET)i, SOL_SOCKET, SO_TYPE, (char*)&_type, &_len) == 0 || WSAGetLastError() != WSAENOTSOCK) { /* socket */ if (SAFE_FD_ISSET(i, rfds)) { - FD_SET((uint32_t)i, &sock_read); + php_growable_fd_set_add(&sock_read, (SOCKET)(uintptr_t)i); } if (SAFE_FD_ISSET(i, wfds)) { - FD_SET((uint32_t)i, &sock_write); + php_growable_fd_set_add(&sock_write, (SOCKET)(uintptr_t)i); } if (SAFE_FD_ISSET(i, efds)) { - FD_SET((uint32_t)i, &sock_except); + php_growable_fd_set_add(&sock_except, (SOCKET)(uintptr_t)i); } if (i > sock_max_fd) { sock_max_fd = i; } } else { - handles[n_handles] = (HANDLE)(uintptr_t)_get_osfhandle(i); - if (handles[n_handles] != INVALID_HANDLE_VALUE) { - if (SAFE_FD_ISSET(i, rfds) && GetFileType(handles[n_handles]) == FILE_TYPE_PIPE) { + HANDLE handle = (HANDLE)(uintptr_t)_get_osfhandle(i); + if (handle != INVALID_HANDLE_VALUE) { + if (n_handles >= MAXIMUM_WAIT_OBJECTS) { + /* WaitForMultipleObjects() cannot wait on more than + * MAXIMUM_WAIT_OBJECTS (64) handles at once. Fail + * gracefully (the caller turns -1 into a warning and + * false) instead of overflowing the fixed-size + * handles[]/handle_slot_to_fd[] stack arrays. */ + errno = EINVAL; + retcode = -1; + goto cleanup; + } + handles[n_handles] = handle; + if (SAFE_FD_ISSET(i, rfds) && GetFileType(handle) == FILE_TYPE_PIPE) { num_read_pipes++; } handle_slot_to_fd[n_handles] = i; @@ -95,32 +106,39 @@ PHPAPI int php_select(php_socket_t max_fd, fd_set *rfds, fd_set *wfds, fd_set *e } if (n_handles == 0) { - /* plain sockets only - let winsock handle the whole thing */ - return select(-1, rfds, wfds, efds, tv); + /* plain sockets only - let winsock handle the whole thing. rfds/wfds/efds + * are growable sets, so this is no longer bounded by FD_SETSIZE. */ + retcode = select(-1, rfds, wfds, efds, tv); + goto cleanup; } /* mixture of handles and sockets; lets multiplex between * winsock and waiting on the handles */ - FD_ZERO(&aread); - FD_ZERO(&awrite); - FD_ZERO(&aexcept); + php_growable_fd_set_init(&aread, sock_read.set->fd_count); + php_growable_fd_set_init(&awrite, sock_write.set->fd_count); + php_growable_fd_set_init(&aexcept, sock_except.set->fd_count); limit = GetTickCount64() + ms_total; do { retcode = 0; if (sock_max_fd >= 0) { - /* overwrite the zero'd sets here; the select call - * will clear those that are not active */ - aread = sock_read; - awrite = sock_write; - aexcept = sock_except; + /* refresh the working copies; the select call will clear the fds + * that are not active. memcpy (via _copy) instead of struct + * assignment because the sets are dynamically sized. */ + php_growable_fd_set_copy(&aread, &sock_read); + php_growable_fd_set_copy(&awrite, &sock_write); + php_growable_fd_set_copy(&aexcept, &sock_except); tvslice.tv_sec = 0; tvslice.tv_usec = 100000; - retcode = select(-1, &aread, &awrite, &aexcept, &tvslice); + retcode = select(-1, aread.set, awrite.set, aexcept.set, &tvslice); + } else { + php_growable_fd_set_zero(&aread); + php_growable_fd_set_zero(&awrite); + php_growable_fd_set_zero(&aexcept); } if (n_handles > 0) { /* check handles */ @@ -150,16 +168,16 @@ PHPAPI int php_select(php_socket_t max_fd, fd_set *rfds, fd_set *wfds, fd_set *e || !PeekNamedPipe(handles[i], NULL, 0, NULL, &avail_read, NULL) || avail_read > 0 ) { - FD_SET((uint32_t)handle_slot_to_fd[i], &aread); + php_growable_fd_set_add(&aread, (SOCKET)(uintptr_t)handle_slot_to_fd[i]); retcode++; } } if (SAFE_FD_ISSET(handle_slot_to_fd[i], wfds)) { - FD_SET((uint32_t)handle_slot_to_fd[i], &awrite); + php_growable_fd_set_add(&awrite, (SOCKET)(uintptr_t)handle_slot_to_fd[i]); retcode++; } if (SAFE_FD_ISSET(handle_slot_to_fd[i], efds)) { - FD_SET((uint32_t)handle_slot_to_fd[i], &aexcept); + php_growable_fd_set_add(&aexcept, (SOCKET)(uintptr_t)handle_slot_to_fd[i]); retcode++; } } @@ -171,15 +189,29 @@ PHPAPI int php_select(php_socket_t max_fd, fd_set *rfds, fd_set *wfds, fd_set *e } } while (retcode == 0 && (ms_total == INFINITE || GetTickCount64() < limit)); + /* Copy the results back into the caller's sets. memcpy (not struct + * assignment) because the sets are dynamically sized; only fd_count + the + * used fd_array entries are written. This never overflows the caller's + * buffer: every fd in a* was present in the corresponding input set, so + * a*->fd_count <= the input fd_count, which the caller's buffer already + * held. */ if (rfds) { - *rfds = aread; + memcpy(rfds, aread.set, PHP_GROWABLE_FD_SET_ALLOC_SIZE(aread.set->fd_count)); } if (wfds) { - *wfds = awrite; + memcpy(wfds, awrite.set, PHP_GROWABLE_FD_SET_ALLOC_SIZE(awrite.set->fd_count)); } if (efds) { - *efds = aexcept; + memcpy(efds, aexcept.set, PHP_GROWABLE_FD_SET_ALLOC_SIZE(aexcept.set->fd_count)); } +cleanup: + php_growable_fd_set_destroy(&sock_read); + php_growable_fd_set_destroy(&sock_write); + php_growable_fd_set_destroy(&sock_except); + php_growable_fd_set_destroy(&aread); + php_growable_fd_set_destroy(&awrite); + php_growable_fd_set_destroy(&aexcept); + return retcode; }