Commit bad5aaca7ae for php

commit bad5aaca7ae34c1002b5224a68cfc00b8a25a9a6
Author: Martin Mirchev <mmirchev@comp.nus.edu.sg>
Date:   Sat Oct 24 02:29:40 2026 +0800

    Fix GH-24616: Socket transports accept ports above 65535

    parse_ip_address_ex() and php_network_parse_network_address_with_port()
    read the port with atoi() and did not check its range, so a port above
    65535 or below 0 silently wrapped to another port. Reject such ports.

    bug69521.phpt relied on this wrapping (port 74321), use an ephemeral
    port instead.

    Close GH-24621

diff --git a/ext/standard/tests/network/gh24616.phpt b/ext/standard/tests/network/gh24616.phpt
new file mode 100644
index 00000000000..5a039805f8a
--- /dev/null
+++ b/ext/standard/tests/network/gh24616.phpt
@@ -0,0 +1,37 @@
+--TEST--
+GH-24616 (fsockopen()/stream_socket_client() accept TCP ports above 65535)
+--FILE--
+<?php
+$srv = stream_socket_server("tcp://127.0.0.1:0");
+$name = stream_socket_get_name($srv, false);
+$port = (int) substr($name, strrpos($name, ':') + 1);
+$bad = $port + 65536;
+
+var_dump(@stream_socket_client("tcp://127.0.0.1:$bad", $errno, $errstr));
+var_dump($errstr === "Failed to parse port of address \"127.0.0.1:$bad\"");
+var_dump(@fsockopen("127.0.0.1", $bad));
+var_dump(@stream_socket_client("tcp://[::1]:$bad"));
+var_dump(@stream_socket_client("tcp://127.0.0.1:-1"));
+var_dump(@stream_socket_client("tcp://127.0.0.1:99999999999999999999"));
+var_dump(@stream_socket_server("tcp://127.0.0.1:65536"));
+
+$udp = stream_socket_server("udp://127.0.0.1:0", $errno, $errstr, STREAM_SERVER_BIND);
+$name = stream_socket_get_name($udp, false);
+$bad = (int) substr($name, strrpos($name, ':') + 1) + 65536;
+$client = stream_socket_client("udp://127.0.0.1:1");
+var_dump(@stream_socket_sendto($client, "x", 0, "127.0.0.1:$bad"));
+
+/* Valid ports still work */
+$c = stream_socket_client("tcp://127.0.0.1:$port");
+var_dump(stream_socket_get_name($c, true) === "127.0.0.1:$port");
+?>
+--EXPECT--
+bool(false)
+bool(true)
+bool(false)
+bool(false)
+bool(false)
+bool(false)
+bool(false)
+bool(false)
+bool(true)
diff --git a/ext/standard/tests/streams/bug69521.phpt b/ext/standard/tests/streams/bug69521.phpt
index 9ec244f7996..287a9327545 100644
--- a/ext/standard/tests/streams/bug69521.phpt
+++ b/ext/standard/tests/streams/bug69521.phpt
@@ -2,8 +2,8 @@
 Bug #69521	Segfault in gc_collect_cycles()
 --FILE--
 <?php
-$serverUri = "tcp://127.0.0.1:74321";
-$sock = stream_socket_server($serverUri, $errno, $errstr, STREAM_SERVER_BIND | STREAM_SERVER_LISTEN);
+$sock = stream_socket_server("tcp://127.0.0.1:0", $errno, $errstr, STREAM_SERVER_BIND | STREAM_SERVER_LISTEN);
+$serverUri = "tcp://" . stream_socket_get_name($sock, false);

 $fp = stream_socket_client($serverUri, $errNumber, $errString, 5, STREAM_CLIENT_CONNECT);

diff --git a/main/network.c b/main/network.c
index 40a80a6f2ab..e2dd6d89c7e 100644
--- a/main/network.c
+++ b/main/network.c
@@ -556,7 +556,7 @@ PHPAPI zend_result php_network_parse_network_address_with_port(const char *addr,
 	const char *colon;
 	char *tmp;
 	zend_result ret = FAILURE;
-	short port;
+	zend_long port;
 	struct sockaddr_in *in4 = (struct sockaddr_in*)sa;
 	struct sockaddr **psal;
 	int n;
@@ -574,14 +574,18 @@ PHPAPI zend_result php_network_parse_network_address_with_port(const char *addr,
 		if (!colon || colon[1] != ':') {
 			return FAILURE;
 		}
-		port = atoi(colon + 2);
+		port = ZEND_STRTOL(colon + 2, NULL, 10);
 		addr++;
 	} else {
 		colon = memchr(addr, ':', addrlen);
 		if (!colon) {
 			return FAILURE;
 		}
-		port = atoi(colon + 1);
+		port = ZEND_STRTOL(colon + 1, NULL, 10);
+	}
+
+	if (port < 0 || port > 65535) {
+		return FAILURE;
 	}

 	tmp = estrndup(addr, colon - addr);
diff --git a/main/streams/xp_socket.c b/main/streams/xp_socket.c
index 69eb078323b..8d577de6e76 100644
--- a/main/streams/xp_socket.c
+++ b/main/streams/xp_socket.c
@@ -615,6 +615,17 @@ static inline int parse_unix_address(php_stream_xport_param *xparam, struct sock
 }
 #endif

+static inline bool parse_port(const char *str, int *portno)
+{
+	/* Like atoi(), but reject ports that do not fit in 16 bits */
+	zend_long port = ZEND_STRTOL(str, NULL, 10);
+	if (port < 0 || port > 65535) {
+		return false;
+	}
+	*portno = (int) port;
+	return true;
+}
+
 static inline char *parse_ip_address_ex(const char *str, size_t str_len, int *portno, int get_err, zend_string **err)
 {
 	const char *colon;
@@ -635,7 +646,12 @@ static inline char *parse_ip_address_ex(const char *str, size_t str_len, int *po
 			}
 			return NULL;
 		}
-		*portno = atoi(p + 2);
+		if (!parse_port(p + 2, portno)) {
+			if (get_err) {
+				*err = strpprintf(0, "Failed to parse port of address \"%s\"", str);
+			}
+			return NULL;
+		}
 		return estrndup(str + 1, p - str - 1);
 	}
 #endif
@@ -645,7 +661,12 @@ static inline char *parse_ip_address_ex(const char *str, size_t str_len, int *po
 		colon = NULL;
 	}
 	if (colon) {
-		*portno = atoi(colon + 1);
+		if (!parse_port(colon + 1, portno)) {
+			if (get_err) {
+				*err = strpprintf(0, "Failed to parse port of address \"%s\"", str);
+			}
+			return NULL;
+		}
 		host = estrndup(str, colon - str);
 	} else {
 		if (get_err) {