diff --git a/ext/standard/fsock.c b/ext/standard/fsock.c index 2b9e00a57554..c8e6ed08e4c0 100644 --- a/ext/standard/fsock.c +++ b/ext/standard/fsock.c @@ -22,6 +22,7 @@ #include #include "php_network.h" #include "file.h" +#include "streams/php_streams_int.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) @@ -84,8 +85,9 @@ static void php_fsockopen_stream(INTERNAL_FUNCTION_PARAMETERS, int persistent) } if (persistent) { - php_fsockopen_format_host_port(&hashkey, "pfsockopen__", strlen("pfsockopen__"), host, - host_len, port); + zend_string *escaped = php_stream_escape_persistent_key(host, host_len); + spprintf(&hashkey, 0, "pfsockopen__%s:" ZEND_LONG_FMT, ZSTR_VAL(escaped), port); + zend_string_release_ex(escaped, false); } if (port > 0) { diff --git a/ext/standard/streamsfuncs.c b/ext/standard/streamsfuncs.c index 1929075b60b7..391a72b5d9e0 100644 --- a/ext/standard/streamsfuncs.c +++ b/ext/standard/streamsfuncs.c @@ -23,6 +23,7 @@ #include "streamsfuncs.h" #include "php_network.h" #include "php_string.h" +#include "streams/php_streams_int.h" #ifdef HAVE_UNISTD_H #include #endif @@ -130,7 +131,9 @@ PHP_FUNCTION(stream_socket_client) context = php_stream_context_from_zval(zcontext, flags & PHP_FILE_NO_DEFAULT_CONTEXT); if (flags & PHP_STREAM_CLIENT_PERSISTENT) { - spprintf(&hashkey, 0, "stream_socket_client__%s", ZSTR_VAL(host)); + zend_string *escaped = php_stream_escape_persistent_key(ZSTR_VAL(host), ZSTR_LEN(host)); + spprintf(&hashkey, 0, "stream_socket_client__%s", ZSTR_VAL(escaped)); + zend_string_release_ex(escaped, false); } /* prepare the timeout value for use */ diff --git a/ext/standard/tests/streams/gh22617.phpt b/ext/standard/tests/streams/gh22617.phpt new file mode 100644 index 000000000000..83dbd832e652 --- /dev/null +++ b/ext/standard/tests/streams/gh22617.phpt @@ -0,0 +1,45 @@ +--TEST-- +GH-22617: Persistent abstract unix domain sockets resolved to wrong resource +--CREDITS-- +Roysten +--SKIPIF-- + +--FILE-- + +--EXPECT-- +bool(true) +bool(true) +bool(true) +string(17) "persistent stream" +bool(true) +string(17) "persistent stream" +bool(true) diff --git a/main/streams/php_streams_int.h b/main/streams/php_streams_int.h index 7580088fba31..f9dda9a52ff7 100644 --- a/main/streams/php_streams_int.h +++ b/main/streams/php_streams_int.h @@ -62,3 +62,7 @@ * any other function that expects standard modes and you allow non-standard * ones. result should be a char[5]. */ void php_stream_mode_sanitize_fdopen_fopencookie(php_stream *stream, char *result); + +/* Escapes NUL and backslash bytes so a host containing them cannot truncate or + * collide the persistent stream hash key. */ +zend_string *php_stream_escape_persistent_key(const char *host, size_t hostlen); diff --git a/main/streams/streams.c b/main/streams/streams.c index 6feb8b7c01b5..368de1a64774 100644 --- a/main/streams/streams.c +++ b/main/streams/streams.c @@ -140,6 +140,27 @@ PHPAPI int php_stream_from_persistent_id(const char *persistent_id, php_stream * /* }}} */ +zend_string *php_stream_escape_persistent_key(const char *host, size_t hostlen) +{ + zend_string *escaped = zend_string_safe_alloc(hostlen, 2, 0, 0); + char *ptr = ZSTR_VAL(escaped); + for (size_t i = 0; i < hostlen; i++) { + if (host[i] == '\0') { + *ptr++ = '\\'; + *ptr++ = '0'; + } else if (host[i] == '\\') { + *ptr++ = '\\'; + *ptr++ = '\\'; + } else { + *ptr++ = host[i]; + } + } + *ptr = '\0'; + ZSTR_LEN(escaped) = ptr - ZSTR_VAL(escaped); + + return escaped; +} + static zend_llist *php_get_wrapper_errors_list(php_stream_wrapper *wrapper) { if (!FG(wrapper_errors)) {