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) {