Skip to content

Commit 5c76ef7

Browse files
committed
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.
1 parent 59fe79f commit 5c76ef7

File tree

4 files changed

+73
-10
lines changed

4 files changed

+73
-10
lines changed

NEWS

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,10 @@ PHP NEWS
4040
. Fixed bug #49169 (SoapServer calls wrong function, although "SOAP action"
4141
header is correct). (nielsdos)
4242

43+
- Windows:
44+
. Fixed bug GH-10992 (Improper long path support for relative paths). (cmb,
45+
nielsdos)
46+
4347
- XMLWriter:
4448
. Improved performance and reduce memory consumption. (nielsdos)
4549

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
--TEST--
2+
GH-10992 (Improper long path support for relative paths)
3+
--FILE--
4+
<?php
5+
$dir = str_repeat('b', 250 - strlen(getcwd()));
6+
var_dump(mkdir($dir));
7+
var_dump(rmdir($dir));
8+
$dir = str_repeat('b', 265 - strlen(getcwd()));
9+
var_dump(mkdir($dir));
10+
var_dump(rmdir($dir));
11+
?>
12+
--EXPECT--
13+
bool(true)
14+
bool(true)
15+
bool(true)
16+
bool(true)

win32/ioutil.c

Lines changed: 25 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -281,7 +281,7 @@ PW32IO int php_win32_ioutil_close(int fd)
281281

282282
PW32IO int php_win32_ioutil_mkdir_w(const wchar_t *path, mode_t mode)
283283
{/*{{{*/
284-
size_t path_len;
284+
size_t path_len, dir_len = 0;
285285
const wchar_t *my_path;
286286

287287
if (!path) {
@@ -292,7 +292,16 @@ PW32IO int php_win32_ioutil_mkdir_w(const wchar_t *path, mode_t mode)
292292
PHP_WIN32_IOUTIL_CHECK_PATH_W(path, -1, 0)
293293

294294
path_len = wcslen(path);
295-
if (path_len < _MAX_PATH && path_len >= _MAX_PATH - 12) {
295+
#ifndef ZTS
296+
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)) {
297+
dir_len = GetCurrentDirectoryW(0, NULL);
298+
if (dir_len == 0) {
299+
return -1;
300+
}
301+
}
302+
#endif
303+
304+
if (dir_len + path_len < _MAX_PATH && dir_len + path_len >= _MAX_PATH - 12) {
296305
/* Special case here. From the doc:
297306
298307
"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)
315324
}
316325

317326
if (!PHP_WIN32_IOUTIL_IS_LONG_PATHW(tmp, path_len)) {
318-
wchar_t *_tmp = (wchar_t *) malloc((path_len + PHP_WIN32_IOUTIL_LONG_PATH_PREFIX_LENW + 1) * sizeof(wchar_t));
327+
wchar_t *_tmp = (wchar_t *) malloc((dir_len + path_len + PHP_WIN32_IOUTIL_LONG_PATH_PREFIX_LENW + 1) * sizeof(wchar_t));
319328
wchar_t *src, *dst;
320329
if (!_tmp) {
321330
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)
325334
memmove(_tmp, PHP_WIN32_IOUTIL_LONG_PATH_PREFIXW, PHP_WIN32_IOUTIL_LONG_PATH_PREFIX_LENW * sizeof(wchar_t));
326335
src = tmp;
327336
dst = _tmp + PHP_WIN32_IOUTIL_LONG_PATH_PREFIX_LENW;
337+
#ifndef ZTS
338+
if (dir_len > 0) {
339+
size_t len = GetCurrentDirectoryW(dir_len, dst);
340+
if (len == 0 || len + 1 != dir_len) {
341+
free(tmp);
342+
free(_tmp);
343+
return -1;
344+
}
345+
dst += len;
346+
*dst++ = PHP_WIN32_IOUTIL_DEFAULT_SLASHW;
347+
}
348+
#endif
328349
while (src < tmp + path_len) {
329350
if (*src == PHP_WIN32_IOUTIL_FW_SLASHW) {
330351
*dst++ = PHP_WIN32_IOUTIL_DEFAULT_SLASHW;
@@ -333,7 +354,7 @@ PW32IO int php_win32_ioutil_mkdir_w(const wchar_t *path, mode_t mode)
333354
*dst++ = *src++;
334355
}
335356
}
336-
path_len += PHP_WIN32_IOUTIL_LONG_PATH_PREFIX_LENW;
357+
path_len += PHP_WIN32_IOUTIL_LONG_PATH_PREFIX_LENW + dir_len;
337358
_tmp[path_len] = L'\0';
338359
free(tmp);
339360
tmp = _tmp;

win32/ioutil.h

Lines changed: 28 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -175,18 +175,28 @@ PW32IO php_win32_ioutil_normalization_result php_win32_ioutil_normalize_path_w(w
175175
__forceinline static wchar_t *php_win32_ioutil_conv_any_to_w(const char* in, size_t in_len, size_t *out_len)
176176
{/*{{{*/
177177
wchar_t *mb, *ret;
178-
size_t mb_len;
178+
size_t mb_len, dir_len = 0;
179179

180180
mb = php_win32_cp_conv_any_to_w(in, in_len, &mb_len);
181181
if (!mb) {
182182
return NULL;
183183
}
184184

185+
#ifndef ZTS
186+
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)) {
187+
dir_len = GetCurrentDirectoryW(0, NULL);
188+
if (dir_len == 0) {
189+
free(mb);
190+
return NULL;
191+
}
192+
}
193+
#endif
194+
185195
/* Only prefix with long if it's needed. */
186-
if (mb_len >= _MAX_PATH) {
196+
if (dir_len + mb_len >= _MAX_PATH) {
187197
size_t new_mb_len;
188198

189-
ret = (wchar_t *) malloc((mb_len + PHP_WIN32_IOUTIL_LONG_PATH_PREFIX_LENW + 1) * sizeof(wchar_t));
199+
ret = (wchar_t *) malloc((dir_len + mb_len + PHP_WIN32_IOUTIL_LONG_PATH_PREFIX_LENW + 1) * sizeof(wchar_t));
190200
if (!ret) {
191201
free(mb);
192202
return NULL;
@@ -199,7 +209,7 @@ __forceinline static wchar_t *php_win32_ioutil_conv_any_to_w(const char* in, siz
199209
}
200210

201211
if (new_mb_len > mb_len) {
202-
wchar_t *tmp = (wchar_t *) realloc(ret, (new_mb_len + 1) * sizeof(wchar_t));
212+
wchar_t *tmp = (wchar_t *) realloc(ret, (dir_len + new_mb_len + 1) * sizeof(wchar_t));
203213
if (!tmp) {
204214
free(ret);
205215
free(mb);
@@ -215,6 +225,18 @@ __forceinline static wchar_t *php_win32_ioutil_conv_any_to_w(const char* in, siz
215225
} else {
216226
wchar_t *src = mb, *dst = ret + PHP_WIN32_IOUTIL_LONG_PATH_PREFIX_LENW;
217227
memmove(ret, PHP_WIN32_IOUTIL_LONG_PATH_PREFIXW, PHP_WIN32_IOUTIL_LONG_PATH_PREFIX_LENW * sizeof(wchar_t));
228+
#ifndef ZTS
229+
if (dir_len > 0) {
230+
size_t len = GetCurrentDirectoryW(dir_len, dst);
231+
if (len == 0 || len + 1 != dir_len) {
232+
free(ret);
233+
free(mb);
234+
return NULL;
235+
}
236+
dst += len;
237+
*dst++ = PHP_WIN32_IOUTIL_DEFAULT_SLASHW;
238+
}
239+
#endif
218240
while (src < mb + mb_len) {
219241
if (*src == PHP_WIN32_IOUTIL_FW_SLASHW) {
220242
*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
223245
*dst++ = *src++;
224246
}
225247
}
226-
ret[mb_len + PHP_WIN32_IOUTIL_LONG_PATH_PREFIX_LENW] = L'\0';
248+
ret[mb_len + PHP_WIN32_IOUTIL_LONG_PATH_PREFIX_LENW + dir_len] = L'\0';
227249

228-
mb_len += PHP_WIN32_IOUTIL_LONG_PATH_PREFIX_LENW;
250+
mb_len += PHP_WIN32_IOUTIL_LONG_PATH_PREFIX_LENW + dir_len;
229251
}
230252

231253
free(mb);

0 commit comments

Comments
 (0)