Skip to content

Commit 84f90c5

Browse files
committed
Merge branch 'PHP-8.5'
* PHP-8.5: phpGH-22617: avoid null byte truncation of persistent stream keys
2 parents 207dd1d + 3583a7b commit 84f90c5

5 files changed

Lines changed: 78 additions & 3 deletions

File tree

ext/standard/fsock.c

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
#include <stddef.h>
2121
#include "php_network.h"
2222
#include "file.h"
23+
#include "streams/php_streams_int.h"
2324

2425
static size_t php_fsockopen_format_host_port(char **message, const char *prefix, size_t prefix_len,
2526
const char *host, size_t host_len, zend_long port)
@@ -82,8 +83,9 @@ static void php_fsockopen_stream(INTERNAL_FUNCTION_PARAMETERS, int persistent)
8283
}
8384

8485
if (persistent) {
85-
php_fsockopen_format_host_port(&hashkey, "pfsockopen__", strlen("pfsockopen__"), host,
86-
host_len, port);
86+
zend_string *escaped = php_stream_escape_persistent_key(host, host_len);
87+
spprintf(&hashkey, 0, "pfsockopen__%s:" ZEND_LONG_FMT, ZSTR_VAL(escaped), port);
88+
zend_string_release_ex(escaped, false);
8789
}
8890

8991
if (port > 0) {

ext/standard/streamsfuncs.c

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
#include "streamsfuncs.h"
2222
#include "php_network.h"
2323
#include "php_string.h"
24+
#include "streams/php_streams_int.h"
2425
#ifdef HAVE_UNISTD_H
2526
#include <unistd.h>
2627
#endif
@@ -139,7 +140,9 @@ PHP_FUNCTION(stream_socket_client)
139140
context = php_stream_context_from_zval(zcontext, flags & PHP_FILE_NO_DEFAULT_CONTEXT);
140141

141142
if (flags & PHP_STREAM_CLIENT_PERSISTENT) {
142-
spprintf(&hashkey, 0, "stream_socket_client__%s", ZSTR_VAL(host));
143+
zend_string *escaped = php_stream_escape_persistent_key(ZSTR_VAL(host), ZSTR_LEN(host));
144+
spprintf(&hashkey, 0, "stream_socket_client__%s", ZSTR_VAL(escaped));
145+
zend_string_release_ex(escaped, false);
143146
}
144147

145148
/* prepare the timeout value for use */
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
--TEST--
2+
GH-22617: Persistent abstract unix domain sockets resolved to wrong resource
3+
--CREDITS--
4+
Roysten
5+
--SKIPIF--
6+
<?php
7+
if (PHP_OS_FAMILY !== "Linux") die("skip abstract unix domain sockets are Linux-only");
8+
if (!function_exists("pfsockopen")) die("skip pfsockopen() not available");
9+
?>
10+
--FILE--
11+
<?php
12+
// Abstract sockets: the address is prefixed with a null byte.
13+
$name1 = "gh22617_" . getmypid() . "_1";
14+
$name2 = "gh22617_" . getmypid() . "_2";
15+
16+
$server1 = stream_socket_server("unix://\0$name1", $errno, $errstr);
17+
$server2 = stream_socket_server("unix://\0$name2", $errno, $errstr);
18+
var_dump($server1 !== false, $server2 !== false);
19+
20+
// Connect to the first abstract socket.
21+
$socket1 = pfsockopen("unix://\0$name1", 0, $errno1, $errstr1);
22+
var_dump($socket1 !== false);
23+
var_dump(get_resource_type($socket1));
24+
25+
// Connect to the second abstract socket.
26+
$socket2 = pfsockopen("unix://\0$name2", 0, $errno2, $errstr2);
27+
var_dump($socket2 !== false);
28+
var_dump(get_resource_type($socket2));
29+
30+
// The two distinct abstract sockets must resolve to distinct resources.
31+
var_dump((int) $socket1 !== (int) $socket2);
32+
33+
fclose($socket1);
34+
fclose($socket2);
35+
fclose($server1);
36+
fclose($server2);
37+
?>
38+
--EXPECT--
39+
bool(true)
40+
bool(true)
41+
bool(true)
42+
string(17) "persistent stream"
43+
bool(true)
44+
string(17) "persistent stream"
45+
bool(true)

main/streams/php_streams_int.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,3 +60,7 @@
6060
* any other function that expects standard modes and you allow non-standard
6161
* ones. result should be a char[5]. */
6262
void php_stream_mode_sanitize_fdopen_fopencookie(php_stream *stream, char *result);
63+
64+
/* Escapes NUL and backslash bytes so a host containing them cannot truncate or
65+
* collide the persistent stream hash key. */
66+
zend_string *php_stream_escape_persistent_key(const char *host, size_t hostlen);

main/streams/streams.c

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -140,6 +140,27 @@ PHPAPI int php_stream_from_persistent_id(const char *persistent_id, php_stream *
140140

141141
/* }}} */
142142

143+
zend_string *php_stream_escape_persistent_key(const char *host, size_t hostlen)
144+
{
145+
zend_string *escaped = zend_string_safe_alloc(hostlen, 2, 0, 0);
146+
char *ptr = ZSTR_VAL(escaped);
147+
for (size_t i = 0; i < hostlen; i++) {
148+
if (host[i] == '\0') {
149+
*ptr++ = '\\';
150+
*ptr++ = '0';
151+
} else if (host[i] == '\\') {
152+
*ptr++ = '\\';
153+
*ptr++ = '\\';
154+
} else {
155+
*ptr++ = host[i];
156+
}
157+
}
158+
*ptr = '\0';
159+
ZSTR_LEN(escaped) = ptr - ZSTR_VAL(escaped);
160+
161+
return escaped;
162+
}
163+
143164
/* allocate a new stream for a particular ops */
144165
PHPAPI php_stream *_php_stream_alloc(const php_stream_ops *ops, void *abstract, const char *persistent_id, const char *mode STREAMS_DC) /* {{{ */
145166
{

0 commit comments

Comments
 (0)