Merge branch 'win32-accommodate-funny-drive-names'

While the only permitted drive letters for physical drives on Windows
are letters of the US-English alphabet, this restriction does not apply
to virtual drives assigned via `subst <letter>: <path>`.

To prevent targeted attacks against systems where "funny" drive letters
such as `1` or `!` are assigned, let's handle them as regular drive
letters on Windows.

This fixes CVE-2019-1351.

Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
maint
Johannes Schindelin 2019-09-16 13:26:40 +02:00
commit 2ddcccf97a
4 changed files with 54 additions and 6 deletions

View File

@ -1989,6 +1989,30 @@ pid_t waitpid(pid_t pid, int *status, int options)
return -1; return -1;
} }


int mingw_has_dos_drive_prefix(const char *path)
{
int i;

/*
* Does it start with an ASCII letter (i.e. highest bit not set),
* followed by a colon?
*/
if (!(0x80 & (unsigned char)*path))
return *path && path[1] == ':' ? 2 : 0;

/*
* While drive letters must be letters of the English alphabet, it is
* possible to assign virtually _any_ Unicode character via `subst` as
* a drive letter to "virtual drives". Even `1`, or `ä`. Or fun stuff
* like this:
*
* subst ֍: %USERPROFILE%\Desktop
*/
for (i = 1; i < 4 && (0x80 & (unsigned char)path[i]); i++)
; /* skip first UTF-8 character */
return path[i] == ':' ? i + 1 : 0;
}

int mingw_skip_dos_drive_prefix(char **path) int mingw_skip_dos_drive_prefix(char **path)
{ {
int ret = has_dos_drive_prefix(*path); int ret = has_dos_drive_prefix(*path);
@ -2137,6 +2161,8 @@ int is_valid_win32_path(const char *path)
if (!protect_ntfs) if (!protect_ntfs)
return 1; return 1;


skip_dos_drive_prefix((char **)&path);

for (;;) { for (;;) {
char c = *(path++); char c = *(path++);
switch (c) { switch (c) {
@ -2158,6 +2184,14 @@ int is_valid_win32_path(const char *path)
preceding_space_or_period = 1; preceding_space_or_period = 1;
i++; i++;
continue; continue;
case ':': /* DOS drive prefix was already skipped */
case '<': case '>': case '"': case '|': case '?': case '*':
/* illegal character */
return 0;
default:
if (c > '\0' && c < '\x20')
/* illegal character */
return 0;
} }
preceding_space_or_period = 0; preceding_space_or_period = 0;
i++; i++;

View File

@ -394,8 +394,8 @@ HANDLE winansi_get_osfhandle(int fd);
* git specific compatibility * git specific compatibility
*/ */


#define has_dos_drive_prefix(path) \ int mingw_has_dos_drive_prefix(const char *path);
(isalpha(*(path)) && (path)[1] == ':' ? 2 : 0) #define has_dos_drive_prefix mingw_has_dos_drive_prefix
int mingw_skip_dos_drive_prefix(char **path); int mingw_skip_dos_drive_prefix(char **path);
#define skip_dos_drive_prefix mingw_skip_dos_drive_prefix #define skip_dos_drive_prefix mingw_skip_dos_drive_prefix
static inline int mingw_is_dir_sep(int c) static inline int mingw_is_dir_sep(int c)
@ -431,8 +431,11 @@ int mingw_offset_1st_component(const char *path);
/** /**
* Verifies that the given path is a valid one on Windows. * Verifies that the given path is a valid one on Windows.
* *
* In particular, path segments are disallowed which end in a period or a * In particular, path segments are disallowed which
* space (except the special directories `.` and `..`). *
* - end in a period or a space (except the special directories `.` and `..`).
*
* - contain any of the reserved characters, e.g. `:`, `;`, `*`, etc
* *
* Returns 1 upon success, otherwise 0. * Returns 1 upon success, otherwise 0.
*/ */

View File

@ -264,7 +264,7 @@ int url_is_local_not_ssh(const char *url)
const char *colon = strchr(url, ':'); const char *colon = strchr(url, ':');
const char *slash = strchr(url, '/'); const char *slash = strchr(url, '/');
return !colon || (slash && slash < colon) || return !colon || (slash && slash < colon) ||
has_dos_drive_prefix(url); (has_dos_drive_prefix(url) && is_valid_path(url));
} }


static const char *prot_name(enum protocol protocol) static const char *prot_name(enum protocol protocol)

View File

@ -165,6 +165,15 @@ test_expect_success 'absolute path rejects the empty string' '
test_must_fail test-path-utils absolute_path "" test_must_fail test-path-utils absolute_path ""
' '


test_expect_success MINGW '<drive-letter>:\\abc is an absolute path' '
for letter in : \" C Z 1 ä
do
path=$letter:\\abc &&
absolute="$(test-path-utils absolute_path "$path")" &&
test "$path" = "$absolute" || return 1
done
'

test_expect_success 'real path rejects the empty string' ' test_expect_success 'real path rejects the empty string' '
test_must_fail test-path-utils real_path "" test_must_fail test-path-utils real_path ""
' '
@ -445,13 +454,15 @@ test_expect_success MINGW 'is_valid_path() on Windows' '
win32 \ win32 \
"win32 x" \ "win32 x" \
../hello.txt \ ../hello.txt \
C:\\git \
\ \
--not \ --not \
"win32 " \ "win32 " \
"win32 /x " \ "win32 /x " \
"win32." \ "win32." \
"win32 . ." \ "win32 . ." \
.../hello.txt .../hello.txt \
colon:test
' '


test_done test_done