From 13714582b7bfe61e0c70c6c85271b9a229f66229 Mon Sep 17 00:00:00 2001 From: "Christoph M. Becker" Date: Sun, 3 Nov 2024 14:22:41 +0100 Subject: [PATCH 1/2] 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. --- ext/standard/tests/directory/gh10992.phpt | 16 +++++++++++ win32/ioutil.c | 29 +++++++++++++++++--- win32/ioutil.h | 33 ++++++++++++++++++----- 3 files changed, 68 insertions(+), 10 deletions(-) create mode 100644 ext/standard/tests/directory/gh10992.phpt diff --git a/ext/standard/tests/directory/gh10992.phpt b/ext/standard/tests/directory/gh10992.phpt new file mode 100644 index 0000000000000..99ac8a5ecd290 --- /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 a6f2fe2ae2fc1..f07d3e173e6b1 100644 --- a/win32/ioutil.c +++ b/win32/ioutil.c @@ -285,7 +285,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) { @@ -296,7 +296,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 @@ -319,7 +328,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); @@ -329,6 +338,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; @@ -337,7 +358,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 f6105a87f2468..8ed5eca975386 100644 --- a/win32/ioutil.h +++ b/win32/ioutil.h @@ -180,18 +180,28 @@ BOOL php_win32_ioutil_init(void); __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; @@ -204,7 +214,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); @@ -220,6 +230,17 @@ __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(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; @@ -228,9 +249,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); From b035a8accfe49204e19d5098e704bc1e6b2a8fa6 Mon Sep 17 00:00:00 2001 From: "Christoph M. Becker" Date: Mon, 4 Nov 2024 23:41:09 +0100 Subject: [PATCH 2/2] Fix memleak --- win32/ioutil.h | 1 + 1 file changed, 1 insertion(+) diff --git a/win32/ioutil.h b/win32/ioutil.h index 8ed5eca975386..85e9575ac6354 100644 --- a/win32/ioutil.h +++ b/win32/ioutil.h @@ -234,6 +234,7 @@ __forceinline static wchar_t *php_win32_ioutil_conv_any_to_w(const char* in, siz if (dir_len > 0) { size_t len = GetCurrentDirectoryW(dir_len, dst); if (len == 0 || len + 1 != dir_len) { + free(ret); free(mb); return NULL; }