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)