From 5c76ef78cb98737883c1e9480ed855bccc59818d Mon Sep 17 00:00:00 2001 From: "Christoph M. Becker" Date: Mon, 4 Nov 2024 23:41:09 +0100 Subject: [PATCH] Fix GH-10992: Improper long path support for relative paths Relative paths are passed to the ioutils APIs, these are not properly converted to long paths. If the path length already exceeds a given threshold (usually 259 characters, but only 247 for `mkdir()`), the long path prefix is prepended, resulting in an invalid path, since long paths have to be absolute. If the path length does not exceed that threshold, no conversion to a long path is done, although that may be necessary. Thus we take the path length of the current working directory into account when checking the threshold, and prepend it to the filename if necessary. Since this is only relevant for NTS builds, and using the current working directory of the process would be erroneous for ZTS builds, we skip the new code for ZTS builds. Closes GH-16687. --- NEWS | 4 +++ ext/standard/tests/directory/gh10992.phpt | 16 +++++++++++ win32/ioutil.c | 29 ++++++++++++++++--- win32/ioutil.h | 34 +++++++++++++++++++---- 4 files changed, 73 insertions(+), 10 deletions(-) create mode 100644 ext/standard/tests/directory/gh10992.phpt diff --git a/NEWS b/NEWS index a833bf4355e..24947599456 100644 --- a/NEWS +++ b/NEWS @@ -40,6 +40,10 @@ PHP NEWS . Fixed bug #49169 (SoapServer calls wrong function, although "SOAP action" header is correct). (nielsdos) +- Windows: + . Fixed bug GH-10992 (Improper long path support for relative paths). (cmb, + nielsdos) + - XMLWriter: . Improved performance and reduce memory consumption. (nielsdos) diff --git a/ext/standard/tests/directory/gh10992.phpt b/ext/standard/tests/directory/gh10992.phpt new file mode 100644 index 00000000000..99ac8a5ecd2 --- /dev/null +++ b/ext/standard/tests/directory/gh10992.phpt @@ -0,0 +1,16 @@ +--TEST-- +GH-10992 (Improper long path support for relative paths) +--FILE-- + +--EXPECT-- +bool(true) +bool(true) +bool(true) +bool(true) diff --git a/win32/ioutil.c b/win32/ioutil.c index acd4103c613..9c313d6001b 100644 --- a/win32/ioutil.c +++ b/win32/ioutil.c @@ -281,7 +281,7 @@ PW32IO int php_win32_ioutil_close(int fd) PW32IO int php_win32_ioutil_mkdir_w(const wchar_t *path, mode_t mode) {/*{{{*/ - size_t path_len; + size_t path_len, dir_len = 0; const wchar_t *my_path; if (!path) { @@ -292,7 +292,16 @@ PW32IO int php_win32_ioutil_mkdir_w(const wchar_t *path, mode_t mode) PHP_WIN32_IOUTIL_CHECK_PATH_W(path, -1, 0) path_len = wcslen(path); - if (path_len < _MAX_PATH && path_len >= _MAX_PATH - 12) { +#ifndef ZTS + if (!PHP_WIN32_IOUTIL_IS_ABSOLUTEW(path, path_len) && !PHP_WIN32_IOUTIL_IS_JUNCTION_PATHW(path, path_len) && !PHP_WIN32_IOUTIL_IS_UNC_PATHW(path, path_len)) { + dir_len = GetCurrentDirectoryW(0, NULL); + if (dir_len == 0) { + return -1; + } + } +#endif + + if (dir_len + path_len < _MAX_PATH && dir_len + path_len >= _MAX_PATH - 12) { /* Special case here. From the doc: "When using an API to create a directory, the specified path cannot be @@ -315,7 +324,7 @@ PW32IO int php_win32_ioutil_mkdir_w(const wchar_t *path, mode_t mode) } if (!PHP_WIN32_IOUTIL_IS_LONG_PATHW(tmp, path_len)) { - wchar_t *_tmp = (wchar_t *) malloc((path_len + PHP_WIN32_IOUTIL_LONG_PATH_PREFIX_LENW + 1) * sizeof(wchar_t)); + wchar_t *_tmp = (wchar_t *) malloc((dir_len + path_len + PHP_WIN32_IOUTIL_LONG_PATH_PREFIX_LENW + 1) * sizeof(wchar_t)); wchar_t *src, *dst; if (!_tmp) { SET_ERRNO_FROM_WIN32_CODE(ERROR_NOT_ENOUGH_MEMORY); @@ -325,6 +334,18 @@ PW32IO int php_win32_ioutil_mkdir_w(const wchar_t *path, mode_t mode) memmove(_tmp, PHP_WIN32_IOUTIL_LONG_PATH_PREFIXW, PHP_WIN32_IOUTIL_LONG_PATH_PREFIX_LENW * sizeof(wchar_t)); src = tmp; dst = _tmp + PHP_WIN32_IOUTIL_LONG_PATH_PREFIX_LENW; +#ifndef ZTS + if (dir_len > 0) { + size_t len = GetCurrentDirectoryW(dir_len, dst); + if (len == 0 || len + 1 != dir_len) { + free(tmp); + free(_tmp); + return -1; + } + dst += len; + *dst++ = PHP_WIN32_IOUTIL_DEFAULT_SLASHW; + } +#endif while (src < tmp + path_len) { if (*src == PHP_WIN32_IOUTIL_FW_SLASHW) { *dst++ = PHP_WIN32_IOUTIL_DEFAULT_SLASHW; @@ -333,7 +354,7 @@ PW32IO int php_win32_ioutil_mkdir_w(const wchar_t *path, mode_t mode) *dst++ = *src++; } } - path_len += PHP_WIN32_IOUTIL_LONG_PATH_PREFIX_LENW; + path_len += PHP_WIN32_IOUTIL_LONG_PATH_PREFIX_LENW + dir_len; _tmp[path_len] = L'\0'; free(tmp); tmp = _tmp; diff --git a/win32/ioutil.h b/win32/ioutil.h index 6a7055fc71a..454efdc3536 100644 --- a/win32/ioutil.h +++ b/win32/ioutil.h @@ -175,18 +175,28 @@ PW32IO php_win32_ioutil_normalization_result php_win32_ioutil_normalize_path_w(w __forceinline static wchar_t *php_win32_ioutil_conv_any_to_w(const char* in, size_t in_len, size_t *out_len) {/*{{{*/ wchar_t *mb, *ret; - size_t mb_len; + size_t mb_len, dir_len = 0; mb = php_win32_cp_conv_any_to_w(in, in_len, &mb_len); if (!mb) { return NULL; } +#ifndef ZTS + if (!PHP_WIN32_IOUTIL_IS_ABSOLUTEW(mb, mb_len) && !PHP_WIN32_IOUTIL_IS_JUNCTION_PATHW(mb, mb_len) && !PHP_WIN32_IOUTIL_IS_UNC_PATHW(mb, mb_len)) { + dir_len = GetCurrentDirectoryW(0, NULL); + if (dir_len == 0) { + free(mb); + return NULL; + } + } +#endif + /* Only prefix with long if it's needed. */ - if (mb_len >= _MAX_PATH) { + if (dir_len + mb_len >= _MAX_PATH) { size_t new_mb_len; - ret = (wchar_t *) malloc((mb_len + PHP_WIN32_IOUTIL_LONG_PATH_PREFIX_LENW + 1) * sizeof(wchar_t)); + ret = (wchar_t *) malloc((dir_len + mb_len + PHP_WIN32_IOUTIL_LONG_PATH_PREFIX_LENW + 1) * sizeof(wchar_t)); if (!ret) { free(mb); return NULL; @@ -199,7 +209,7 @@ __forceinline static wchar_t *php_win32_ioutil_conv_any_to_w(const char* in, siz } if (new_mb_len > mb_len) { - wchar_t *tmp = (wchar_t *) realloc(ret, (new_mb_len + 1) * sizeof(wchar_t)); + wchar_t *tmp = (wchar_t *) realloc(ret, (dir_len + new_mb_len + 1) * sizeof(wchar_t)); if (!tmp) { free(ret); free(mb); @@ -215,6 +225,18 @@ __forceinline static wchar_t *php_win32_ioutil_conv_any_to_w(const char* in, siz } else { wchar_t *src = mb, *dst = ret + PHP_WIN32_IOUTIL_LONG_PATH_PREFIX_LENW; memmove(ret, PHP_WIN32_IOUTIL_LONG_PATH_PREFIXW, PHP_WIN32_IOUTIL_LONG_PATH_PREFIX_LENW * sizeof(wchar_t)); +#ifndef ZTS + if (dir_len > 0) { + size_t len = GetCurrentDirectoryW(dir_len, dst); + if (len == 0 || len + 1 != dir_len) { + free(ret); + free(mb); + return NULL; + } + dst += len; + *dst++ = PHP_WIN32_IOUTIL_DEFAULT_SLASHW; + } +#endif while (src < mb + mb_len) { if (*src == PHP_WIN32_IOUTIL_FW_SLASHW) { *dst++ = PHP_WIN32_IOUTIL_DEFAULT_SLASHW; @@ -223,9 +245,9 @@ __forceinline static wchar_t *php_win32_ioutil_conv_any_to_w(const char* in, siz *dst++ = *src++; } } - ret[mb_len + PHP_WIN32_IOUTIL_LONG_PATH_PREFIX_LENW] = L'\0'; + ret[mb_len + PHP_WIN32_IOUTIL_LONG_PATH_PREFIX_LENW + dir_len] = L'\0'; - mb_len += PHP_WIN32_IOUTIL_LONG_PATH_PREFIX_LENW; + mb_len += PHP_WIN32_IOUTIL_LONG_PATH_PREFIX_LENW + dir_len; } free(mb);