Skip to content

Commit 13432fc

Browse files
committed
Merge branch 'js/mingw-reserved-filenames'
Forbid pathnames that the platform's filesystem cannot represent on MinGW. * js/mingw-reserved-filenames: mingw: refuse paths containing reserved names mingw: short-circuit the conversion of `/dev/null` to UTF-16
2 parents e0e1ac5 + 4dc42c6 commit 13432fc

File tree

3 files changed

+122
-24
lines changed

3 files changed

+122
-24
lines changed

compat/mingw.c

Lines changed: 102 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -404,7 +404,7 @@ int mingw_mkdir(const char *path, int mode)
404404
int ret;
405405
wchar_t wpath[MAX_PATH];
406406

407-
if (!is_valid_win32_path(path)) {
407+
if (!is_valid_win32_path(path, 0)) {
408408
errno = EINVAL;
409409
return -1;
410410
}
@@ -490,21 +490,21 @@ int mingw_open (const char *filename, int oflags, ...)
490490
mode = va_arg(args, int);
491491
va_end(args);
492492

493-
if (!is_valid_win32_path(filename)) {
493+
if (!is_valid_win32_path(filename, !create)) {
494494
errno = create ? EINVAL : ENOENT;
495495
return -1;
496496
}
497497

498-
if (filename && !strcmp(filename, "/dev/null"))
499-
filename = "nul";
500-
501498
if ((oflags & O_APPEND) && !is_local_named_pipe_path(filename))
502499
open_fn = mingw_open_append;
503500
else
504501
open_fn = _wopen;
505502

506-
if (xutftowcs_path(wfilename, filename) < 0)
503+
if (filename && !strcmp(filename, "/dev/null"))
504+
wcscpy(wfilename, L"nul");
505+
else if (xutftowcs_path(wfilename, filename) < 0)
507506
return -1;
507+
508508
fd = open_fn(wfilename, oflags, mode);
509509

510510
if (fd < 0 && (oflags & O_ACCMODE) != O_RDONLY && errno == EACCES) {
@@ -561,16 +561,18 @@ FILE *mingw_fopen (const char *filename, const char *otype)
561561
int hide = needs_hiding(filename);
562562
FILE *file;
563563
wchar_t wfilename[MAX_PATH], wotype[4];
564-
if (!is_valid_win32_path(filename)) {
564+
if (filename && !strcmp(filename, "/dev/null"))
565+
wcscpy(wfilename, L"nul");
566+
else if (!is_valid_win32_path(filename, 1)) {
565567
int create = otype && strchr(otype, 'w');
566568
errno = create ? EINVAL : ENOENT;
567569
return NULL;
568-
}
569-
if (filename && !strcmp(filename, "/dev/null"))
570-
filename = "nul";
571-
if (xutftowcs_path(wfilename, filename) < 0 ||
572-
xutftowcs(wotype, otype, ARRAY_SIZE(wotype)) < 0)
570+
} else if (xutftowcs_path(wfilename, filename) < 0)
573571
return NULL;
572+
573+
if (xutftowcs(wotype, otype, ARRAY_SIZE(wotype)) < 0)
574+
return NULL;
575+
574576
if (hide && !access(filename, F_OK) && set_hidden_flag(wfilename, 0)) {
575577
error("could not unhide %s", filename);
576578
return NULL;
@@ -588,16 +590,18 @@ FILE *mingw_freopen (const char *filename, const char *otype, FILE *stream)
588590
int hide = needs_hiding(filename);
589591
FILE *file;
590592
wchar_t wfilename[MAX_PATH], wotype[4];
591-
if (!is_valid_win32_path(filename)) {
593+
if (filename && !strcmp(filename, "/dev/null"))
594+
wcscpy(wfilename, L"nul");
595+
else if (!is_valid_win32_path(filename, 1)) {
592596
int create = otype && strchr(otype, 'w');
593597
errno = create ? EINVAL : ENOENT;
594598
return NULL;
595-
}
596-
if (filename && !strcmp(filename, "/dev/null"))
597-
filename = "nul";
598-
if (xutftowcs_path(wfilename, filename) < 0 ||
599-
xutftowcs(wotype, otype, ARRAY_SIZE(wotype)) < 0)
599+
} else if (xutftowcs_path(wfilename, filename) < 0)
600+
return NULL;
601+
602+
if (xutftowcs(wotype, otype, ARRAY_SIZE(wotype)) < 0)
600603
return NULL;
604+
601605
if (hide && !access(filename, F_OK) && set_hidden_flag(wfilename, 0)) {
602606
error("could not unhide %s", filename);
603607
return NULL;
@@ -2529,14 +2533,16 @@ static void setup_windows_environment(void)
25292533
}
25302534
}
25312535

2532-
int is_valid_win32_path(const char *path)
2536+
int is_valid_win32_path(const char *path, int allow_literal_nul)
25332537
{
2538+
const char *p = path;
25342539
int preceding_space_or_period = 0, i = 0, periods = 0;
25352540

25362541
if (!protect_ntfs)
25372542
return 1;
25382543

25392544
skip_dos_drive_prefix((char **)&path);
2545+
goto segment_start;
25402546

25412547
for (;;) {
25422548
char c = *(path++);
@@ -2551,7 +2557,83 @@ int is_valid_win32_path(const char *path)
25512557
return 1;
25522558

25532559
i = periods = preceding_space_or_period = 0;
2554-
continue;
2560+
2561+
segment_start:
2562+
switch (*path) {
2563+
case 'a': case 'A': /* AUX */
2564+
if (((c = path[++i]) != 'u' && c != 'U') ||
2565+
((c = path[++i]) != 'x' && c != 'X')) {
2566+
not_a_reserved_name:
2567+
path += i;
2568+
continue;
2569+
}
2570+
break;
2571+
case 'c': case 'C': /* COM<N>, CON, CONIN$, CONOUT$ */
2572+
if ((c = path[++i]) != 'o' && c != 'O')
2573+
goto not_a_reserved_name;
2574+
c = path[++i];
2575+
if (c == 'm' || c == 'M') { /* COM<N> */
2576+
if (!isdigit(path[++i]))
2577+
goto not_a_reserved_name;
2578+
} else if (c == 'n' || c == 'N') { /* CON */
2579+
c = path[i + 1];
2580+
if ((c == 'i' || c == 'I') &&
2581+
((c = path[i + 2]) == 'n' ||
2582+
c == 'N') &&
2583+
path[i + 3] == '$')
2584+
i += 3; /* CONIN$ */
2585+
else if ((c == 'o' || c == 'O') &&
2586+
((c = path[i + 2]) == 'u' ||
2587+
c == 'U') &&
2588+
((c = path[i + 3]) == 't' ||
2589+
c == 'T') &&
2590+
path[i + 4] == '$')
2591+
i += 4; /* CONOUT$ */
2592+
} else
2593+
goto not_a_reserved_name;
2594+
break;
2595+
case 'l': case 'L': /* LPT<N> */
2596+
if (((c = path[++i]) != 'p' && c != 'P') ||
2597+
((c = path[++i]) != 't' && c != 'T') ||
2598+
!isdigit(path[++i]))
2599+
goto not_a_reserved_name;
2600+
break;
2601+
case 'n': case 'N': /* NUL */
2602+
if (((c = path[++i]) != 'u' && c != 'U') ||
2603+
((c = path[++i]) != 'l' && c != 'L') ||
2604+
(allow_literal_nul &&
2605+
!path[i + 1] && p == path))
2606+
goto not_a_reserved_name;
2607+
break;
2608+
case 'p': case 'P': /* PRN */
2609+
if (((c = path[++i]) != 'r' && c != 'R') ||
2610+
((c = path[++i]) != 'n' && c != 'N'))
2611+
goto not_a_reserved_name;
2612+
break;
2613+
default:
2614+
continue;
2615+
}
2616+
2617+
/*
2618+
* So far, this looks like a reserved name. Let's see
2619+
* whether it actually is one: trailing spaces, a file
2620+
* extension, or an NTFS Alternate Data Stream do not
2621+
* matter, the name is still reserved if any of those
2622+
* follow immediately after the actual name.
2623+
*/
2624+
i++;
2625+
if (path[i] == ' ') {
2626+
preceding_space_or_period = 1;
2627+
while (path[++i] == ' ')
2628+
; /* skip all spaces */
2629+
}
2630+
2631+
c = path[i];
2632+
if (c && c != '.' && c != ':' && c != '/' && c != '\\')
2633+
goto not_a_reserved_name;
2634+
2635+
/* contains reserved name */
2636+
return 0;
25552637
case '.':
25562638
periods++;
25572639
/* fallthru */

compat/mingw.h

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -461,10 +461,17 @@ char *mingw_query_user_email(void);
461461
*
462462
* - contain any of the reserved characters, e.g. `:`, `;`, `*`, etc
463463
*
464+
* - correspond to reserved names (such as `AUX`, `PRN`, etc)
465+
*
466+
* The `allow_literal_nul` parameter controls whether the path `NUL` should
467+
* be considered valid (this makes sense e.g. before opening files, as it is
468+
* perfectly legitimate to open `NUL` on Windows, just as it is to open
469+
* `/dev/null` on Unix/Linux).
470+
*
464471
* Returns 1 upon success, otherwise 0.
465472
*/
466-
int is_valid_win32_path(const char *path);
467-
#define is_valid_path(path) is_valid_win32_path(path)
473+
int is_valid_win32_path(const char *path, int allow_literal_nul);
474+
#define is_valid_path(path) is_valid_win32_path(path, 0)
468475

469476
/**
470477
* Converts UTF-8 encoded string to UTF-16LE.

t/t0060-path-utils.sh

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -469,19 +469,28 @@ test_expect_success 'match .gitmodules' '
469469
'
470470

471471
test_expect_success MINGW 'is_valid_path() on Windows' '
472-
test-tool path-utils is_valid_path \
472+
test-tool path-utils is_valid_path \
473473
win32 \
474474
"win32 x" \
475475
../hello.txt \
476476
C:\\git \
477+
comm \
478+
conout.c \
479+
lptN \
477480
\
478481
--not \
479482
"win32 " \
480483
"win32 /x " \
481484
"win32." \
482485
"win32 . ." \
483486
.../hello.txt \
484-
colon:test
487+
colon:test \
488+
"AUX.c" \
489+
"abc/conOut\$ .xyz/test" \
490+
lpt8 \
491+
"lpt*" \
492+
Nul \
493+
"PRN./abc"
485494
'
486495

487496
test_done

0 commit comments

Comments
 (0)