Skip to content

Commit cf0c397

Browse files
bukkaericmann
authored andcommitted
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.
1 parent 8ae80d2 commit cf0c397

File tree

4 files changed

+78
-5
lines changed

4 files changed

+78
-5
lines changed

ext/standard/fsock.c

Lines changed: 25 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,28 @@
2323
#include "php_network.h"
2424
#include "file.h"
2525

26+
static size_t php_fsockopen_format_host_port(char **message, const char *prefix, size_t prefix_len,
27+
const char *host, size_t host_len, zend_long port)
28+
{
29+
char portbuf[32];
30+
int portlen = snprintf(portbuf, sizeof(portbuf), ":" ZEND_LONG_FMT, port);
31+
size_t total_len = prefix_len + host_len + portlen;
32+
33+
char *result = emalloc(total_len + 1);
34+
35+
if (prefix_len > 0) {
36+
memcpy(result, prefix, prefix_len);
37+
}
38+
memcpy(result + prefix_len, host, host_len);
39+
memcpy(result + prefix_len + host_len, portbuf, portlen);
40+
41+
result[total_len] = '\0';
42+
43+
*message = result;
44+
45+
return total_len;
46+
}
47+
2648
/* {{{ php_fsockopen() */
2749

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

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

6891
if (port > 0) {
69-
hostname_len = spprintf(&hostname, 0, "%s:" ZEND_LONG_FMT, host, port);
92+
hostname_len = php_fsockopen_format_host_port(&hostname, "", 0, host, host_len, port);
7093
} else {
7194
hostname_len = host_len;
7295
hostname = host;
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
--TEST--
2+
GHSA-3cr5-j632-f35r: Null byte termination in fsockopen()
3+
--FILE--
4+
<?php
5+
6+
$server = stream_socket_server("tcp://localhost:0");
7+
8+
if (preg_match('/:(\d+)$/', stream_socket_get_name($server, false), $m)) {
9+
$client = fsockopen("localhost\0.example.com", intval($m[1]));
10+
var_dump($client);
11+
if ($client) {
12+
fclose($client);
13+
}
14+
}
15+
fclose($server);
16+
17+
?>
18+
--EXPECTF--
19+
20+
Warning: fsockopen(): Unable to connect to localhost:%d (The hostname must not contain null bytes) in %s
21+
bool(false)
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
--TEST--
2+
GHSA-3cr5-j632-f35r: Null byte termination in stream_socket_client()
3+
--FILE--
4+
<?php
5+
6+
$server = stream_socket_server("tcp://localhost:0");
7+
$socket_name = stream_socket_get_name($server, false);
8+
9+
if (preg_match('/:(\d+)$/', $socket_name, $m)) {
10+
$port = $m[1];
11+
$client = stream_socket_client("tcp://localhost\0.example.com:$port");
12+
var_dump($client);
13+
if ($client) {
14+
fclose($client);
15+
}
16+
} else {
17+
echo "Could not extract port from socket name: $socket_name\n";
18+
}
19+
20+
fclose($server);
21+
22+
?>
23+
--EXPECTF--
24+
25+
Warning: stream_socket_client(): Unable to connect to tcp://localhost\0.example.com:%d (The hostname must not contain null bytes) in %s
26+
bool(false)

main/streams/xp_socket.c

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -616,12 +616,15 @@ static inline char *parse_ip_address_ex(const char *str, size_t str_len, int *po
616616
char *colon;
617617
char *host = NULL;
618618

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

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

0 commit comments

Comments
 (0)