Skip to content

Commit baa8b8a

Browse files
authored
Merge pull request #2440 from dscho/mingw-reserved-filenames-gfw
Refuse to write to reserved filenames
2 parents b5deb24 + 5b7d8fa commit baa8b8a

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
@@ -621,7 +621,7 @@ int mingw_mkdir(const char *path, int mode)
621621
int ret;
622622
wchar_t wpath[MAX_LONG_PATH];
623623

624-
if (!is_valid_win32_path(path)) {
624+
if (!is_valid_win32_path(path, 0)) {
625625
errno = EINVAL;
626626
return -1;
627627
}
@@ -723,21 +723,21 @@ int mingw_open (const char *filename, int oflags, ...)
723723
mode = va_arg(args, int);
724724
va_end(args);
725725

726-
if (!is_valid_win32_path(filename)) {
726+
if (!is_valid_win32_path(filename, !create)) {
727727
errno = create ? EINVAL : ENOENT;
728728
return -1;
729729
}
730730

731-
if (filename && !strcmp(filename, "/dev/null"))
732-
filename = "nul";
733-
734731
if ((oflags & O_APPEND) && !is_local_named_pipe_path(filename))
735732
open_fn = mingw_open_append;
736733
else
737734
open_fn = _wopen;
738735

739-
if (xutftowcs_long_path(wfilename, filename) < 0)
736+
if (filename && !strcmp(filename, "/dev/null"))
737+
wcscpy(wfilename, L"nul");
738+
else if (xutftowcs_long_path(wfilename, filename) < 0)
740739
return -1;
740+
741741
fd = open_fn(wfilename, oflags, mode);
742742

743743
if (fd < 0 && (oflags & O_ACCMODE) != O_RDONLY && errno == EACCES) {
@@ -794,16 +794,18 @@ FILE *mingw_fopen (const char *filename, const char *otype)
794794
int hide = needs_hiding(filename);
795795
FILE *file;
796796
wchar_t wfilename[MAX_LONG_PATH], wotype[4];
797-
if (!is_valid_win32_path(filename)) {
797+
if (filename && !strcmp(filename, "/dev/null"))
798+
wcscpy(wfilename, L"nul");
799+
else if (!is_valid_win32_path(filename, 1)) {
798800
int create = otype && strchr(otype, 'w');
799801
errno = create ? EINVAL : ENOENT;
800802
return NULL;
801-
}
802-
if (filename && !strcmp(filename, "/dev/null"))
803-
filename = "nul";
804-
if (xutftowcs_long_path(wfilename, filename) < 0 ||
805-
xutftowcs(wotype, otype, ARRAY_SIZE(wotype)) < 0)
803+
} else if (xutftowcs_long_path(wfilename, filename) < 0)
806804
return NULL;
805+
806+
if (xutftowcs(wotype, otype, ARRAY_SIZE(wotype)) < 0)
807+
return NULL;
808+
807809
if (hide && !access(filename, F_OK) && set_hidden_flag(wfilename, 0)) {
808810
error("could not unhide %s", filename);
809811
return NULL;
@@ -821,16 +823,18 @@ FILE *mingw_freopen (const char *filename, const char *otype, FILE *stream)
821823
int hide = needs_hiding(filename);
822824
FILE *file;
823825
wchar_t wfilename[MAX_LONG_PATH], wotype[4];
824-
if (!is_valid_win32_path(filename)) {
826+
if (filename && !strcmp(filename, "/dev/null"))
827+
wcscpy(wfilename, L"nul");
828+
else if (!is_valid_win32_path(filename, 1)) {
825829
int create = otype && strchr(otype, 'w');
826830
errno = create ? EINVAL : ENOENT;
827831
return NULL;
828-
}
829-
if (filename && !strcmp(filename, "/dev/null"))
830-
filename = "nul";
831-
if (xutftowcs_long_path(wfilename, filename) < 0 ||
832-
xutftowcs(wotype, otype, ARRAY_SIZE(wotype)) < 0)
832+
} else if (xutftowcs_long_path(wfilename, filename) < 0)
833+
return NULL;
834+
835+
if (xutftowcs(wotype, otype, ARRAY_SIZE(wotype)) < 0)
833836
return NULL;
837+
834838
if (hide && !access(filename, F_OK) && set_hidden_flag(wfilename, 0)) {
835839
error("could not unhide %s", filename);
836840
return NULL;
@@ -3191,14 +3195,16 @@ static void setup_windows_environment(void)
31913195
setenv("LC_CTYPE", "C", 1);
31923196
}
31933197

3194-
int is_valid_win32_path(const char *path)
3198+
int is_valid_win32_path(const char *path, int allow_literal_nul)
31953199
{
3200+
const char *p = path;
31963201
int preceding_space_or_period = 0, i = 0, periods = 0;
31973202

31983203
if (!protect_ntfs)
31993204
return 1;
32003205

32013206
skip_dos_drive_prefix((char **)&path);
3207+
goto segment_start;
32023208

32033209
for (;;) {
32043210
char c = *(path++);
@@ -3213,7 +3219,83 @@ int is_valid_win32_path(const char *path)
32133219
return 1;
32143220

32153221
i = periods = preceding_space_or_period = 0;
3216-
continue;
3222+
3223+
segment_start:
3224+
switch (*path) {
3225+
case 'a': case 'A': /* AUX */
3226+
if (((c = path[++i]) != 'u' && c != 'U') ||
3227+
((c = path[++i]) != 'x' && c != 'X')) {
3228+
not_a_reserved_name:
3229+
path += i;
3230+
continue;
3231+
}
3232+
break;
3233+
case 'c': case 'C': /* COM<N>, CON, CONIN$, CONOUT$ */
3234+
if ((c = path[++i]) != 'o' && c != 'O')
3235+
goto not_a_reserved_name;
3236+
c = path[++i];
3237+
if (c == 'm' || c == 'M') { /* COM<N> */
3238+
if (!isdigit(path[++i]))
3239+
goto not_a_reserved_name;
3240+
} else if (c == 'n' || c == 'N') { /* CON */
3241+
c = path[i + 1];
3242+
if ((c == 'i' || c == 'I') &&
3243+
((c = path[i + 2]) == 'n' ||
3244+
c == 'N') &&
3245+
path[i + 3] == '$')
3246+
i += 3; /* CONIN$ */
3247+
else if ((c == 'o' || c == 'O') &&
3248+
((c = path[i + 2]) == 'u' ||
3249+
c == 'U') &&
3250+
((c = path[i + 3]) == 't' ||
3251+
c == 'T') &&
3252+
path[i + 4] == '$')
3253+
i += 4; /* CONOUT$ */
3254+
} else
3255+
goto not_a_reserved_name;
3256+
break;
3257+
case 'l': case 'L': /* LPT<N> */
3258+
if (((c = path[++i]) != 'p' && c != 'P') ||
3259+
((c = path[++i]) != 't' && c != 'T') ||
3260+
!isdigit(path[++i]))
3261+
goto not_a_reserved_name;
3262+
break;
3263+
case 'n': case 'N': /* NUL */
3264+
if (((c = path[++i]) != 'u' && c != 'U') ||
3265+
((c = path[++i]) != 'l' && c != 'L') ||
3266+
(allow_literal_nul &&
3267+
!path[i + 1] && p == path))
3268+
goto not_a_reserved_name;
3269+
break;
3270+
case 'p': case 'P': /* PRN */
3271+
if (((c = path[++i]) != 'r' && c != 'R') ||
3272+
((c = path[++i]) != 'n' && c != 'N'))
3273+
goto not_a_reserved_name;
3274+
break;
3275+
default:
3276+
continue;
3277+
}
3278+
3279+
/*
3280+
* So far, this looks like a reserved name. Let's see
3281+
* whether it actually is one: trailing spaces, a file
3282+
* extension, or an NTFS Alternate Data Stream do not
3283+
* matter, the name is still reserved if any of those
3284+
* follow immediately after the actual name.
3285+
*/
3286+
i++;
3287+
if (path[i] == ' ') {
3288+
preceding_space_or_period = 1;
3289+
while (path[++i] == ' ')
3290+
; /* skip all spaces */
3291+
}
3292+
3293+
c = path[i];
3294+
if (c && c != '.' && c != ':' && c != '/' && c != '\\')
3295+
goto not_a_reserved_name;
3296+
3297+
/* contains reserved name */
3298+
return 0;
32173299
case '.':
32183300
periods++;
32193301
/* fallthru */

compat/mingw.h

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -487,10 +487,17 @@ char *mingw_query_user_email(void);
487487
*
488488
* - contain any of the reserved characters, e.g. `:`, `;`, `*`, etc
489489
*
490+
* - correspond to reserved names (such as `AUX`, `PRN`, etc)
491+
*
492+
* The `allow_literal_nul` parameter controls whether the path `NUL` should
493+
* be considered valid (this makes sense e.g. before opening files, as it is
494+
* perfectly legitimate to open `NUL` on Windows, just as it is to open
495+
* `/dev/null` on Unix/Linux).
496+
*
490497
* Returns 1 upon success, otherwise 0.
491498
*/
492-
int is_valid_win32_path(const char *path);
493-
#define is_valid_path(path) is_valid_win32_path(path)
499+
int is_valid_win32_path(const char *path, int allow_literal_nul);
500+
#define is_valid_path(path) is_valid_win32_path(path, 0)
494501

495502
/**
496503
* Max length of long paths (exceeding MAX_PATH). The actual maximum supported

t/t0060-path-utils.sh

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

467467
test_expect_success MINGW 'is_valid_path() on Windows' '
468-
test-tool path-utils is_valid_path \
468+
test-tool path-utils is_valid_path \
469469
win32 \
470470
"win32 x" \
471471
../hello.txt \
472472
C:\\git \
473+
comm \
474+
conout.c \
475+
lptN \
473476
\
474477
--not \
475478
"win32 " \
476479
"win32 /x " \
477480
"win32." \
478481
"win32 . ." \
479482
.../hello.txt \
480-
colon:test
483+
colon:test \
484+
"AUX.c" \
485+
"abc/conOut\$ .xyz/test" \
486+
lpt8 \
487+
"lpt*" \
488+
Nul \
489+
"PRN./abc"
481490
'
482491

483492
test_done

0 commit comments

Comments
 (0)