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.
This commit is contained in:
Christoph M. Becker 2024-11-04 23:41:09 +01:00
parent 59fe79fb45
commit 5c76ef78cb
No known key found for this signature in database
GPG Key ID: D66C9593118BCCB6
4 changed files with 73 additions and 10 deletions

4
NEWS
View File

@ -40,6 +40,10 @@ PHP NEWS
. Fixed bug #49169 (SoapServer calls wrong function, although "SOAP action" . Fixed bug #49169 (SoapServer calls wrong function, although "SOAP action"
header is correct). (nielsdos) header is correct). (nielsdos)
- Windows:
. Fixed bug GH-10992 (Improper long path support for relative paths). (cmb,
nielsdos)
- XMLWriter: - XMLWriter:
. Improved performance and reduce memory consumption. (nielsdos) . Improved performance and reduce memory consumption. (nielsdos)

View File

@ -0,0 +1,16 @@
--TEST--
GH-10992 (Improper long path support for relative paths)
--FILE--
<?php
$dir = str_repeat('b', 250 - strlen(getcwd()));
var_dump(mkdir($dir));
var_dump(rmdir($dir));
$dir = str_repeat('b', 265 - strlen(getcwd()));
var_dump(mkdir($dir));
var_dump(rmdir($dir));
?>
--EXPECT--
bool(true)
bool(true)
bool(true)
bool(true)

View File

@ -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) 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; const wchar_t *my_path;
if (!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) PHP_WIN32_IOUTIL_CHECK_PATH_W(path, -1, 0)
path_len = wcslen(path); 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: /* Special case here. From the doc:
"When using an API to create a directory, the specified path cannot be "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)) { 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; wchar_t *src, *dst;
if (!_tmp) { if (!_tmp) {
SET_ERRNO_FROM_WIN32_CODE(ERROR_NOT_ENOUGH_MEMORY); 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)); memmove(_tmp, PHP_WIN32_IOUTIL_LONG_PATH_PREFIXW, PHP_WIN32_IOUTIL_LONG_PATH_PREFIX_LENW * sizeof(wchar_t));
src = tmp; src = tmp;
dst = _tmp + PHP_WIN32_IOUTIL_LONG_PATH_PREFIX_LENW; 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) { while (src < tmp + path_len) {
if (*src == PHP_WIN32_IOUTIL_FW_SLASHW) { if (*src == PHP_WIN32_IOUTIL_FW_SLASHW) {
*dst++ = PHP_WIN32_IOUTIL_DEFAULT_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++; *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'; _tmp[path_len] = L'\0';
free(tmp); free(tmp);
tmp = _tmp; tmp = _tmp;

View File

@ -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) __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; 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); mb = php_win32_cp_conv_any_to_w(in, in_len, &mb_len);
if (!mb) { if (!mb) {
return NULL; 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. */ /* 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; 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) { if (!ret) {
free(mb); free(mb);
return NULL; 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) { 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) { if (!tmp) {
free(ret); free(ret);
free(mb); free(mb);
@ -215,6 +225,18 @@ __forceinline static wchar_t *php_win32_ioutil_conv_any_to_w(const char* in, siz
} else { } else {
wchar_t *src = mb, *dst = ret + PHP_WIN32_IOUTIL_LONG_PATH_PREFIX_LENW; 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)); 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) { while (src < mb + mb_len) {
if (*src == PHP_WIN32_IOUTIL_FW_SLASHW) { if (*src == PHP_WIN32_IOUTIL_FW_SLASHW) {
*dst++ = PHP_WIN32_IOUTIL_DEFAULT_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++; *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); free(mb);