Skip to content

Commit

Permalink
Merge pull request #2440 from dscho/mingw-reserved-filenames-gfw
Browse files Browse the repository at this point in the history
 Refuse to write to reserved filenames
  • Loading branch information
dscho authored Dec 26, 2019
2 parents b5deb24 + 5b7d8fa commit baa8b8a
Show file tree
Hide file tree
Showing 3 changed files with 122 additions and 24 deletions.
122 changes: 102 additions & 20 deletions compat/mingw.c
Original file line number Diff line number Diff line change
Expand Up @@ -621,7 +621,7 @@ int mingw_mkdir(const char *path, int mode)
int ret;
wchar_t wpath[MAX_LONG_PATH];

if (!is_valid_win32_path(path)) {
if (!is_valid_win32_path(path, 0)) {
errno = EINVAL;
return -1;
}
Expand Down Expand Up @@ -723,21 +723,21 @@ int mingw_open (const char *filename, int oflags, ...)
mode = va_arg(args, int);
va_end(args);

if (!is_valid_win32_path(filename)) {
if (!is_valid_win32_path(filename, !create)) {
errno = create ? EINVAL : ENOENT;
return -1;
}

if (filename && !strcmp(filename, "/dev/null"))
filename = "nul";

if ((oflags & O_APPEND) && !is_local_named_pipe_path(filename))
open_fn = mingw_open_append;
else
open_fn = _wopen;

if (xutftowcs_long_path(wfilename, filename) < 0)
if (filename && !strcmp(filename, "/dev/null"))
wcscpy(wfilename, L"nul");
else if (xutftowcs_long_path(wfilename, filename) < 0)
return -1;

fd = open_fn(wfilename, oflags, mode);

if (fd < 0 && (oflags & O_ACCMODE) != O_RDONLY && errno == EACCES) {
Expand Down Expand Up @@ -794,16 +794,18 @@ FILE *mingw_fopen (const char *filename, const char *otype)
int hide = needs_hiding(filename);
FILE *file;
wchar_t wfilename[MAX_LONG_PATH], wotype[4];
if (!is_valid_win32_path(filename)) {
if (filename && !strcmp(filename, "/dev/null"))
wcscpy(wfilename, L"nul");
else if (!is_valid_win32_path(filename, 1)) {
int create = otype && strchr(otype, 'w');
errno = create ? EINVAL : ENOENT;
return NULL;
}
if (filename && !strcmp(filename, "/dev/null"))
filename = "nul";
if (xutftowcs_long_path(wfilename, filename) < 0 ||
xutftowcs(wotype, otype, ARRAY_SIZE(wotype)) < 0)
} else if (xutftowcs_long_path(wfilename, filename) < 0)
return NULL;

if (xutftowcs(wotype, otype, ARRAY_SIZE(wotype)) < 0)
return NULL;

if (hide && !access(filename, F_OK) && set_hidden_flag(wfilename, 0)) {
error("could not unhide %s", filename);
return NULL;
Expand All @@ -821,16 +823,18 @@ FILE *mingw_freopen (const char *filename, const char *otype, FILE *stream)
int hide = needs_hiding(filename);
FILE *file;
wchar_t wfilename[MAX_LONG_PATH], wotype[4];
if (!is_valid_win32_path(filename)) {
if (filename && !strcmp(filename, "/dev/null"))
wcscpy(wfilename, L"nul");
else if (!is_valid_win32_path(filename, 1)) {
int create = otype && strchr(otype, 'w');
errno = create ? EINVAL : ENOENT;
return NULL;
}
if (filename && !strcmp(filename, "/dev/null"))
filename = "nul";
if (xutftowcs_long_path(wfilename, filename) < 0 ||
xutftowcs(wotype, otype, ARRAY_SIZE(wotype)) < 0)
} else if (xutftowcs_long_path(wfilename, filename) < 0)
return NULL;

if (xutftowcs(wotype, otype, ARRAY_SIZE(wotype)) < 0)
return NULL;

if (hide && !access(filename, F_OK) && set_hidden_flag(wfilename, 0)) {
error("could not unhide %s", filename);
return NULL;
Expand Down Expand Up @@ -3191,14 +3195,16 @@ static void setup_windows_environment(void)
setenv("LC_CTYPE", "C", 1);
}

int is_valid_win32_path(const char *path)
int is_valid_win32_path(const char *path, int allow_literal_nul)
{
const char *p = path;
int preceding_space_or_period = 0, i = 0, periods = 0;

if (!protect_ntfs)
return 1;

skip_dos_drive_prefix((char **)&path);
goto segment_start;

for (;;) {
char c = *(path++);
Expand All @@ -3213,7 +3219,83 @@ int is_valid_win32_path(const char *path)
return 1;

i = periods = preceding_space_or_period = 0;
continue;

segment_start:
switch (*path) {
case 'a': case 'A': /* AUX */
if (((c = path[++i]) != 'u' && c != 'U') ||
((c = path[++i]) != 'x' && c != 'X')) {
not_a_reserved_name:
path += i;
continue;
}
break;
case 'c': case 'C': /* COM<N>, CON, CONIN$, CONOUT$ */
if ((c = path[++i]) != 'o' && c != 'O')
goto not_a_reserved_name;
c = path[++i];
if (c == 'm' || c == 'M') { /* COM<N> */
if (!isdigit(path[++i]))
goto not_a_reserved_name;
} else if (c == 'n' || c == 'N') { /* CON */
c = path[i + 1];
if ((c == 'i' || c == 'I') &&
((c = path[i + 2]) == 'n' ||
c == 'N') &&
path[i + 3] == '$')
i += 3; /* CONIN$ */
else if ((c == 'o' || c == 'O') &&
((c = path[i + 2]) == 'u' ||
c == 'U') &&
((c = path[i + 3]) == 't' ||
c == 'T') &&
path[i + 4] == '$')
i += 4; /* CONOUT$ */
} else
goto not_a_reserved_name;
break;
case 'l': case 'L': /* LPT<N> */
if (((c = path[++i]) != 'p' && c != 'P') ||
((c = path[++i]) != 't' && c != 'T') ||
!isdigit(path[++i]))
goto not_a_reserved_name;
break;
case 'n': case 'N': /* NUL */
if (((c = path[++i]) != 'u' && c != 'U') ||
((c = path[++i]) != 'l' && c != 'L') ||
(allow_literal_nul &&
!path[i + 1] && p == path))
goto not_a_reserved_name;
break;
case 'p': case 'P': /* PRN */
if (((c = path[++i]) != 'r' && c != 'R') ||
((c = path[++i]) != 'n' && c != 'N'))
goto not_a_reserved_name;
break;
default:
continue;
}

/*
* So far, this looks like a reserved name. Let's see
* whether it actually is one: trailing spaces, a file
* extension, or an NTFS Alternate Data Stream do not
* matter, the name is still reserved if any of those
* follow immediately after the actual name.
*/
i++;
if (path[i] == ' ') {
preceding_space_or_period = 1;
while (path[++i] == ' ')
; /* skip all spaces */
}

c = path[i];
if (c && c != '.' && c != ':' && c != '/' && c != '\\')
goto not_a_reserved_name;

/* contains reserved name */
return 0;
case '.':
periods++;
/* fallthru */
Expand Down
11 changes: 9 additions & 2 deletions compat/mingw.h
Original file line number Diff line number Diff line change
Expand Up @@ -487,10 +487,17 @@ char *mingw_query_user_email(void);
*
* - contain any of the reserved characters, e.g. `:`, `;`, `*`, etc
*
* - correspond to reserved names (such as `AUX`, `PRN`, etc)
*
* The `allow_literal_nul` parameter controls whether the path `NUL` should
* be considered valid (this makes sense e.g. before opening files, as it is
* perfectly legitimate to open `NUL` on Windows, just as it is to open
* `/dev/null` on Unix/Linux).
*
* Returns 1 upon success, otherwise 0.
*/
int is_valid_win32_path(const char *path);
#define is_valid_path(path) is_valid_win32_path(path)
int is_valid_win32_path(const char *path, int allow_literal_nul);
#define is_valid_path(path) is_valid_win32_path(path, 0)

/**
* Max length of long paths (exceeding MAX_PATH). The actual maximum supported
Expand Down
13 changes: 11 additions & 2 deletions t/t0060-path-utils.sh
Original file line number Diff line number Diff line change
Expand Up @@ -465,19 +465,28 @@ test_expect_success 'match .gitmodules' '
'

test_expect_success MINGW 'is_valid_path() on Windows' '
test-tool path-utils is_valid_path \
test-tool path-utils is_valid_path \
win32 \
"win32 x" \
../hello.txt \
C:\\git \
comm \
conout.c \
lptN \
\
--not \
"win32 " \
"win32 /x " \
"win32." \
"win32 . ." \
.../hello.txt \
colon:test
colon:test \
"AUX.c" \
"abc/conOut\$ .xyz/test" \
lpt8 \
"lpt*" \
Nul \
"PRN./abc"
'

test_done

0 comments on commit baa8b8a

Please sign in to comment.