summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorLibravatar Junio C Hamano <gitster@pobox.com>2020-01-02 12:38:30 -0800
committerLibravatar Junio C Hamano <gitster@pobox.com>2020-01-02 12:38:30 -0800
commit13432fc6dd4f9f78d0015611621991693f8fb218 (patch)
tree221e5140bf01713bf9c2f4fd05c0501ccee3ae40
parentMerge branch 'en/rebase-signoff-fix' (diff)
parentmingw: refuse paths containing reserved names (diff)
downloadtgif-13432fc6dd4f9f78d0015611621991693f8fb218.tar.xz
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
-rw-r--r--compat/mingw.c122
-rw-r--r--compat/mingw.h11
-rwxr-xr-xt/t0060-path-utils.sh13
3 files changed, 122 insertions, 24 deletions
diff --git a/compat/mingw.c b/compat/mingw.c
index 76ac8713d2..402c1ad91c 100644
--- a/compat/mingw.c
+++ b/compat/mingw.c
@@ -404,7 +404,7 @@ int mingw_mkdir(const char *path, int mode)
int ret;
wchar_t wpath[MAX_PATH];
- if (!is_valid_win32_path(path)) {
+ if (!is_valid_win32_path(path, 0)) {
errno = EINVAL;
return -1;
}
@@ -490,21 +490,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_path(wfilename, filename) < 0)
+ if (filename && !strcmp(filename, "/dev/null"))
+ wcscpy(wfilename, L"nul");
+ else if (xutftowcs_path(wfilename, filename) < 0)
return -1;
+
fd = open_fn(wfilename, oflags, mode);
if (fd < 0 && (oflags & O_ACCMODE) != O_RDONLY && errno == EACCES) {
@@ -561,16 +561,18 @@ FILE *mingw_fopen (const char *filename, const char *otype)
int hide = needs_hiding(filename);
FILE *file;
wchar_t wfilename[MAX_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_path(wfilename, filename) < 0 ||
- xutftowcs(wotype, otype, ARRAY_SIZE(wotype)) < 0)
+ } else if (xutftowcs_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;
@@ -588,16 +590,18 @@ FILE *mingw_freopen (const char *filename, const char *otype, FILE *stream)
int hide = needs_hiding(filename);
FILE *file;
wchar_t wfilename[MAX_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_path(wfilename, filename) < 0 ||
- xutftowcs(wotype, otype, ARRAY_SIZE(wotype)) < 0)
+ } else if (xutftowcs_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;
@@ -2529,14 +2533,16 @@ static void setup_windows_environment(void)
}
}
-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++);
@@ -2551,7 +2557,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 */
diff --git a/compat/mingw.h b/compat/mingw.h
index 3ec9fc36a5..714bc1d591 100644
--- a/compat/mingw.h
+++ b/compat/mingw.h
@@ -461,10 +461,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)
/**
* Converts UTF-8 encoded string to UTF-16LE.
diff --git a/t/t0060-path-utils.sh b/t/t0060-path-utils.sh
index de2df573a7..2ea2d00c39 100755
--- a/t/t0060-path-utils.sh
+++ b/t/t0060-path-utils.sh
@@ -469,11 +469,14 @@ 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 " \
@@ -481,7 +484,13 @@ test_expect_success MINGW 'is_valid_path() on Windows' '
"win32." \
"win32 . ." \
.../hello.txt \
- colon:test
+ colon:test \
+ "AUX.c" \
+ "abc/conOut\$ .xyz/test" \
+ lpt8 \
+ "lpt*" \
+ Nul \
+ "PRN./abc"
'
test_done