Description
The host of an abstract unix domain socket starts with a null byte. This null byte trips up PHP when it tries to resolve the address to a resource. PHP always returns the same resource.
This is reproducible using the following server and client scripts. The server creates two server sockets (\0test_socket_1 and \0test_socket_2). The client connects with both sockets.
Server:
<?php
// server.php
// This script creates two abstract Unix domain sockets and listens on both
function create_server($socket_name) {
// Abstract socket: prefix with null byte
$address = "unix://\0{$socket_name}";
$socket = stream_socket_server($address, $errno, $errstr);
if (!$socket) {
echo "Failed to create socket at \\0{$socket_name}: $errstr ($errno)\n";
return null;
}
echo "Server listening on abstract socket: \\0{$socket_name}\n";
return $socket;
}
function handle_client($client_socket, $server_name) {
$data = fread($client_socket, 1024);
echo "[$server_name] Received: $data";
fclose($client_socket);
}
// Create two abstract servers
$server1 = create_server('test_socket_1');
$server2 = create_server('test_socket_2');
if (!$server1 || !$server2) {
exit(1);
}
echo "Both servers started. Waiting for connections...\n";
echo "Press Ctrl+C to stop.\n\n";
// Keep accepting connections
while (true) {
$read = array($server1, $server2);
$write = null;
$except = null;
$changed = stream_select($read, $write, $except, 1);
if ($changed === false) {
echo "stream_select error\n";
break;
}
foreach ($read as $socket) {
if ($socket === $server1) {
$client = stream_socket_accept($server1);
if ($client) {
handle_client($client, "Server1");
}
} elseif ($socket === $server2) {
$client = stream_socket_accept($server2);
if ($client) {
handle_client($client, "Server2");
}
}
}
}
?>
Client:
<?php
// Connect to first abstract socket
echo "Connecting to abstract socket 1...\n";
$socket1 = pfsockopen("unix://\0test_socket_1", 0, $errno1, $errstr1);
if (!$socket1) {
echo "Failed to connect to socket1: $errstr1 ($errno1)\n";
exit(1);
}
echo "Connected to abstract socket 1\n";
echo "Socket 1 resource type: " . get_resource_type($socket1) . "\n";
echo "Socket 1 resource ID: " . (int)$socket1 . "\n\n";
// Connect to second abstract socket
echo "Connecting to abstract socket 2...\n";
$socket2 = pfsockopen("unix://\0test_socket_2", 0, $errno2, $errstr2);
if (!$socket2) {
echo "Failed to connect to socket2: $errstr2 ($errno2)\n";
exit(1);
}
echo "Connected to abstract socket 2\n";
echo "Socket 2 resource type: " . get_resource_type($socket2) . "\n";
echo "Socket 2 resource ID: " . (int)$socket2 . "\n\n";
?>
Output:
Connecting to abstract socket 1...
Connected to abstract socket 1
Socket 1 resource type: persistent stream
Socket 1 resource ID: 4
Connecting to abstract socket 2...
Connected to abstract socket 2
Socket 2 resource type: persistent stream
Socket 2 resource ID: 4
I expect the resource IDs for socket 1 and socket 2 to be distinct.
The problem is probably the function php_stream_from_persistent_id. It accepts a persistent ID, which is of the format pfsockopen__<hostname>. This function does a strlen on the persistent ID. Because of the leading null byte in the hostname it always returns only the length of the pfsockopen__ prefix (i.e. the actual hostname is not taken into account).
PHP Version
PHP 8.4.23 (cli) (built: Jul 3 2026 12:26:56) (NTS)
Copyright (c) The PHP Group
Built by Debian
Zend Engine v4.4.23, Copyright (c) Zend Technologies
with Zend OPcache v8.4.23, Copyright (c), by Zend Technologies
with Xdebug v3.4.5, Copyright (c) 2002-2025, by Derick Rethans
Operating System
Debian 13 (trixie)
Description
The host of an abstract unix domain socket starts with a null byte. This null byte trips up PHP when it tries to resolve the address to a resource. PHP always returns the same resource.
This is reproducible using the following server and client scripts. The server creates two server sockets (
\0test_socket_1and\0test_socket_2). The client connects with both sockets.Server:
Client:
Output:
I expect the resource IDs for socket 1 and socket 2 to be distinct.
The problem is probably the function
php_stream_from_persistent_id. It accepts a persistent ID, which is of the formatpfsockopen__<hostname>. This function does a strlen on the persistent ID. Because of the leading null byte in the hostname it always returns only the length of thepfsockopen__prefix (i.e. the actual hostname is not taken into account).PHP Version
Operating System
Debian 13 (trixie)