diff --git a/ext/standard/proc_open.c b/ext/standard/proc_open.c index 4ce57ba2dcc..2d4cb42b7a6 100644 --- a/ext/standard/proc_open.c +++ b/ext/standard/proc_open.c @@ -592,48 +592,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); - - 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) +static bool is_executed_by_cmd(const char *prog_name, size_t prog_name_length) { - /* 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; - } + size_t out_len; + WCHAR long_name[MAX_PATH]; + WCHAR full_name[MAX_PATH]; + LPWSTR file_part = 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; + wchar_t *prog_name_wide = php_win32_cp_conv_any_to_w(prog_name, prog_name_length, &out_len); + + 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); } - /* 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, '.'); - } + free(prog_name_wide); + prog_name_wide = NULL; - if (extension == NULL || extension == prog_name) { - /* No file extension found, it is not batch file. */ + if (GetFullPathNameW(long_name, MAX_PATH, full_name, &file_part) == 0 || file_part == NULL) { 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; + bool uses_cmd = false; + if (_wcsicmp(file_part, L"cmd.exe") == 0 || _wcsicmp(file_part, L"cmd") == 0) { + uses_cmd = true; + } else { + 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; + } + } + + return uses_cmd; } static zend_string *create_win_command_from_args(HashTable *args) @@ -652,7 +643,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 00000000000..28732106084 --- /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 00000000000..714836557af --- /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 00000000000..a632965eb98 --- /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-- +