Commit e463914fb2f for php.net

commit e463914fb2f273ecc49f7ac0a7b089c9bcd835fa
Author: Ilia Alshanetsky <ilia@ilia.ws>
Date:   Tue Jun 16 14:38:31 2026 -0400

    Fix socket_sendmsg() sending wrong fd for Socket objects in SCM_RIGHTS

    The Socket-object branch of from_zval_write_fd_array_aux() indexed the fd
    array with the 1-based loop counter (iarr[i]) instead of iarr[i - 1] like
    the resource branch, leaving slot 0 zeroed so a single Socket delivered
    fd 0 (stdin) to the receiver. Broken since the 8.0 resource-to-object
    conversion.

    Closes GH-22338

diff --git a/ext/sockets/conversions.c b/ext/sockets/conversions.c
index a419a20c873..eb4e58b3988 100644
--- a/ext/sockets/conversions.c
+++ b/ext/sockets/conversions.c
@@ -1385,7 +1385,7 @@ static void from_zval_write_fd_array_aux(zval *elem, unsigned i, void **args, se
 			return;
 		}

-		iarr[i] = sock->bsd_socket;
+		iarr[i - 1] = sock->bsd_socket;
 		return;
 	}

diff --git a/ext/sockets/tests/socket_cmsg_rights.phpt b/ext/sockets/tests/socket_cmsg_rights.phpt
index 1794eaf767a..8bfbcd57157 100644
--- a/ext/sockets/tests/socket_cmsg_rights.phpt
+++ b/ext/sockets/tests/socket_cmsg_rights.phpt
@@ -58,7 +58,7 @@
     if ($control["level"] == SOL_SOCKET &&
         $control["type"]  == SCM_RIGHTS) {
         foreach ($control["data"] as $resource) {
-            if (!is_resource($resource)) {
+            if (!is_resource($resource) && !($resource instanceof Socket)) {
                 echo "FAIL RES\n";
                 var_dump($data);
                 exit;
diff --git a/ext/sockets/tests/socket_sendmsg_scm_rights_object.phpt b/ext/sockets/tests/socket_sendmsg_scm_rights_object.phpt
new file mode 100644
index 00000000000..907ea70ac16
--- /dev/null
+++ b/ext/sockets/tests/socket_sendmsg_scm_rights_object.phpt
@@ -0,0 +1,77 @@
+--TEST--
+socket_sendmsg(): SCM_RIGHTS transfers Socket objects with the correct fds
+--EXTENSIONS--
+sockets
+--SKIPIF--
+<?php
+if (strtolower(substr(PHP_OS, 0, 3)) == 'win') {
+    die('skip not for Microsoft Windows');
+}
+if (strtolower(substr(PHP_OS, 0, 3)) == 'aix') {
+    die('skip not for AIX');
+}
+if (!defined('SCM_RIGHTS')) {
+    die('skip SCM_RIGHTS not available');
+}
+?>
+--FILE--
+<?php
+$dir = sys_get_temp_dir();
+$rpath = $dir . "/socket_sendmsg_scm_rights_object_r.sock";
+$ppath1 = $dir . "/socket_sendmsg_scm_rights_object_p1.sock";
+$ppath2 = $dir . "/socket_sendmsg_scm_rights_object_p2.sock";
+@unlink($rpath);
+@unlink($ppath1);
+@unlink($ppath2);
+
+$recv = socket_create(AF_UNIX, SOCK_DGRAM, 0);
+socket_bind($recv, $rpath);
+
+$send = socket_create(AF_UNIX, SOCK_DGRAM, 0);
+socket_connect($send, $rpath);
+
+// Two Socket objects in a single SCM_RIGHTS message, each bound to a known
+// path so the received fds can be identified and ordered via getsockname().
+$payload1 = socket_create(AF_UNIX, SOCK_DGRAM, 0);
+socket_bind($payload1, $ppath1);
+$payload2 = socket_create(AF_UNIX, SOCK_DGRAM, 0);
+socket_bind($payload2, $ppath2);
+
+socket_sendmsg($send, [
+    'iov' => ['x'],
+    'control' => [[
+        'level' => SOL_SOCKET,
+        'type' => SCM_RIGHTS,
+        'data' => [$payload1, $payload2],
+    ]],
+], 0);
+
+$data = [
+    'name' => [],
+    'buffer_size' => 64,
+    'controllen' => socket_cmsg_space(SOL_SOCKET, SCM_RIGHTS, 2),
+];
+socket_recvmsg($recv, $data, 0);
+
+$got = $data['control'][0]['data'];
+var_dump(count($got));
+var_dump($got[0] instanceof Socket);
+var_dump($got[1] instanceof Socket);
+socket_getsockname($got[0], $addr0);
+socket_getsockname($got[1], $addr1);
+var_dump($addr0 === $ppath1);
+var_dump($addr1 === $ppath2);
+?>
+--CLEAN--
+<?php
+$dir = sys_get_temp_dir();
+@unlink($dir . "/socket_sendmsg_scm_rights_object_r.sock");
+@unlink($dir . "/socket_sendmsg_scm_rights_object_p1.sock");
+@unlink($dir . "/socket_sendmsg_scm_rights_object_p2.sock");
+?>
+--EXPECT--
+int(2)
+bool(true)
+bool(true)
+bool(true)
+bool(true)