Commit 2bca7364790 for php.net
commit 2bca7364790124b75fbfc9e10b08277e3744cafd
Author: David Carlier <devnexen@gmail.com>
Date: Thu Sep 17 20:06:08 2026 +0100
ext/sockets: socket_select() silent set truncation on Windows.
php_sock_array_to_fd_set() returned a flag rather than the socket count, so
PHP_SAFE_MAX_FD() got a zero and never fired. Sets larger than FD_SETSIZE
were quietly truncated and select() reported on the survivors.
Close GH-23632
diff --git a/NEWS b/NEWS
index 69b3dec86ed..42534ef4c37 100644
--- a/NEWS
+++ b/NEWS
@@ -32,6 +32,10 @@ PHP NEWS
. Fixed PDOStatement::getColumnMeta() reading out of bounds for an invalid
column index. (Ilia Alshanetsky)
+- Sockets:
+ . Fixed socket_select() silently truncating sets larger than FD_SETSIZE on
+ Windows. (David Carlier)
+
- SQLite:
. Fixed a crash when SQLite3::close() is called from a userland callback.
(Ilia Alshanetsky)
diff --git a/ext/sockets/sockets.c b/ext/sockets/sockets.c
index 565a2d791bc..5e70df60726 100644
--- a/ext/sockets/sockets.c
+++ b/ext/sockets/sockets.c
@@ -542,7 +542,7 @@ static int php_sock_array_to_fd_set(uint32_t arg_num, zval *sock_array, fd_set *
num++;
} ZEND_HASH_FOREACH_END();
- return num ? 1 : 0;
+ return num;
}
/* }}} */
@@ -593,7 +593,7 @@ PHP_FUNCTION(socket_select)
struct timeval *tv_p = NULL;
fd_set rfds, wfds, efds;
PHP_SOCKET max_fd = 0;
- int retval, sets = 0;
+ int retval, max_set_count = 0;
zend_long sec, usec = 0;
bool sec_is_null = 0;
@@ -611,30 +611,39 @@ PHP_FUNCTION(socket_select)
FD_ZERO(&efds);
if (r_array != NULL) {
- sets += retval = php_sock_array_to_fd_set(1, r_array, &rfds, &max_fd);
+ retval = php_sock_array_to_fd_set(1, r_array, &rfds, &max_fd);
if (retval == -1) {
RETURN_THROWS();
}
+ if (retval > max_set_count) {
+ max_set_count = retval;
+ }
}
if (w_array != NULL) {
- sets += retval = php_sock_array_to_fd_set(2, w_array, &wfds, &max_fd);
+ retval = php_sock_array_to_fd_set(2, w_array, &wfds, &max_fd);
if (retval == -1) {
RETURN_THROWS();
}
+ if (retval > max_set_count) {
+ max_set_count = retval;
+ }
}
if (e_array != NULL) {
- sets += retval = php_sock_array_to_fd_set(3, e_array, &efds, &max_fd);
+ retval = php_sock_array_to_fd_set(3, e_array, &efds, &max_fd);
if (retval == -1) {
RETURN_THROWS();
}
+ if (retval > max_set_count) {
+ max_set_count = retval;
+ }
}
- if (!sets) {
+ if (!max_set_count) {
zend_value_error("socket_select(): At least one array argument must be passed");
RETURN_THROWS();
}
- if (!PHP_SAFE_MAX_FD(max_fd, 0)) {
+ if (!PHP_SAFE_MAX_FD(max_fd, max_set_count)) {
RETURN_FALSE;
}
diff --git a/ext/sockets/tests/socket_select_fd_setsize_overflow.phpt b/ext/sockets/tests/socket_select_fd_setsize_overflow.phpt
new file mode 100644
index 00000000000..bb20d0df919
--- /dev/null
+++ b/ext/sockets/tests/socket_select_fd_setsize_overflow.phpt
@@ -0,0 +1,50 @@
+--TEST--
+socket_select() reports an overflowing set instead of silently truncating it
+--EXTENSIONS--
+sockets
+--SKIPIF--
+<?php
+if (PHP_OS_FAMILY !== 'Windows') {
+ die('skip Windows only, elsewhere the overflow is caught through max_fd');
+}
+?>
+--FILE--
+<?php
+$sockets = [];
+for ($i = 0; $i < 300; $i++) {
+ $socket = socket_create(AF_INET, SOCK_DGRAM, SOL_UDP);
+ if ($socket === false) {
+ break;
+ }
+ $sockets[] = $socket;
+}
+
+var_dump(count($sockets) > 256);
+
+$read = $sockets;
+$write = $except = null;
+var_dump(socket_select($read, $write, $except, 0));
+
+$write = $sockets;
+$read = $except = null;
+var_dump(socket_select($read, $write, $except, 0));
+
+$except = $sockets;
+$read = $write = null;
+var_dump(socket_select($read, $write, $except, 0));
+
+foreach ($sockets as $socket) {
+ socket_close($socket);
+}
+?>
+--EXPECTF--
+bool(true)
+
+Warning: socket_select(): PHP needs to be recompiled with a larger value of FD_SETSIZE.%A
+bool(false)
+
+Warning: socket_select(): PHP needs to be recompiled with a larger value of FD_SETSIZE.%A
+bool(false)
+
+Warning: socket_select(): PHP needs to be recompiled with a larger value of FD_SETSIZE.%A
+bool(false)