diff --git a/NEWS b/NEWS index bf80ab696b090..1984a4a81dc52 100644 --- a/NEWS +++ b/NEWS @@ -25,6 +25,8 @@ PHP NEWS (timwolla) . Fixed double-free when assigning to $errors fails when using the Uri\WhatWg\Url parser. (timwolla) + . Reject out-of-range ports when using the Uri\Rfc3986\Uri parser. + (timwolla) . Clean up naming of internal API. (timwolla) 28 Aug 2025, PHP 8.5.0beta2 diff --git a/Zend/Optimizer/zend_func_infos.h b/Zend/Optimizer/zend_func_infos.h index 5313a85d7a6c0..b7b118c710c53 100644 --- a/Zend/Optimizer/zend_func_infos.h +++ b/Zend/Optimizer/zend_func_infos.h @@ -510,7 +510,7 @@ static const func_info_t func_infos[] = { F1("getcwd", MAY_BE_STRING|MAY_BE_FALSE), F1("readdir", MAY_BE_STRING|MAY_BE_FALSE), F1("scandir", MAY_BE_ARRAY|MAY_BE_ARRAY_KEY_LONG|MAY_BE_ARRAY_OF_STRING|MAY_BE_FALSE), - F1("glob", MAY_BE_ARRAY|MAY_BE_ARRAY_KEY_LONG|MAY_BE_ARRAY_OF_STRING|MAY_BE_FALSE), + FN("glob", MAY_BE_ARRAY|MAY_BE_ARRAY_KEY_LONG|MAY_BE_ARRAY_OF_STRING|MAY_BE_FALSE), F1("exec", MAY_BE_STRING|MAY_BE_FALSE), F1("system", MAY_BE_STRING|MAY_BE_FALSE), F1("escapeshellcmd", MAY_BE_STRING), diff --git a/Zend/tests/require_directory.phpt b/Zend/tests/require_directory.phpt new file mode 100644 index 0000000000000..c8b4b6a6382cb --- /dev/null +++ b/Zend/tests/require_directory.phpt @@ -0,0 +1,21 @@ +--TEST-- +Including a directory generates an error +--SKIPIF-- + +--FILE-- + +--EXPECTF-- +Notice: require(): Read of %d bytes failed with errno=21 Is a directory in %s on line %d + +Fatal error: Uncaught Error: Failed opening required '%s' (include_path='.:') in %s:%d +Stack trace: +#0 {main} + thrown in %s on line %d diff --git a/Zend/tests/require_directory_windows.phpt b/Zend/tests/require_directory_windows.phpt new file mode 100644 index 0000000000000..d8b68c2064e57 --- /dev/null +++ b/Zend/tests/require_directory_windows.phpt @@ -0,0 +1,21 @@ +--TEST-- +Including a directory generates an error (Windows variant) +--SKIPIF-- + +--FILE-- + +--EXPECTF-- +Warning: require(%s): Failed to open stream: Permission denied in %s on line %d + +Fatal error: Uncaught Error: Failed opening required '%s' (include_path='%s') in %s:%d +Stack trace: +#0 {main} + thrown in %s on line %d diff --git a/ext/standard/basic_functions.stub.php b/ext/standard/basic_functions.stub.php index 541415a5ab918..f909022c0498a 100644 --- a/ext/standard/basic_functions.stub.php +++ b/ext/standard/basic_functions.stub.php @@ -2697,7 +2697,6 @@ function scandir(string $directory, int $sorting_order = SCANDIR_SORT_ASCENDING, /** * @return array|false - * @refcount 1 */ function glob(string $pattern, int $flags = 0): array|false {} diff --git a/ext/standard/basic_functions_arginfo.h b/ext/standard/basic_functions_arginfo.h index ba9d1710137cc..cced5b0961271 100644 --- a/ext/standard/basic_functions_arginfo.h +++ b/ext/standard/basic_functions_arginfo.h @@ -1,5 +1,5 @@ /* This is a generated file, edit the .stub.php file instead. - * Stub hash: deb4ea96dd130d8a0174678095c30a61e118bd60 */ + * Stub hash: 01fc9901c45992450d60ceedbb82d3a6fb8500de */ ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_set_time_limit, 0, 1, _IS_BOOL, 0) ZEND_ARG_TYPE_INFO(0, seconds, IS_LONG, 0) diff --git a/ext/standard/tests/file/bug35740.phpt b/ext/standard/tests/file/bug35740.phpt index 29ab0248d8dcf..d06b53be06ed8 100644 --- a/ext/standard/tests/file/bug35740.phpt +++ b/ext/standard/tests/file/bug35740.phpt @@ -1,5 +1,9 @@ --TEST-- Bug #35740 (memory leak when including a directory) +--SKIPIF-- + --FILE-- --EXPECTF-- -Warning: include(%s): Failed to open stream: %s in %s on line %d +Notice: include(): Read of %s bytes failed with errno=21 Is a directory in %s on line %d Warning: include(): Failed opening '%s' for inclusion (include_path='%s') in %s on line %d Done diff --git a/ext/uri/tests/058.phpt b/ext/uri/tests/058.phpt new file mode 100644 index 0000000000000..efca0a0e8aaf6 --- /dev/null +++ b/ext/uri/tests/058.phpt @@ -0,0 +1,32 @@ +--TEST-- +Test that integer overflows in the port are rejected +--EXTENSIONS-- +uri +--FILE-- +getPort(), PHP_EOL; + echo "2147483647", PHP_EOL; +} else { + $uri = new \Uri\Rfc3986\Uri('https://example.com:2147483647'); + echo "9223372036854775807", PHP_EOL; + echo $uri->getPort(), PHP_EOL; +} + +try { + if (PHP_INT_SIZE == 8) { + new \Uri\Rfc3986\Uri('https://example.com:9223372036854775808'); + } else { + new \Uri\Rfc3986\Uri('https://example.com:2147483648'); + } +} catch (Throwable $e) { + echo $e::class, ": ", $e->getMessage(), PHP_EOL; +} + +?> +--EXPECT-- +9223372036854775807 +2147483647 +Uri\InvalidUriException: The port is out of range diff --git a/ext/uri/uri_parser_rfc3986.c b/ext/uri/uri_parser_rfc3986.c index cf7235b071b4c..f9581e6d93ed2 100644 --- a/ext/uri/uri_parser_rfc3986.c +++ b/ext/uri/uri_parser_rfc3986.c @@ -190,15 +190,22 @@ ZEND_ATTRIBUTE_NONNULL static zend_result php_uri_parser_rfc3986_host_read(const return SUCCESS; } -ZEND_ATTRIBUTE_NONNULL static size_t str_to_int(const char *str, size_t len) +ZEND_ATTRIBUTE_NONNULL static zend_long port_str_to_zend_long_checked(const char *str, size_t len) { - size_t result = 0; + if (len > MAX_LENGTH_OF_LONG) { + return -1; + } + + char buf[MAX_LENGTH_OF_LONG + 1]; + *(char*)zend_mempcpy(buf, str, len) = 0; + + zend_ulong result = ZEND_STRTOUL(buf, NULL, 10); - for (size_t i = 0; i < len; ++i) { - result = result * 10 + (str[i] - '0'); + if (result > ZEND_LONG_MAX) { + return -1; } - return result; + return (zend_long)result; } ZEND_ATTRIBUTE_NONNULL static zend_result php_uri_parser_rfc3986_port_read(const uri_internal_t *internal_uri, uri_component_read_mode_t read_mode, zval *retval) @@ -206,7 +213,7 @@ ZEND_ATTRIBUTE_NONNULL static zend_result php_uri_parser_rfc3986_port_read(const const UriUriA *uriparser_uri = get_uri_for_reading(internal_uri->uri, read_mode); if (has_text_range(&uriparser_uri->portText)) { - ZVAL_LONG(retval, str_to_int(uriparser_uri->portText.first, get_text_range_length(&uriparser_uri->portText))); + ZVAL_LONG(retval, port_str_to_zend_long_checked(uriparser_uri->portText.first, get_text_range_length(&uriparser_uri->portText))); } else { ZVAL_NULL(retval); } @@ -319,6 +326,17 @@ php_uri_parser_rfc3986_uris *php_uri_parser_rfc3986_parse_ex(const char *uri_str /* Make the resulting URI independent of the 'uri_str'. */ uriMakeOwnerMmA(&uri, mm); + if ( + has_text_range(&uri.portText) + && port_str_to_zend_long_checked(uri.portText.first, get_text_range_length(&uri.portText)) == -1 + ) { + if (!silent) { + zend_throw_exception(uri_invalid_uri_exception_ce, "The port is out of range", 0); + } + + goto fail; + } + php_uri_parser_rfc3986_uris *uriparser_uris = uriparser_create_uris(); uriparser_uris->uri = uri; diff --git a/main/streams/plain_wrapper.c b/main/streams/plain_wrapper.c index a8fd70b8d6ac2..a0471a8b9218c 100644 --- a/main/streams/plain_wrapper.c +++ b/main/streams/plain_wrapper.c @@ -135,7 +135,6 @@ typedef struct { unsigned is_pipe:1; /* stream is an actual pipe, currently Windows only*/ unsigned cached_fstat:1; /* sb is valid */ unsigned is_pipe_blocking:1; /* allow blocking read() on pipes, currently Windows only */ - unsigned no_forced_fstat:1; /* Use fstat cache even if forced */ unsigned is_seekable:1; /* don't try and seek, if not set */ unsigned _reserved:26; @@ -161,7 +160,7 @@ typedef struct { static int do_fstat(php_stdio_stream_data *d, int force) { - if (!d->cached_fstat || (force && !d->no_forced_fstat)) { + if (!d->cached_fstat || force) { int fd; int r; @@ -1188,30 +1187,7 @@ PHPAPI php_stream *_php_stream_fopen(const char *filename, const char *mode, zen efree(persistent_id); } - /* WIN32 always set ISREG flag */ #ifndef PHP_WIN32 - /* sanity checks for include/require. - * We check these after opening the stream, so that we save - * on fstat() syscalls */ - if (options & STREAM_OPEN_FOR_INCLUDE) { - php_stdio_stream_data *self = (php_stdio_stream_data*)ret->abstract; - int r; - - r = do_fstat(self, 0); - if ((r == 0 && !S_ISREG(self->sb.st_mode))) { - if (opened_path) { - zend_string_release_ex(*opened_path, 0); - *opened_path = NULL; - } - php_stream_close(ret); - return NULL; - } - - /* Make sure the fstat result is reused when we later try to get the - * file size. */ - self->no_forced_fstat = 1; - } - if (options & STREAM_USE_BLOCKING_PIPE) { php_stdio_stream_data *self = (php_stdio_stream_data*)ret->abstract; self->is_pipe_blocking = 1;