From e3c784f2bfb6029b49d27783b2efc87ee6923f79 Mon Sep 17 00:00:00 2001 From: Jakub Zelenka Date: Thu, 15 Feb 2024 23:53:49 +0000 Subject: [PATCH 01/11] Add proc_open escaping for cmd file execution --- ext/standard/proc_open.c | 86 +++++++++++++++++-- .../ghsa-pc52-254m-w9w7_1.phpt | 29 +++++++ .../ghsa-pc52-254m-w9w7_2.phpt | 29 +++++++ .../ghsa-pc52-254m-w9w7_3.phpt | 29 +++++++ 4 files changed, 168 insertions(+), 5 deletions(-) create mode 100644 ext/standard/tests/general_functions/ghsa-pc52-254m-w9w7_1.phpt create mode 100644 ext/standard/tests/general_functions/ghsa-pc52-254m-w9w7_2.phpt create mode 100644 ext/standard/tests/general_functions/ghsa-pc52-254m-w9w7_3.phpt diff --git a/ext/standard/proc_open.c b/ext/standard/proc_open.c index 3f8eaafd6d281..8aae54072650e 100644 --- a/ext/standard/proc_open.c +++ b/ext/standard/proc_open.c @@ -492,11 +492,32 @@ static void append_backslashes(smart_str *str, size_t num_bs) } } -/* See https://docs.microsoft.com/en-us/cpp/cpp/parsing-cpp-command-line-arguments */ -static void append_win_escaped_arg(smart_str *str, zend_string *arg) +const char *special_chars = "()!^\"<>&|%"; + +static bool is_special_character_present(const zend_string *arg) +{ + for (size_t i = 0; i < ZSTR_LEN(arg); ++i) { + if (strchr(special_chars, ZSTR_VAL(arg)[i]) != NULL) { + return true; + } + } + return false; +} + +/* See https://docs.microsoft.com/en-us/cpp/cpp/parsing-cpp-command-line-arguments and + * https://learn.microsoft.com/en-us/archive/blogs/twistylittlepassagesallalike/everyone-quotes-command-line-arguments-the-wrong-way */ +static void append_win_escaped_arg(smart_str *str, zend_string *arg, bool is_cmd_argument) { size_t num_bs = 0; + bool has_special_character = false; + if (is_cmd_argument) { + has_special_character = is_special_character_present(arg); + if (has_special_character) { + /* Escape double quote with ^ if executed by cmd.exe. */ + smart_str_appendc(str, '^'); + } + } smart_str_appendc(str, '"'); for (size_t i = 0; i < ZSTR_LEN(arg); ++i) { char c = ZSTR_VAL(arg)[i]; @@ -510,18 +531,71 @@ static void append_win_escaped_arg(smart_str *str, zend_string *arg) num_bs = num_bs * 2 + 1; } append_backslashes(str, num_bs); + if (has_special_character && strchr(special_chars, c) != NULL) { + /* Escape special chars with ^ if executed by cmd.exe. */ + smart_str_appendc(str, '^'); + } smart_str_appendc(str, c); num_bs = 0; } append_backslashes(str, num_bs * 2); + if (has_special_character) { + /* Escape double quote with ^ if executed by cmd.exe. */ + smart_str_appendc(str, '^'); + } smart_str_appendc(str, '"'); } +static inline int stricmp_end(const char* suffix, const char* str) { + size_t suffix_len = strlen(suffix); + size_t str_len = strlen(str); + + if (suffix_len > str_len) { + return -1; /* Suffix is longer than string, cannot match. */ + } + + /* Compare the end of the string with the suffix, ignoring case. */ + return _stricmp(str + (str_len - suffix_len), suffix); +} + +static bool is_executed_by_cmd(const char *prog_name) +{ + /* If program name is cmd.exe, then return true. */ + if (_stricmp("cmd.exe", prog_name) == 0 || _stricmp("cmd", prog_name) == 0 + || stricmp_end("\\cmd.exe", prog_name) == 0 || stricmp_end("\\cmd", prog_name) == 0) { + return true; + } + + /* Find the last occurrence of the directory separator (backslash or forward slash). */ + char *last_separator = strrchr(prog_name, '\\'); + char *last_separator_fwd = strrchr(prog_name, '/'); + if (last_separator_fwd && (!last_separator || last_separator < last_separator_fwd)) { + last_separator = last_separator_fwd; + } + + /* Find the last dot in the filename after the last directory separator. */ + char *extension = NULL; + if (last_separator != NULL) { + extension = strrchr(last_separator, '.'); + } else { + extension = strrchr(prog_name, '.'); + } + + if (extension == NULL || extension == prog_name) { + /* No file extension found, it is not batch file. */ + return false; + } + + /* Check if the file extension is ".bat" or ".cmd" which is always executed by cmd.exe. */ + return _stricmp(extension, ".bat") == 0 || _stricmp(extension, ".cmd") == 0; +} + static zend_string *create_win_command_from_args(HashTable *args) { smart_str str = {0}; zval *arg_zv; - bool is_prog_name = 1; + bool is_prog_name = true; + bool is_cmd_execution = false; int elem_num = 0; ZEND_HASH_FOREACH_VAL(args, arg_zv) { @@ -531,11 +605,13 @@ static zend_string *create_win_command_from_args(HashTable *args) return NULL; } - if (!is_prog_name) { + if (is_prog_name) { + is_cmd_execution = is_executed_by_cmd(ZSTR_VAL(arg_str)); + } else { smart_str_appendc(&str, ' '); } - append_win_escaped_arg(&str, arg_str); + append_win_escaped_arg(&str, arg_str, !is_prog_name && is_cmd_execution); is_prog_name = 0; zend_string_release(arg_str); diff --git a/ext/standard/tests/general_functions/ghsa-pc52-254m-w9w7_1.phpt b/ext/standard/tests/general_functions/ghsa-pc52-254m-w9w7_1.phpt new file mode 100644 index 0000000000000..8d0939cdf1bc7 --- /dev/null +++ b/ext/standard/tests/general_functions/ghsa-pc52-254m-w9w7_1.phpt @@ -0,0 +1,29 @@ +--TEST-- +GHSA-54hq-v5wp-fqgv - proc_open does not correctly escape args for bat files +--SKIPIF-- + +--FILE-- + +--EXPECT-- +"¬epad.exe +--CLEAN-- + diff --git a/ext/standard/tests/general_functions/ghsa-pc52-254m-w9w7_2.phpt b/ext/standard/tests/general_functions/ghsa-pc52-254m-w9w7_2.phpt new file mode 100644 index 0000000000000..a1e39d7ef9ba0 --- /dev/null +++ b/ext/standard/tests/general_functions/ghsa-pc52-254m-w9w7_2.phpt @@ -0,0 +1,29 @@ +--TEST-- +GHSA-54hq-v5wp-fqgv - proc_open does not correctly escape args for cmd files +--SKIPIF-- + +--FILE-- +^()!.exe"], $descriptorspec, $pipes); +proc_close($proc); + +?> +--EXPECT-- +"¬epad<>^()!.exe +--CLEAN-- + diff --git a/ext/standard/tests/general_functions/ghsa-pc52-254m-w9w7_3.phpt b/ext/standard/tests/general_functions/ghsa-pc52-254m-w9w7_3.phpt new file mode 100644 index 0000000000000..69f12d7b358d2 --- /dev/null +++ b/ext/standard/tests/general_functions/ghsa-pc52-254m-w9w7_3.phpt @@ -0,0 +1,29 @@ +--TEST-- +GHSA-54hq-v5wp-fqgv - proc_open does not correctly escape args for cmd executing batch files +--SKIPIF-- + +--FILE-- + +--EXPECT-- +"¬epad.exe +--CLEAN-- + From 093c08af25fb323efa0c8e6154aa9fdeae3d3b53 Mon Sep 17 00:00:00 2001 From: Niels Dossche <7771979+nielsdos@users.noreply.github.com> Date: Sun, 17 Mar 2024 21:04:47 +0100 Subject: [PATCH 02/11] Fix GHSA-wpj3-hf5j-x4v4: __Host-/__Secure- cookie bypass due to partial CVE-2022-31629 fix The check happened too early as later code paths may perform more mangling rules. Move the check downwards right before adding the actual variable. --- ext/standard/tests/ghsa-wpj3-hf5j-x4v4.phpt | 63 +++++++++++++++++++++ main/php_variables.c | 41 +++++++++----- 2 files changed, 90 insertions(+), 14 deletions(-) create mode 100644 ext/standard/tests/ghsa-wpj3-hf5j-x4v4.phpt diff --git a/ext/standard/tests/ghsa-wpj3-hf5j-x4v4.phpt b/ext/standard/tests/ghsa-wpj3-hf5j-x4v4.phpt new file mode 100644 index 0000000000000..77fcb68089488 --- /dev/null +++ b/ext/standard/tests/ghsa-wpj3-hf5j-x4v4.phpt @@ -0,0 +1,63 @@ +--TEST-- +ghsa-wpj3-hf5j-x4v4 (__Host-/__Secure- cookie bypass due to partial CVE-2022-31629 fix) +--COOKIE-- +..Host-test=ignore_1; +._Host-test=ignore_2; +.[Host-test=ignore_3; +_.Host-test=ignore_4; +__Host-test=ignore_5; +_[Host-test=ignore_6; +[.Host-test=ignore_7; +[_Host-test=ignore_8; +[[Host-test=ignore_9; +..Host-test[]=ignore_10; +._Host-test[]=ignore_11; +.[Host-test[]=ignore_12; +_.Host-test[]=ignore_13; +__Host-test[]=legitimate_14; +_[Host-test[]=legitimate_15; +[.Host-test[]=ignore_16; +[_Host-test[]=ignore_17; +[[Host-test[]=ignore_18; +..Secure-test=ignore_1; +._Secure-test=ignore_2; +.[Secure-test=ignore_3; +_.Secure-test=ignore_4; +__Secure-test=ignore_5; +_[Secure-test=ignore_6; +[.Secure-test=ignore_7; +[_Secure-test=ignore_8; +[[Secure-test=ignore_9; +..Secure-test[]=ignore_10; +._Secure-test[]=ignore_11; +.[Secure-test[]=ignore_12; +_.Secure-test[]=ignore_13; +__Secure-test[]=legitimate_14; +_[Secure-test[]=legitimate_15; +[.Secure-test[]=ignore_16; +[_Secure-test[]=ignore_17; +[[Secure-test[]=ignore_18; +--FILE-- + +--EXPECT-- +array(3) { + ["__Host-test"]=> + array(1) { + [0]=> + string(13) "legitimate_14" + } + ["_"]=> + array(2) { + ["Host-test["]=> + string(13) "legitimate_15" + ["Secure-test["]=> + string(13) "legitimate_15" + } + ["__Secure-test"]=> + array(1) { + [0]=> + string(13) "legitimate_14" + } +} diff --git a/main/php_variables.c b/main/php_variables.c index 17e4a1e5d2cf1..da7266416a54d 100644 --- a/main/php_variables.c +++ b/main/php_variables.c @@ -54,6 +54,21 @@ static zend_always_inline void php_register_variable_quick(const char *name, siz zend_string_release_ex(key, 0); } +/* Discard variable if mangling made it start with __Host-, where pre-mangling it did not start with __Host- + * Discard variable if mangling made it start with __Secure-, where pre-mangling it did not start with __Secure- */ +static bool php_is_forbidden_variable_name(const char *mangled_name, size_t mangled_name_len, const char *pre_mangled_name) +{ + if (mangled_name_len >= sizeof("__Host-")-1 && strncmp(mangled_name, "__Host-", sizeof("__Host-")-1) == 0 && strncmp(pre_mangled_name, "__Host-", sizeof("__Host-")-1) != 0) { + return true; + } + + if (mangled_name_len >= sizeof("__Secure-")-1 && strncmp(mangled_name, "__Secure-", sizeof("__Secure-")-1) == 0 && strncmp(pre_mangled_name, "__Secure-", sizeof("__Secure-")-1) != 0) { + return true; + } + + return false; +} + PHPAPI void php_register_variable_ex(const char *var_name, zval *val, zval *track_vars_array) { char *p = NULL; @@ -104,20 +119,6 @@ PHPAPI void php_register_variable_ex(const char *var_name, zval *val, zval *trac } var_len = p - var; - /* Discard variable if mangling made it start with __Host-, where pre-mangling it did not start with __Host- */ - if (strncmp(var, "__Host-", sizeof("__Host-")-1) == 0 && strncmp(var_name, "__Host-", sizeof("__Host-")-1) != 0) { - zval_ptr_dtor_nogc(val); - free_alloca(var_orig, use_heap); - return; - } - - /* Discard variable if mangling made it start with __Secure-, where pre-mangling it did not start with __Secure- */ - if (strncmp(var, "__Secure-", sizeof("__Secure-")-1) == 0 && strncmp(var_name, "__Secure-", sizeof("__Secure-")-1) != 0) { - zval_ptr_dtor_nogc(val); - free_alloca(var_orig, use_heap); - return; - } - if (var_len==0) { /* empty variable name, or variable name with a space in it */ zval_ptr_dtor_nogc(val); free_alloca(var_orig, use_heap); @@ -221,6 +222,12 @@ PHPAPI void php_register_variable_ex(const char *var_name, zval *val, zval *trac return; } } else { + if (php_is_forbidden_variable_name(index, index_len, var_name)) { + zval_ptr_dtor_nogc(val); + free_alloca(var_orig, use_heap); + return; + } + gpc_element_p = zend_symtable_str_find(symtable1, index, index_len); if (!gpc_element_p) { zval tmp; @@ -258,6 +265,12 @@ PHPAPI void php_register_variable_ex(const char *var_name, zval *val, zval *trac zval_ptr_dtor_nogc(val); } } else { + if (php_is_forbidden_variable_name(index, index_len, var_name)) { + zval_ptr_dtor_nogc(val); + free_alloca(var_orig, use_heap); + return; + } + zend_ulong idx; /* From 0ba5229a3f7572846e91c8f5382e87785f543826 Mon Sep 17 00:00:00 2001 From: Jakub Zelenka Date: Fri, 29 Mar 2024 15:27:59 +0000 Subject: [PATCH 03/11] Fix bug GHSA-q6x7-frmf-grcw: password_verify can erroneously return true Disallow null character in bcrypt password --- ext/standard/password.c | 5 +++++ ext/standard/tests/password/password_bcrypt_errors.phpt | 7 +++++++ 2 files changed, 12 insertions(+) diff --git a/ext/standard/password.c b/ext/standard/password.c index 651cffc9fe656..fbe58da603053 100644 --- a/ext/standard/password.c +++ b/ext/standard/password.c @@ -184,6 +184,11 @@ static zend_string* php_password_bcrypt_hash(const zend_string *password, zend_a zval *zcost; zend_long cost = PHP_PASSWORD_BCRYPT_COST; + if (memchr(ZSTR_VAL(password), '\0', ZSTR_LEN(password))) { + zend_value_error("Bcrypt password must not contain null character"); + return NULL; + } + if (options && (zcost = zend_hash_str_find(options, "cost", sizeof("cost")-1)) != NULL) { cost = zval_get_long(zcost); } diff --git a/ext/standard/tests/password/password_bcrypt_errors.phpt b/ext/standard/tests/password/password_bcrypt_errors.phpt index 10c3483f5a80d..5d823cba0217d 100644 --- a/ext/standard/tests/password/password_bcrypt_errors.phpt +++ b/ext/standard/tests/password/password_bcrypt_errors.phpt @@ -14,7 +14,14 @@ try { } catch (ValueError $exception) { echo $exception->getMessage() . "\n"; } + +try { + var_dump(password_hash("null\0password", PASSWORD_BCRYPT)); +} catch (ValueError $e) { + echo $e->getMessage(), "\n"; +} ?> --EXPECT-- Invalid bcrypt cost parameter specified: 3 Invalid bcrypt cost parameter specified: 32 +Bcrypt password must not contain null character From de4f7f932166e66ff0950d97ae3bda5e355003d7 Mon Sep 17 00:00:00 2001 From: Ben Ramsey Date: Tue, 9 Apr 2024 23:41:29 -0500 Subject: [PATCH 04/11] Update NEWS --- NEWS | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/NEWS b/NEWS index 8373c66329af5..0539611812d92 100644 --- a/NEWS +++ b/NEWS @@ -2,6 +2,13 @@ PHP NEWS ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||| ?? ??? ????, PHP 8.1.28 +- Standard: + . Fixed bug GHSA-pc52-254m-w9w7 (Command injection via array-ish $command + parameter of proc_open). (CVE-2024-1874) (Jakub Zelenka) + . Fixed bug GHSA-wpj3-hf5j-x4v4 (__Host-/__Secure- cookie bypass due to + partial CVE-2022-31629 fix). (CVE-2024-2756) (nielsdos) + . Fixed bug GHSA-h746-cjrr-wfmr (password_verify can erroneously return true, + opening ATO risk). (CVE-2024-3096) (Jakub Zelenka) 21 Dec 2023, PHP 8.1.27 From ca5fe4030cddfdb654b2752fa147997efa3ad714 Mon Sep 17 00:00:00 2001 From: Ben Ramsey Date: Wed, 10 Apr 2024 00:48:59 -0500 Subject: [PATCH 05/11] PHP-8.1 is now for PHP 8.1.29-dev --- NEWS | 6 +++++- Zend/zend.h | 2 +- configure.ac | 2 +- main/php_version.h | 6 +++--- 4 files changed, 10 insertions(+), 6 deletions(-) diff --git a/NEWS b/NEWS index 0539611812d92..f9b8fb276472d 100644 --- a/NEWS +++ b/NEWS @@ -1,6 +1,10 @@ PHP NEWS ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||| -?? ??? ????, PHP 8.1.28 +?? ??? ????, PHP 8.1.29 + + + +11 Apr 2024, PHP 8.1.28 - Standard: . Fixed bug GHSA-pc52-254m-w9w7 (Command injection via array-ish $command diff --git a/Zend/zend.h b/Zend/zend.h index 2bba956fe4744..9a414960e2a87 100644 --- a/Zend/zend.h +++ b/Zend/zend.h @@ -20,7 +20,7 @@ #ifndef ZEND_H #define ZEND_H -#define ZEND_VERSION "4.1.28-dev" +#define ZEND_VERSION "4.1.29-dev" #define ZEND_ENGINE_3 diff --git a/configure.ac b/configure.ac index 90e1f8e8a6588..83d50445be205 100644 --- a/configure.ac +++ b/configure.ac @@ -17,7 +17,7 @@ dnl Basic autoconf initialization, generation of config.nice. dnl ---------------------------------------------------------------------------- AC_PREREQ([2.68]) -AC_INIT([PHP],[8.1.28-dev],[https://github.com/php/php-src/issues],[php],[https://www.php.net]) +AC_INIT([PHP],[8.1.29-dev],[https://github.com/php/php-src/issues],[php],[https://www.php.net]) AC_CONFIG_SRCDIR([main/php_version.h]) AC_CONFIG_AUX_DIR([build]) AC_PRESERVE_HELP_ORDER diff --git a/main/php_version.h b/main/php_version.h index 59842b5307cb7..a7b35a63a9285 100644 --- a/main/php_version.h +++ b/main/php_version.h @@ -2,7 +2,7 @@ /* edit configure.ac to change version number */ #define PHP_MAJOR_VERSION 8 #define PHP_MINOR_VERSION 1 -#define PHP_RELEASE_VERSION 28 +#define PHP_RELEASE_VERSION 29 #define PHP_EXTRA_VERSION "-dev" -#define PHP_VERSION "8.1.28-dev" -#define PHP_VERSION_ID 80128 +#define PHP_VERSION "8.1.29-dev" +#define PHP_VERSION_ID 80129 From 469ad32581292693dd3338a486f40ab7cbec33f9 Mon Sep 17 00:00:00 2001 From: Ilija Tovilo Date: Sun, 21 Apr 2024 16:05:26 +0200 Subject: [PATCH 06/11] [skip ci] Backport 0e7ef95 and 4f0d4c0 --- .github/workflows/push.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/workflows/push.yml b/.github/workflows/push.yml index 52f70f809e9ce..ddb4ee0aaf172 100644 --- a/.github/workflows/push.yml +++ b/.github/workflows/push.yml @@ -41,6 +41,7 @@ env: CXX: ccache g++ jobs: LINUX_X64: + if: github.repository == 'php/php-src' || github.event_name == 'pull_request' strategy: fail-fast: false matrix: @@ -94,6 +95,7 @@ jobs: - name: Verify generated files are up to date uses: ./.github/actions/verify-generated-files MACOS_DEBUG_NTS: + if: github.repository == 'php/php-src' || github.event_name == 'pull_request' runs-on: macos-12 steps: - name: git checkout @@ -126,6 +128,7 @@ jobs: - name: Verify generated files are up to date uses: ./.github/actions/verify-generated-files WINDOWS: + if: github.repository == 'php/php-src' || github.event_name == 'pull_request' name: WINDOWS_X64_ZTS runs-on: windows-2019 env: From c8b36406c0deae34beda28d908984f708db70289 Mon Sep 17 00:00:00 2001 From: Niels Dossche <7771979+nielsdos@users.noreply.github.com> Date: Sat, 20 Apr 2024 17:09:35 +0200 Subject: [PATCH 07/11] Fix GHSA-9fcc-425m-g385: bypass CVE-2024-1874 The old code checked for suffixes but didn't take into account trailing whitespace. Furthermore, there is peculiar behaviour with trailing dots too. This all happens because of the special path-handling code inside CreateProcessW. By studying Wine's code, we can see that CreateProcessInternalW calls get_file_name [1] in our case because we haven't provided an application name. That code gets the first whitespace-delimited string into app_name excluding the quotes. It's then passed to create_process_params [2] where there is the path handling code that transforms the command line argument to an image path [3]. Inside Wine, the extension check if performed after these transformations [4]. By doing the same thing in PHP we match the behaviour and can properly match the extension even in the given edge cases. [1] https://github.com/wine-mirror/wine/blob/166895ae3ad3890ad946a309d0fd85e89ea3630e/dlls/kernelbase/process.c#L542-L543 [2] https://github.com/wine-mirror/wine/blob/166895ae3ad3890ad946a309d0fd85e89ea3630e/dlls/kernelbase/process.c#L565 [3] https://github.com/wine-mirror/wine/blob/166895ae3ad3890ad946a309d0fd85e89ea3630e/dlls/kernelbase/process.c#L150-L151 [4] https://github.com/wine-mirror/wine/blob/166895ae3ad3890ad946a309d0fd85e89ea3630e/dlls/kernelbase/process.c#L647-L654 --- ext/standard/proc_open.c | 59 +- .../ghsa-9fcc-425m-g385_001.phpt | 56 ++ .../ghsa-9fcc-425m-g385_002.phpt | 66 +++ .../ghsa-9fcc-425m-g385_003.phpt | 550 ++++++++++++++++++ 4 files changed, 697 insertions(+), 34 deletions(-) create mode 100644 ext/standard/tests/general_functions/ghsa-9fcc-425m-g385_001.phpt create mode 100644 ext/standard/tests/general_functions/ghsa-9fcc-425m-g385_002.phpt create mode 100644 ext/standard/tests/general_functions/ghsa-9fcc-425m-g385_003.phpt diff --git a/ext/standard/proc_open.c b/ext/standard/proc_open.c index 8aae54072650e..495af6cba35e5 100644 --- a/ext/standard/proc_open.c +++ b/ext/standard/proc_open.c @@ -546,48 +546,39 @@ static void append_win_escaped_arg(smart_str *str, zend_string *arg, bool is_cmd smart_str_appendc(str, '"'); } -static inline int stricmp_end(const char* suffix, const char* str) { - size_t suffix_len = strlen(suffix); - size_t str_len = strlen(str); +static bool is_executed_by_cmd(const char *prog_name, size_t prog_name_length) +{ + size_t out_len; + WCHAR long_name[MAX_PATH]; + WCHAR full_name[MAX_PATH]; + LPWSTR file_part = NULL; - if (suffix_len > str_len) { - return -1; /* Suffix is longer than string, cannot match. */ - } + wchar_t *prog_name_wide = php_win32_cp_conv_any_to_w(prog_name, prog_name_length, &out_len); - /* Compare the end of the string with the suffix, ignoring case. */ - return _stricmp(str + (str_len - suffix_len), suffix); -} + if (GetLongPathNameW(prog_name_wide, long_name, MAX_PATH) == 0) { + /* This can fail for example with ERROR_FILE_NOT_FOUND (short path resolution only works for existing files) + * in which case we'll pass the path verbatim to the FullPath transformation. */ + lstrcpynW(long_name, prog_name_wide, MAX_PATH); + } -static bool is_executed_by_cmd(const char *prog_name) -{ - /* If program name is cmd.exe, then return true. */ - if (_stricmp("cmd.exe", prog_name) == 0 || _stricmp("cmd", prog_name) == 0 - || stricmp_end("\\cmd.exe", prog_name) == 0 || stricmp_end("\\cmd", prog_name) == 0) { - return true; - } + free(prog_name_wide); + prog_name_wide = NULL; - /* Find the last occurrence of the directory separator (backslash or forward slash). */ - char *last_separator = strrchr(prog_name, '\\'); - char *last_separator_fwd = strrchr(prog_name, '/'); - if (last_separator_fwd && (!last_separator || last_separator < last_separator_fwd)) { - last_separator = last_separator_fwd; + if (GetFullPathNameW(long_name, MAX_PATH, full_name, &file_part) == 0 || file_part == NULL) { + return false; } - /* Find the last dot in the filename after the last directory separator. */ - char *extension = NULL; - if (last_separator != NULL) { - extension = strrchr(last_separator, '.'); + bool uses_cmd = false; + if (_wcsicmp(file_part, L"cmd.exe") == 0 || _wcsicmp(file_part, L"cmd") == 0) { + uses_cmd = true; } else { - extension = strrchr(prog_name, '.'); - } - - if (extension == NULL || extension == prog_name) { - /* No file extension found, it is not batch file. */ - return false; + const WCHAR *extension_dot = wcsrchr(file_part, L'.'); + if (extension_dot && (_wcsicmp(extension_dot, L".bat") == 0 || _wcsicmp(extension_dot, L".cmd") == 0)) { + uses_cmd = true; + } } - /* Check if the file extension is ".bat" or ".cmd" which is always executed by cmd.exe. */ - return _stricmp(extension, ".bat") == 0 || _stricmp(extension, ".cmd") == 0; + return uses_cmd; } static zend_string *create_win_command_from_args(HashTable *args) @@ -606,7 +597,7 @@ static zend_string *create_win_command_from_args(HashTable *args) } if (is_prog_name) { - is_cmd_execution = is_executed_by_cmd(ZSTR_VAL(arg_str)); + is_cmd_execution = is_executed_by_cmd(ZSTR_VAL(arg_str), ZSTR_LEN(arg_str)); } else { smart_str_appendc(&str, ' '); } diff --git a/ext/standard/tests/general_functions/ghsa-9fcc-425m-g385_001.phpt b/ext/standard/tests/general_functions/ghsa-9fcc-425m-g385_001.phpt new file mode 100644 index 0000000000000..2873210608497 --- /dev/null +++ b/ext/standard/tests/general_functions/ghsa-9fcc-425m-g385_001.phpt @@ -0,0 +1,56 @@ +--TEST-- +GHSA-9fcc-425m-g385 - bypass CVE-2024-1874 - batch file variation +--SKIPIF-- + +--FILE-- + +--EXPECTF-- +'"%sghsa-9fcc-425m-g385_001.bat."' is not recognized as an internal or external command, +operable program or batch file. +%sghsa-9fcc-425m-g385_001.bat +"¬epad.exe +%sghsa-9fcc-425m-g385_001.bat. +"¬epad.exe +%sghsa-9fcc-425m-g385_001.bat. ... +"¬epad.exe +%sghsa-9fcc-425m-g385_001.bat. ... . +"¬epad.exe +'"%sghsa-9fcc-425m-g385_001.bat. ... . ."' is not recognized as an internal or external command, +operable program or batch file. + +Warning: proc_open(): CreateProcess failed, error code: 2 in %s on line %d +--CLEAN-- + diff --git a/ext/standard/tests/general_functions/ghsa-9fcc-425m-g385_002.phpt b/ext/standard/tests/general_functions/ghsa-9fcc-425m-g385_002.phpt new file mode 100644 index 0000000000000..714836557af5c --- /dev/null +++ b/ext/standard/tests/general_functions/ghsa-9fcc-425m-g385_002.phpt @@ -0,0 +1,66 @@ +--TEST-- +GHSA-9fcc-425m-g385 - bypass CVE-2024-1874 - cmd.exe variation +--SKIPIF-- + +--FILE-- + +--EXPECTF-- +%sghsa-9fcc-425m-g385_002.bat +"¬epad.exe +%sghsa-9fcc-425m-g385_002.bat +"¬epad.exe +%sghsa-9fcc-425m-g385_002.bat +"¬epad.exe +%sghsa-9fcc-425m-g385_002.bat +"¬epad.exe + +Warning: proc_open(): CreateProcess failed, error code: 2 in %s on line %d +%sghsa-9fcc-425m-g385_002.bat +"¬epad.exe +%sghsa-9fcc-425m-g385_002.bat +"¬epad.exe + +Warning: proc_open(): CreateProcess failed, error code: 2 in %s on line %d + +Warning: proc_open(): CreateProcess failed, error code: 2 in %s on line %d + +Warning: proc_open(): CreateProcess failed, error code: 2 in %s on line %d +--CLEAN-- + diff --git a/ext/standard/tests/general_functions/ghsa-9fcc-425m-g385_003.phpt b/ext/standard/tests/general_functions/ghsa-9fcc-425m-g385_003.phpt new file mode 100644 index 0000000000000..a632965eb989a --- /dev/null +++ b/ext/standard/tests/general_functions/ghsa-9fcc-425m-g385_003.phpt @@ -0,0 +1,550 @@ +--TEST-- +GHSA-9fcc-425m-g385 - bypass CVE-2024-1874 - exhaustive suffix test +--SKIPIF-- + +--FILE-- + true)); + var_dump($proc); + proc_close($proc); + } catch (Error) {} +} + +?> +--EXPECTF-- +Testing 1 +bool(false) +Testing 2 +bool(false) +Testing 3 +bool(false) +Testing 4 +bool(false) +Testing 5 +bool(false) +Testing 6 +bool(false) +Testing 7 +bool(false) +Testing 8 +bool(false) +Testing 9 +bool(false) +Testing 10 +bool(false) +Testing 11 +bool(false) +Testing 12 +bool(false) +Testing 13 +bool(false) +Testing 14 +bool(false) +Testing 15 +bool(false) +Testing 16 +bool(false) +Testing 17 +bool(false) +Testing 18 +bool(false) +Testing 19 +bool(false) +Testing 20 +bool(false) +Testing 21 +bool(false) +Testing 22 +bool(false) +Testing 23 +bool(false) +Testing 24 +bool(false) +Testing 25 +bool(false) +Testing 26 +bool(false) +Testing 27 +bool(false) +Testing 28 +bool(false) +Testing 29 +bool(false) +Testing 30 +bool(false) +Testing 31 +bool(false) +Testing 32 +resource(%d) of type (process) +%s.bat +"¬epad.exe +Testing 33 +bool(false) +Testing 34 +bool(false) +Testing 35 +bool(false) +Testing 36 +bool(false) +Testing 37 +bool(false) +Testing 38 +bool(false) +Testing 39 +bool(false) +Testing 40 +bool(false) +Testing 41 +bool(false) +Testing 42 +bool(false) +Testing 43 +bool(false) +Testing 44 +bool(false) +Testing 45 +bool(false) +Testing 46 +resource(%d) of type (process) +'"%s.bat."' is not recognized as an internal or external command, +operable program or batch file. +Testing 47 +bool(false) +Testing 48 +bool(false) +Testing 49 +bool(false) +Testing 50 +bool(false) +Testing 51 +bool(false) +Testing 52 +bool(false) +Testing 53 +bool(false) +Testing 54 +bool(false) +Testing 55 +bool(false) +Testing 56 +bool(false) +Testing 57 +bool(false) +Testing 58 +bool(false) +Testing 59 +bool(false) +Testing 60 +bool(false) +Testing 61 +bool(false) +Testing 62 +bool(false) +Testing 63 +bool(false) +Testing 64 +bool(false) +Testing 65 +bool(false) +Testing 66 +bool(false) +Testing 67 +bool(false) +Testing 68 +bool(false) +Testing 69 +bool(false) +Testing 70 +bool(false) +Testing 71 +bool(false) +Testing 72 +bool(false) +Testing 73 +bool(false) +Testing 74 +bool(false) +Testing 75 +bool(false) +Testing 76 +bool(false) +Testing 77 +bool(false) +Testing 78 +bool(false) +Testing 79 +bool(false) +Testing 80 +bool(false) +Testing 81 +bool(false) +Testing 82 +bool(false) +Testing 83 +bool(false) +Testing 84 +bool(false) +Testing 85 +bool(false) +Testing 86 +bool(false) +Testing 87 +bool(false) +Testing 88 +bool(false) +Testing 89 +bool(false) +Testing 90 +bool(false) +Testing 91 +bool(false) +Testing 92 +bool(false) +Testing 93 +bool(false) +Testing 94 +bool(false) +Testing 95 +bool(false) +Testing 96 +bool(false) +Testing 97 +bool(false) +Testing 98 +bool(false) +Testing 99 +bool(false) +Testing 100 +bool(false) +Testing 101 +bool(false) +Testing 102 +bool(false) +Testing 103 +bool(false) +Testing 104 +bool(false) +Testing 105 +bool(false) +Testing 106 +bool(false) +Testing 107 +bool(false) +Testing 108 +bool(false) +Testing 109 +bool(false) +Testing 110 +bool(false) +Testing 111 +bool(false) +Testing 112 +bool(false) +Testing 113 +bool(false) +Testing 114 +bool(false) +Testing 115 +bool(false) +Testing 116 +bool(false) +Testing 117 +bool(false) +Testing 118 +bool(false) +Testing 119 +bool(false) +Testing 120 +bool(false) +Testing 121 +bool(false) +Testing 122 +bool(false) +Testing 123 +bool(false) +Testing 124 +bool(false) +Testing 125 +bool(false) +Testing 126 +bool(false) +Testing 127 +bool(false) +Testing 128 +bool(false) +Testing 129 +bool(false) +Testing 130 +bool(false) +Testing 131 +bool(false) +Testing 132 +bool(false) +Testing 133 +bool(false) +Testing 134 +bool(false) +Testing 135 +bool(false) +Testing 136 +bool(false) +Testing 137 +bool(false) +Testing 138 +bool(false) +Testing 139 +bool(false) +Testing 140 +bool(false) +Testing 141 +bool(false) +Testing 142 +bool(false) +Testing 143 +bool(false) +Testing 144 +bool(false) +Testing 145 +bool(false) +Testing 146 +bool(false) +Testing 147 +bool(false) +Testing 148 +bool(false) +Testing 149 +bool(false) +Testing 150 +bool(false) +Testing 151 +bool(false) +Testing 152 +bool(false) +Testing 153 +bool(false) +Testing 154 +bool(false) +Testing 155 +bool(false) +Testing 156 +bool(false) +Testing 157 +bool(false) +Testing 158 +bool(false) +Testing 159 +bool(false) +Testing 160 +bool(false) +Testing 161 +bool(false) +Testing 162 +bool(false) +Testing 163 +bool(false) +Testing 164 +bool(false) +Testing 165 +bool(false) +Testing 166 +bool(false) +Testing 167 +bool(false) +Testing 168 +bool(false) +Testing 169 +bool(false) +Testing 170 +bool(false) +Testing 171 +bool(false) +Testing 172 +bool(false) +Testing 173 +bool(false) +Testing 174 +bool(false) +Testing 175 +bool(false) +Testing 176 +bool(false) +Testing 177 +bool(false) +Testing 178 +bool(false) +Testing 179 +bool(false) +Testing 180 +bool(false) +Testing 181 +bool(false) +Testing 182 +bool(false) +Testing 183 +bool(false) +Testing 184 +bool(false) +Testing 185 +bool(false) +Testing 186 +bool(false) +Testing 187 +bool(false) +Testing 188 +bool(false) +Testing 189 +bool(false) +Testing 190 +bool(false) +Testing 191 +bool(false) +Testing 192 +bool(false) +Testing 193 +bool(false) +Testing 194 +bool(false) +Testing 195 +bool(false) +Testing 196 +bool(false) +Testing 197 +bool(false) +Testing 198 +bool(false) +Testing 199 +bool(false) +Testing 200 +bool(false) +Testing 201 +bool(false) +Testing 202 +bool(false) +Testing 203 +bool(false) +Testing 204 +bool(false) +Testing 205 +bool(false) +Testing 206 +bool(false) +Testing 207 +bool(false) +Testing 208 +bool(false) +Testing 209 +bool(false) +Testing 210 +bool(false) +Testing 211 +bool(false) +Testing 212 +bool(false) +Testing 213 +bool(false) +Testing 214 +bool(false) +Testing 215 +bool(false) +Testing 216 +bool(false) +Testing 217 +bool(false) +Testing 218 +bool(false) +Testing 219 +bool(false) +Testing 220 +bool(false) +Testing 221 +bool(false) +Testing 222 +bool(false) +Testing 223 +bool(false) +Testing 224 +bool(false) +Testing 225 +bool(false) +Testing 226 +bool(false) +Testing 227 +bool(false) +Testing 228 +bool(false) +Testing 229 +bool(false) +Testing 230 +bool(false) +Testing 231 +bool(false) +Testing 232 +bool(false) +Testing 233 +bool(false) +Testing 234 +bool(false) +Testing 235 +bool(false) +Testing 236 +bool(false) +Testing 237 +bool(false) +Testing 238 +bool(false) +Testing 239 +bool(false) +Testing 240 +bool(false) +Testing 241 +bool(false) +Testing 242 +bool(false) +Testing 243 +bool(false) +Testing 244 +bool(false) +Testing 245 +bool(false) +Testing 246 +bool(false) +Testing 247 +bool(false) +Testing 248 +bool(false) +Testing 249 +bool(false) +Testing 250 +bool(false) +Testing 251 +bool(false) +Testing 252 +bool(false) +Testing 253 +bool(false) +Testing 254 +bool(false) +Testing 255 +bool(false) +--CLEAN-- + From 4dd9a36c165974c84c4217aa41849b70a9fc19c9 Mon Sep 17 00:00:00 2001 From: Niels Dossche <7771979+nielsdos@users.noreply.github.com> Date: Fri, 17 May 2024 21:51:30 +0200 Subject: [PATCH 08/11] Fix GHSA-3qgc-jrrr-25jv The original code is error-prone due to the "best fit mapping" that happens with the argument parsing but not with the query string. When we get a non-ASCII character, try to remap it and see if it becomes a hyphen. An alternative approach is to create a custom main `wmain` receiving wide-character variations that does the ANSI transformation with the best-fit mapping, but that's more error-prone and could cause unexpected breakage. Another alternative was just don't doing this check altogether and always check for `cgi || fastcgi` instead, but that breaks real-world use-cases. --- sapi/cgi/cgi_main.c | 23 ++++++++++++++- sapi/cgi/tests/ghsa-3qgc-jrrr-25jv.phpt | 38 +++++++++++++++++++++++++ 2 files changed, 60 insertions(+), 1 deletion(-) create mode 100644 sapi/cgi/tests/ghsa-3qgc-jrrr-25jv.phpt diff --git a/sapi/cgi/cgi_main.c b/sapi/cgi/cgi_main.c index 499a7932bed17..2c1fa9332dfbb 100644 --- a/sapi/cgi/cgi_main.c +++ b/sapi/cgi/cgi_main.c @@ -1798,8 +1798,13 @@ int main(int argc, char *argv[]) } } + /* Apache CGI will pass the query string to the command line if it doesn't contain a '='. + * This can create an issue where a malicious request can pass command line arguments to + * the executable. Ideally we skip argument parsing when we're in cgi or fastcgi mode, + * but that breaks PHP scripts on Linux with a hashbang: `#!/php-cgi -d option=value`. + * Therefore, this code only prevents passing arguments if the query string starts with a '-'. + * Similarly, scripts spawned in subprocesses on Windows may have the same issue. */ if((query_string = getenv("QUERY_STRING")) != NULL && strchr(query_string, '=') == NULL) { - /* we've got query string that has no = - apache CGI will pass it to command line */ unsigned char *p; decoded_query_string = strdup(query_string); php_url_decode(decoded_query_string, strlen(decoded_query_string)); @@ -1809,6 +1814,22 @@ int main(int argc, char *argv[]) if(*p == '-') { skip_getopt = 1; } + + /* On Windows we have to take into account the "best fit" mapping behaviour. */ +#ifdef PHP_WIN32 + if (*p >= 0x80) { + wchar_t wide_buf[1]; + wide_buf[0] = *p; + char char_buf[4]; + size_t wide_buf_len = sizeof(wide_buf) / sizeof(wide_buf[0]); + size_t char_buf_len = sizeof(char_buf) / sizeof(char_buf[0]); + if (WideCharToMultiByte(CP_ACP, 0, wide_buf, wide_buf_len, char_buf, char_buf_len, NULL, NULL) == 0 + || char_buf[0] == '-') { + skip_getopt = 1; + } + } +#endif + free(decoded_query_string); } diff --git a/sapi/cgi/tests/ghsa-3qgc-jrrr-25jv.phpt b/sapi/cgi/tests/ghsa-3qgc-jrrr-25jv.phpt new file mode 100644 index 0000000000000..fd2fcdfbf897d --- /dev/null +++ b/sapi/cgi/tests/ghsa-3qgc-jrrr-25jv.phpt @@ -0,0 +1,38 @@ +--TEST-- +GHSA-3qgc-jrrr-25jv +--SKIPIF-- + +--FILE-- +'; +file_put_contents($filename, $script); + +$php = get_cgi_path(); +reset_env_vars(); + +putenv("SERVER_NAME=Test"); +putenv("SCRIPT_FILENAME=$filename"); +putenv("QUERY_STRING=%ads"); +putenv("REDIRECT_STATUS=1"); + +passthru("$php -s"); + +?> +--CLEAN-- + +--EXPECTF-- +X-Powered-By: PHP/%s +Content-type: %s + +hello world From 5c6d47372cff9ac01113763cce5bdc097bb90f33 Mon Sep 17 00:00:00 2001 From: Niels Dossche <7771979+nielsdos@users.noreply.github.com> Date: Wed, 22 May 2024 22:25:02 +0200 Subject: [PATCH 09/11] Fix GHSA-w8qr-v226-r27w We should not early-out with success status if we found an ipv6 hostname, we should keep checking the rest of the conditions. Because integrating the if-check of the ipv6 hostname in the "Validate domain" if-check made the code hard to read, I extracted the condition out to a separate function. This also required to make a few pointers const in order to have some clean code. --- ext/filter/logical_filters.c | 35 ++++++++++--------- ext/filter/tests/ghsa-w8qr-v226-r27w.phpt | 41 +++++++++++++++++++++++ 2 files changed, 61 insertions(+), 15 deletions(-) create mode 100644 ext/filter/tests/ghsa-w8qr-v226-r27w.phpt diff --git a/ext/filter/logical_filters.c b/ext/filter/logical_filters.c index 182f2b3cd0448..3db0ef4090500 100644 --- a/ext/filter/logical_filters.c +++ b/ext/filter/logical_filters.c @@ -89,7 +89,7 @@ #define FORMAT_IPV4 4 #define FORMAT_IPV6 6 -static int _php_filter_validate_ipv6(char *str, size_t str_len, int ip[8]); +static int _php_filter_validate_ipv6(const char *str, size_t str_len, int ip[8]); static int php_filter_parse_int(const char *str, size_t str_len, zend_long *ret) { /* {{{ */ zend_long ctx_value; @@ -580,6 +580,14 @@ static int is_userinfo_valid(zend_string *str) return 1; } +static bool php_filter_is_valid_ipv6_hostname(const char *s, size_t l) +{ + const char *e = s + l; + const char *t = e - 1; + + return *s == '[' && *t == ']' && _php_filter_validate_ipv6(s + 1, l - 2, NULL); +} + void php_filter_validate_url(https://redirect.github.com/PHP_INPUT_FILTER_PARAM_DECL) /* {{{ */ { php_url *url; @@ -600,7 +608,7 @@ void php_filter_validate_url(https://redirect.github.com/PHP_INPUT_FILTER_PARAM_DECL) /* {{{ */ if (url->scheme != NULL && (zend_string_equals_literal_ci(url->scheme, "http") || zend_string_equals_literal_ci(url->scheme, "https"))) { - char *e, *s, *t; + const char *s; size_t l; if (url->host == NULL) { @@ -609,17 +617,14 @@ void php_filter_validate_url(https://redirect.github.com/PHP_INPUT_FILTER_PARAM_DECL) /* {{{ */ s = ZSTR_VAL(url->host); l = ZSTR_LEN(url->host); - e = s + l; - t = e - 1; - - /* An IPv6 enclosed by square brackets is a valid hostname */ - if (*s == '[' && *t == ']' && _php_filter_validate_ipv6((s + 1), l - 2, NULL)) { - php_url_free(url); - return; - } - // Validate domain - if (!_php_filter_validate_domain(ZSTR_VAL(url->host), l, FILTER_FLAG_HOSTNAME)) { + if ( + /* An IPv6 enclosed by square brackets is a valid hostname.*/ + !php_filter_is_valid_ipv6_hostname(s, l) && + /* Validate domain. + * This includes a loose check for an IPv4 address. */ + !_php_filter_validate_domain(ZSTR_VAL(url->host), l, FILTER_FLAG_HOSTNAME) + ) { php_url_free(url); RETURN_VALIDATION_FAILED } @@ -753,15 +758,15 @@ static int _php_filter_validate_ipv4(char *str, size_t str_len, int *ip) /* {{{ } /* }}} */ -static int _php_filter_validate_ipv6(char *str, size_t str_len, int ip[8]) /* {{{ */ +static int _php_filter_validate_ipv6(const char *str, size_t str_len, int ip[8]) /* {{{ */ { int compressed_pos = -1; int blocks = 0; int num, n, i; char *ipv4; - char *end; + const char *end; int ip4elm[4]; - char *s = str; + const char *s = str; if (!memchr(str, ':', str_len)) { return 0; diff --git a/ext/filter/tests/ghsa-w8qr-v226-r27w.phpt b/ext/filter/tests/ghsa-w8qr-v226-r27w.phpt new file mode 100644 index 0000000000000..0092408ee5ad6 --- /dev/null +++ b/ext/filter/tests/ghsa-w8qr-v226-r27w.phpt @@ -0,0 +1,41 @@ +--TEST-- +GHSA-w8qr-v226-r27w +--EXTENSIONS-- +filter +--FILE-- + +--EXPECT-- +--- These ones should fail --- +bool(false) +bool(false) +bool(false) +bool(false) +bool(false) +bool(false) +bool(false) +bool(false) +--- These ones should work --- +string(21) "http://test@127.0.0.1/" +string(50) "http://test@[2001:db8:3333:4444:5555:6666:102:304]/" +string(17) "http://test@[::1]/" From 6150156d3a41f4eb16db412cb2c1bd5afa87d646 Mon Sep 17 00:00:00 2001 From: Ben Ramsey Date: Wed, 5 Jun 2024 00:39:47 -0500 Subject: [PATCH 10/11] Update NEWS Co-authored-by: Eric Mann --- NEWS | 24 ++++++++++++++++++++++-- 1 file changed, 22 insertions(+), 2 deletions(-) diff --git a/NEWS b/NEWS index f9b8fb276472d..3cfa614f0cd9b 100644 --- a/NEWS +++ b/NEWS @@ -1,8 +1,28 @@ PHP NEWS ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||| -?? ??? ????, PHP 8.1.29 +06 Jun 2024, PHP 8.1.29 +- CGI: + . Fixed bug GHSA-3qgc-jrrr-25jv (Bypass of CVE-2012-1823, Argument Injection + in PHP-CGI). (CVE-2024-4577) (nielsdos) +- Filter: + . Fixed bug GHSA-w8qr-v226-r27w (Filter bypass in filter_var FILTER_VALIDATE_URL). + (CVE-2024-5458) (nielsdos) + +- OpenSSL: + . The openssl_private_decrypt function in PHP, when using PKCS1 padding + (OPENSSL_PKCS1_PADDING, which is the default), is vulnerable to the Marvin Attack + unless it is used with an OpenSSL version that includes the changes from this pull + request: https://github.com/openssl/openssl/pull/13817 (rsa_pkcs1_implicit_rejection). + These changes are part of OpenSSL 3.2 and have also been backported to stable + versions of various Linux distributions, as well as to the PHP builds provided for + Windows since the previous release. All distributors and builders should ensure that + this version is used to prevent PHP from being vulnerable. (CVE-2024-2408) + +- Standard: + . Fixed bug GHSA-9fcc-425m-g385 (Bypass of CVE-2024-1874). + (CVE-2024-5585) (nielsdos) 11 Apr 2024, PHP 8.1.28 @@ -31,7 +51,7 @@ PHP NEWS - FPM: . Fixed bug GH-12705 (Segmentation fault in fpm_status_export_to_zval). (Patrick Prasse) - + - Intl: . Fixed bug GH-12635 (Test bug69398.phpt fails with ICU 74.1). (nielsdos) From fc4973fb0dfae6085742868b8f0f05163e150a0c Mon Sep 17 00:00:00 2001 From: Ben Ramsey Date: Wed, 5 Jun 2024 00:51:57 -0500 Subject: [PATCH 11/11] Update versions for PHP 8.1.29 --- Zend/zend.h | 2 +- configure.ac | 2 +- main/php_version.h | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Zend/zend.h b/Zend/zend.h index 9a414960e2a87..015b815e41815 100644 --- a/Zend/zend.h +++ b/Zend/zend.h @@ -20,7 +20,7 @@ #ifndef ZEND_H #define ZEND_H -#define ZEND_VERSION "4.1.29-dev" +#define ZEND_VERSION "4.1.29" #define ZEND_ENGINE_3 diff --git a/configure.ac b/configure.ac index 83d50445be205..d88ead381bfb6 100644 --- a/configure.ac +++ b/configure.ac @@ -17,7 +17,7 @@ dnl Basic autoconf initialization, generation of config.nice. dnl ---------------------------------------------------------------------------- AC_PREREQ([2.68]) -AC_INIT([PHP],[8.1.29-dev],[https://github.com/php/php-src/issues],[php],[https://www.php.net]) +AC_INIT([PHP],[8.1.29],[https://github.com/php/php-src/issues],[php],[https://www.php.net]) AC_CONFIG_SRCDIR([main/php_version.h]) AC_CONFIG_AUX_DIR([build]) AC_PRESERVE_HELP_ORDER diff --git a/main/php_version.h b/main/php_version.h index a7b35a63a9285..66624d0499be3 100644 --- a/main/php_version.h +++ b/main/php_version.h @@ -3,6 +3,6 @@ #define PHP_MAJOR_VERSION 8 #define PHP_MINOR_VERSION 1 #define PHP_RELEASE_VERSION 29 -#define PHP_EXTRA_VERSION "-dev" -#define PHP_VERSION "8.1.29-dev" +#define PHP_EXTRA_VERSION "" +#define PHP_VERSION "8.1.29" #define PHP_VERSION_ID 80129