Commit cf0c39723ee for php.net

commit cf0c39723ee05fac979e8f11a0f0c0645e61a83a
Author: Jakub Zelenka <bukka@php.net>
Date:   Thu Apr 10 15:15:36 2025 +0200

    Fix GHSA-3cr5-j632-f35r: Null byte in hostnames

    This fixes stream_socket_client() and fsockopen().

    Specifically it adds a check to parse_ip_address_ex and it also makes
    sure that the \0 is not ignored in fsockopen() hostname formatting.

diff --git a/ext/standard/fsock.c b/ext/standard/fsock.c
index cb7a471e935..2b9e00a5755 100644
--- a/ext/standard/fsock.c
+++ b/ext/standard/fsock.c
@@ -23,6 +23,28 @@
 #include "php_network.h"
 #include "file.h"

+static size_t php_fsockopen_format_host_port(char **message, const char *prefix, size_t prefix_len,
+	const char *host, size_t host_len, zend_long port)
+{
+    char portbuf[32];
+    int portlen = snprintf(portbuf, sizeof(portbuf), ":" ZEND_LONG_FMT, port);
+    size_t total_len = prefix_len + host_len + portlen;
+
+    char *result = emalloc(total_len + 1);
+
+	if (prefix_len > 0) {
+    	memcpy(result, prefix, prefix_len);
+	}
+    memcpy(result + prefix_len, host, host_len);
+    memcpy(result + prefix_len + host_len, portbuf, portlen);
+
+    result[total_len] = '\0';
+
+    *message = result;
+
+	return total_len;
+}
+
 /* {{{ php_fsockopen() */

 static void php_fsockopen_stream(INTERNAL_FUNCTION_PARAMETERS, int persistent)
@@ -62,11 +84,12 @@ static void php_fsockopen_stream(INTERNAL_FUNCTION_PARAMETERS, int persistent)
 	}

 	if (persistent) {
-		spprintf(&hashkey, 0, "pfsockopen__%s:" ZEND_LONG_FMT, host, port);
+		php_fsockopen_format_host_port(&hashkey, "pfsockopen__", strlen("pfsockopen__"), host,
+				host_len, port);
 	}

 	if (port > 0) {
-		hostname_len = spprintf(&hostname, 0, "%s:" ZEND_LONG_FMT, host, port);
+		hostname_len = php_fsockopen_format_host_port(&hostname, "", 0, host, host_len, port);
 	} else {
 		hostname_len = host_len;
 		hostname = host;
diff --git a/ext/standard/tests/network/ghsa-3cr5-j632-f35r.phpt b/ext/standard/tests/network/ghsa-3cr5-j632-f35r.phpt
new file mode 100644
index 00000000000..7556c3be94c
--- /dev/null
+++ b/ext/standard/tests/network/ghsa-3cr5-j632-f35r.phpt
@@ -0,0 +1,21 @@
+--TEST--
+GHSA-3cr5-j632-f35r: Null byte termination in fsockopen()
+--FILE--
+<?php
+
+$server = stream_socket_server("tcp://localhost:0");
+
+if (preg_match('/:(\d+)$/', stream_socket_get_name($server, false), $m)) {
+    $client = fsockopen("localhost\0.example.com", intval($m[1]));
+    var_dump($client);
+    if ($client) {
+        fclose($client);
+    }
+}
+fclose($server);
+
+?>
+--EXPECTF--
+
+Warning: fsockopen(): Unable to connect to localhost:%d (The hostname must not contain null bytes) in %s
+bool(false)
diff --git a/ext/standard/tests/streams/ghsa-3cr5-j632-f35r.phpt b/ext/standard/tests/streams/ghsa-3cr5-j632-f35r.phpt
new file mode 100644
index 00000000000..52f9263c99a
--- /dev/null
+++ b/ext/standard/tests/streams/ghsa-3cr5-j632-f35r.phpt
@@ -0,0 +1,26 @@
+--TEST--
+GHSA-3cr5-j632-f35r: Null byte termination in stream_socket_client()
+--FILE--
+<?php
+
+$server = stream_socket_server("tcp://localhost:0");
+$socket_name = stream_socket_get_name($server, false);
+
+if (preg_match('/:(\d+)$/', $socket_name, $m)) {
+    $port = $m[1];
+    $client = stream_socket_client("tcp://localhost\0.example.com:$port");
+    var_dump($client);
+    if ($client) {
+        fclose($client);
+    }
+} else {
+    echo "Could not extract port from socket name: $socket_name\n";
+}
+
+fclose($server);
+
+?>
+--EXPECTF--
+
+Warning: stream_socket_client(): Unable to connect to tcp://localhost\0.example.com:%d (The hostname must not contain null bytes) in %s
+bool(false)
diff --git a/main/streams/xp_socket.c b/main/streams/xp_socket.c
index 606d1499456..b1d89bc44cb 100644
--- a/main/streams/xp_socket.c
+++ b/main/streams/xp_socket.c
@@ -616,12 +616,15 @@ static inline char *parse_ip_address_ex(const char *str, size_t str_len, int *po
 	char *colon;
 	char *host = NULL;

-#ifdef HAVE_IPV6
-	char *p;
+	if (memchr(str, '\0', str_len)) {
+		*err = ZSTR_INIT_LITERAL("The hostname must not contain null bytes", 0);
+		return NULL;
+	}

+#ifdef HAVE_IPV6
 	if (*(str) == '[' && str_len > 1) {
 		/* IPV6 notation to specify raw address with port (i.e. [fe80::1]:80) */
-		p = memchr(str + 1, ']', str_len - 2);
+		char *p = memchr(str + 1, ']', str_len - 2);
 		if (!p || *(p + 1) != ':') {
 			if (get_err) {
 				*err = strpprintf(0, "Failed to parse IPv6 address \"%s\"", str);