From 36150278addd8686a9899559241296094bd57282 Mon Sep 17 00:00:00 2001 From: Jakub Zelenka Date: Thu, 10 Apr 2025 15:15:36 +0200 Subject: [PATCH 2/4] 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. (cherry picked from commit cac8f7f1cf4939f55f06b68120040f057682d89c) --- ext/standard/fsock.c | 27 +++++++++++++++++-- .../tests/network/ghsa-3cr5-j632-f35r.phpt | 21 +++++++++++++++ .../tests/streams/ghsa-3cr5-j632-f35r.phpt | 26 ++++++++++++++++++ main/streams/xp_socket.c | 9 ++++--- 4 files changed, 78 insertions(+), 5 deletions(-) create mode 100644 ext/standard/tests/network/ghsa-3cr5-j632-f35r.phpt create mode 100644 ext/standard/tests/streams/ghsa-3cr5-j632-f35r.phpt diff --git a/ext/standard/fsock.c b/ext/standard/fsock.c index a9c3cb0bf5d..636dbb6e359 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-- + +--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-- + +--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 68df3366340..47f1bdd4b1d 100644 --- a/main/streams/xp_socket.c +++ b/main/streams/xp_socket.c @@ -580,12 +580,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 = strpprintf(0, "The hostname must not contain null bytes"); + 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); -- 2.50.0