From 3064540134148ca469c4757ec658d690d8cd90b3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=80=9CLamentXU123=E2=80=9D?= <108666168+LamentXU123@users.noreply.github.com> Date: Mon, 25 May 2026 16:59:28 +0800 Subject: [PATCH 1/3] ext/phar: improve .phar madic directory preservation logic in phar::addEmptyDir() Now, the .phar directory is a magic dir for phar files, and in phar::addEmptyDir(), users couldn't create a dir naming .phar The implementation is: ```c if (zend_string_starts_with_literal(dir_name, ".phar")) { zend_throw_exception_ex(spl_ce_BadMethodCallException, 0, "Cannot create a directory in magic \".phar\" directory"); RETURN_THROWS(); ``` This has two bugs. Firstly, people can use /.phar to create the .phar dir. The leading / will be ignored. (no need to concern about ../ though, it will be ignored.) ```php addEmptyDir('/.phar'); var_dump(is_dir('phar://' . __DIR__ . '/test.phar/.phar')); ``` Will return true with the .phar dir created, while if the dir is .phar it will raise an error. Secondly, it only matches the prefix. That means, /.pharxxx will not be allowed to create, which is not a magic dir. ```php addEmptyDir('.pharx'); ``` This will raise an error. ``` PHP Fatal error: Uncaught BadMethodCallException: Cannot create a directory in magic ".phar" directory in C:\Users\admin\Desktop\bench.php:3 ``` This PR fix both by 1. adding a trailing check of the path to make .pharx valid 2. adding a check to /.phar Closes GH-22146. --- NEWS | 5 +++++ ext/phar/phar_object.c | 13 ++++++++++--- ext/phar/tests/mkdir.phpt | 9 +++++++++ 3 files changed, 24 insertions(+), 3 deletions(-) diff --git a/NEWS b/NEWS index dbf83143dce3..49b61d236967 100644 --- a/NEWS +++ b/NEWS @@ -17,6 +17,11 @@ PHP NEWS IntlCalendar::equals(), ::before(), ::after(), and ::isEquivalentTo(). (Weilin Du) +- Phar: + . Fixed a bypass of the magic ".phar" directory protection in + Phar::addEmptyDir() for paths starting with "/.phar", while allowing + non-magic directory names that merely share the ".phar" prefix. (Weilin Du) + - Zlib: . Fixed memory leak if deflate initialization fails and there is a dict. (ndossche) diff --git a/ext/phar/phar_object.c b/ext/phar/phar_object.c index bb96c783e931..a2d70a1a000f 100644 --- a/ext/phar/phar_object.c +++ b/ext/phar/phar_object.c @@ -3863,9 +3863,16 @@ PHP_METHOD(Phar, addEmptyDir) PHAR_ARCHIVE_OBJECT(); - if (zend_string_starts_with_literal(dir_name, ".phar")) { - zend_throw_exception_ex(spl_ce_BadMethodCallException, 0, "Cannot create a directory in magic \".phar\" directory"); - RETURN_THROWS(); + if ( + zend_string_starts_with_literal(dir_name, ".phar") + || zend_string_starts_with_literal(dir_name, "/.phar") + ) { + size_t prefix_len = (ZSTR_VAL(dir_name)[0] == '/') + sizeof(".phar")-1; + char next_char = ZSTR_VAL(dir_name)[prefix_len]; + if (next_char == '/' || next_char == '\\' || next_char == '\0') { + zend_throw_exception_ex(spl_ce_BadMethodCallException, 0, "Cannot create a directory in magic \".phar\" directory"); + RETURN_THROWS(); + } } phar_mkdir(&phar_obj->archive, dir_name); diff --git a/ext/phar/tests/mkdir.phpt b/ext/phar/tests/mkdir.phpt index 1ffdc7fe252d..2c1586b0de5c 100644 --- a/ext/phar/tests/mkdir.phpt +++ b/ext/phar/tests/mkdir.phpt @@ -24,6 +24,13 @@ $a->addEmptyDir('.phar'); } catch (Exception $e) { echo $e->getMessage(),"\n"; } +try { +$a->addEmptyDir('/.phar'); +} catch (Exception $e) { +echo $e->getMessage(),"\n"; +} +$a->addEmptyDir('/.pharx'); +var_dump(is_dir($pname . '/.pharx')); ?> --CLEAN-- Date: Wed, 3 Jun 2026 14:11:26 +0100 Subject: [PATCH 2/3] win32/signal.c: convert ctrl_handler to FCC (#22210) --- .../sapi_windows_set_ctrl_trampoline.phpt | 31 ++++++++++++++ win32/signal.c | 41 +++++++++++-------- 2 files changed, 55 insertions(+), 17 deletions(-) create mode 100644 sapi/cli/tests/sapi_windows_set_ctrl_trampoline.phpt diff --git a/sapi/cli/tests/sapi_windows_set_ctrl_trampoline.phpt b/sapi/cli/tests/sapi_windows_set_ctrl_trampoline.phpt new file mode 100644 index 000000000000..4699387b2c39 --- /dev/null +++ b/sapi/cli/tests/sapi_windows_set_ctrl_trampoline.phpt @@ -0,0 +1,31 @@ +--TEST-- +sapi_windows_set_ctrl_handler() trampoline test +--SKIPIF-- + +--FILE-- + +--EXPECT-- +Done diff --git a/win32/signal.c b/win32/signal.c index c04fe860437d..abce3edb2fd1 100644 --- a/win32/signal.c +++ b/win32/signal.c @@ -18,7 +18,7 @@ #include "win32/console.h" /* true globals; only used from main thread and from kernel callback */ -static zval ctrl_handler; +static zend_fcall_info_cache ctrl_handler; static DWORD ctrl_evt = (DWORD)-1; static zend_atomic_bool *vm_interrupt_flag = NULL; @@ -26,14 +26,12 @@ static void (*orig_interrupt_function)(zend_execute_data *execute_data); static void php_win32_signal_ctrl_interrupt_function(zend_execute_data *execute_data) {/*{{{*/ - if (IS_UNDEF != Z_TYPE(ctrl_handler)) { - zval retval, params[1]; + if (ZEND_FCC_INITIALIZED(ctrl_handler)) { + zval params[1]; ZVAL_LONG(¶ms[0], ctrl_evt); - /* If the function returns, */ - call_user_function(NULL, NULL, &ctrl_handler, &retval, 1, params); - zval_ptr_dtor(&retval); + zend_call_known_fcc(&ctrl_handler, NULL, 1, params, NULL); } if (orig_interrupt_function) { @@ -51,7 +49,7 @@ PHP_WINUTIL_API void php_win32_signal_ctrl_handler_init(void) orig_interrupt_function = zend_interrupt_function; zend_interrupt_function = php_win32_signal_ctrl_interrupt_function; vm_interrupt_flag = &EG(vm_interrupt); - ZVAL_UNDEF(&ctrl_handler); + ctrl_handler = empty_fcall_info_cache; REGISTER_MAIN_LONG_CONSTANT("PHP_WINDOWS_EVENT_CTRL_C", CTRL_C_EVENT, CONST_PERSISTENT); REGISTER_MAIN_LONG_CONSTANT("PHP_WINDOWS_EVENT_CTRL_BREAK", CTRL_BREAK_EVENT, CONST_PERSISTENT); @@ -82,9 +80,8 @@ PHP_WINUTIL_API void php_win32_signal_ctrl_handler_request_shutdown(void) /* The ctrl_handler must be cleared between requests, otherwise we can crash * due to accessing a previous request's memory. */ - if (!Z_ISUNDEF(ctrl_handler)) { - zval_ptr_dtor(&ctrl_handler); - ZVAL_UNDEF(&ctrl_handler); + if (ZEND_FCC_INITIALIZED(ctrl_handler)) { + zend_fcc_dtor(&ctrl_handler); } } @@ -110,25 +107,32 @@ PHP_FUNCTION(sapi_windows_set_ctrl_handler) /* callable argument corresponds to the CTRL handler */ - if (zend_parse_parameters(ZEND_NUM_ARGS(), "f!|b", &fci, &fcc, &add) == FAILURE) { + if (zend_parse_parameters(ZEND_NUM_ARGS(), "F!|b", &fci, &fcc, &add) == FAILURE) { RETURN_THROWS(); } #ifdef ZTS if (!tsrm_is_main_thread()) { + if (ZEND_FCC_INITIALIZED(fcc)) { + zend_release_fcall_info_cache(&fcc); + } zend_throw_error(NULL, "CTRL events can only be received on the main thread"); RETURN_THROWS(); } #endif if (!php_win32_console_is_cli_sapi()) { + if (ZEND_FCC_INITIALIZED(fcc)) { + zend_release_fcall_info_cache(&fcc); + } zend_throw_error(NULL, "CTRL events trapping is only supported on console"); RETURN_THROWS(); } - if (!ZEND_FCI_INITIALIZED(fci)) { - zval_ptr_dtor(&ctrl_handler); - ZVAL_UNDEF(&ctrl_handler); + if (!ZEND_FCC_INITIALIZED(fcc)) { + if (ZEND_FCC_INITIALIZED(ctrl_handler)) { + zend_fcc_dtor(&ctrl_handler); + } RETURN_BOOL(SetConsoleCtrlHandler(NULL, add)); } @@ -136,11 +140,14 @@ PHP_FUNCTION(sapi_windows_set_ctrl_handler) zend_string *func_name = zend_get_callable_name(&fci.function_name); php_error_docref(NULL, E_WARNING, "Unable to attach %s as a CTRL handler", ZSTR_VAL(func_name)); zend_string_release_ex(func_name, 0); + zend_release_fcall_info_cache(&fcc); RETURN_FALSE; } - zval_ptr_dtor(&ctrl_handler); - ZVAL_COPY(&ctrl_handler, &fci.function_name); + if (ZEND_FCC_INITIALIZED(ctrl_handler)) { + zend_fcc_dtor(&ctrl_handler); + } + zend_fcc_dup(&ctrl_handler, &fcc); RETURN_TRUE; }/*}}}*/ @@ -163,7 +170,7 @@ PHP_FUNCTION(sapi_windows_generate_ctrl_event) ret = (GenerateConsoleCtrlEvent(evt, pid) != 0); - if (IS_UNDEF != Z_TYPE(ctrl_handler)) { + if (ZEND_FCC_INITIALIZED(ctrl_handler)) { ret = ret && SetConsoleCtrlHandler(php_win32_signal_system_ctrl_handler, TRUE); } From 4e4306af5aa328937f8f05c6012de58b7dfb9483 Mon Sep 17 00:00:00 2001 From: Gina Peter Banyard Date: Wed, 3 Jun 2026 14:16:05 +0100 Subject: [PATCH 3/3] zend_ini.c: fix zend_ini_bool_literal() with unknown INI setting (#22209) Closes GH-22208 --- Zend/zend_ini.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/Zend/zend_ini.c b/Zend/zend_ini.c index 99df90486632..093683526d31 100644 --- a/Zend/zend_ini.c +++ b/Zend/zend_ini.c @@ -561,6 +561,11 @@ ZEND_API zend_string *zend_ini_get_value(zend_string *name) /* {{{ */ ZEND_API bool zend_ini_parse_bool(const zend_string *str) { + /* May happen if an unknown INI setting is queried via zend_ini_bool_literal(), + * as zend_ini_str() would return NULL */ + if (UNEXPECTED(str == NULL)) { + return false; + } if (zend_string_equals_literal_ci(str, "true") || zend_string_equals_literal_ci(str, "yes") || zend_string_equals_literal_ci(str, "on")