Skip to content

Commit d3ac8c3

Browse files
committed
Sync with 2.14.6
* maint-2.14: (28 commits) Git 2.14.6 mingw: handle `subst`-ed "DOS drives" mingw: refuse to access paths with trailing spaces or periods mingw: refuse to access paths with illegal characters unpack-trees: let merged_entry() pass through do_add_entry()'s errors quote-stress-test: offer to test quoting arguments for MSYS2 sh t6130/t9350: prepare for stringent Win32 path validation quote-stress-test: allow skipping some trials quote-stress-test: accept arguments to test via the command-line tests: add a helper to stress test argument quoting mingw: fix quoting of arguments Disallow dubiously-nested submodule git directories protect_ntfs: turn on NTFS protection by default path: also guard `.gitmodules` against NTFS Alternate Data Streams is_ntfs_dotgit(): speed it up mingw: disallow backslash characters in tree objects' file names path: safeguard `.git` against NTFS Alternate Streams Accesses clone --recurse-submodules: prevent name squatting on Windows is_ntfs_dotgit(): only verify the leading segment test-path-utils: offer to run a protectNTFS/protectHFS benchmark ...
2 parents 924c623 + 66d2a61 commit d3ac8c3

31 files changed

+811
-66
lines changed

Documentation/RelNotes/2.14.6.txt

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
Git v2.14.6 Release Notes
2+
=========================
3+
4+
This release addresses the security issues CVE-2019-1348,
5+
CVE-2019-1349, CVE-2019-1350, CVE-2019-1351, CVE-2019-1352,
6+
CVE-2019-1353, CVE-2019-1354, and CVE-2019-1387.
7+
8+
Fixes since v2.14.5
9+
-------------------
10+
11+
* CVE-2019-1348:
12+
The --export-marks option of git fast-import is exposed also via
13+
the in-stream command feature export-marks=... and it allows
14+
overwriting arbitrary paths.
15+
16+
* CVE-2019-1349:
17+
When submodules are cloned recursively, under certain circumstances
18+
Git could be fooled into using the same Git directory twice. We now
19+
require the directory to be empty.
20+
21+
* CVE-2019-1350:
22+
Incorrect quoting of command-line arguments allowed remote code
23+
execution during a recursive clone in conjunction with SSH URLs.
24+
25+
* CVE-2019-1351:
26+
While the only permitted drive letters for physical drives on
27+
Windows are letters of the US-English alphabet, this restriction
28+
does not apply to virtual drives assigned via subst <letter>:
29+
<path>. Git mistook such paths for relative paths, allowing writing
30+
outside of the worktree while cloning.
31+
32+
* CVE-2019-1352:
33+
Git was unaware of NTFS Alternate Data Streams, allowing files
34+
inside the .git/ directory to be overwritten during a clone.
35+
36+
* CVE-2019-1353:
37+
When running Git in the Windows Subsystem for Linux (also known as
38+
"WSL") while accessing a working directory on a regular Windows
39+
drive, none of the NTFS protections were active.
40+
41+
* CVE-2019-1354:
42+
Filenames on Linux/Unix can contain backslashes. On Windows,
43+
backslashes are directory separators. Git did not use to refuse to
44+
write out tracked files with such filenames.
45+
46+
* CVE-2019-1387:
47+
Recursive clones are currently affected by a vulnerability that is
48+
caused by too-lax validation of submodule names, allowing very
49+
targeted attacks via remote code execution in recursive clones.
50+
51+
Credit for finding these vulnerabilities goes to Microsoft Security
52+
Response Center, in particular to Nicolas Joly. The `fast-import`
53+
fixes were provided by Jeff King, the other fixes by Johannes
54+
Schindelin with help from Garima Singh.

Documentation/git-fast-import.txt

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,21 @@ OPTIONS
5050
memory used by fast-import during this run. Showing this output
5151
is currently the default, but can be disabled with --quiet.
5252

53+
--allow-unsafe-features::
54+
Many command-line options can be provided as part of the
55+
fast-import stream itself by using the `feature` or `option`
56+
commands. However, some of these options are unsafe (e.g.,
57+
allowing fast-import to access the filesystem outside of the
58+
repository). These options are disabled by default, but can be
59+
allowed by providing this option on the command line. This
60+
currently impacts only the `export-marks`, `import-marks`, and
61+
`import-marks-if-exists` feature commands.
62+
+
63+
Only enable this option if you trust the program generating the
64+
fast-import stream! This option is enabled automatically for
65+
remote-helpers that use the `import` capability, as they are
66+
already trusted to run their own code.
67+
5368
Options for Frontends
5469
~~~~~~~~~~~~~~~~~~~~~
5570

builtin/clone.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -758,7 +758,7 @@ static int checkout(int submodule_progress)
758758

759759
if (!err && (option_recurse_submodules.nr > 0)) {
760760
struct argv_array args = ARGV_ARRAY_INIT;
761-
argv_array_pushl(&args, "submodule", "update", "--init", "--recursive", NULL);
761+
argv_array_pushl(&args, "submodule", "update", "--require-init", "--recursive", NULL);
762762

763763
if (option_shallow_submodules == 1)
764764
argv_array_push(&args, "--depth=1");

builtin/submodule--helper.c

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
#include "remote.h"
1414
#include "refs.h"
1515
#include "connect.h"
16+
#include "dir.h"
1617

1718
static char *get_default_remote(void)
1819
{
@@ -616,6 +617,7 @@ static int module_clone(int argc, const char **argv, const char *prefix)
616617
char *p, *path = NULL, *sm_gitdir;
617618
struct strbuf sb = STRBUF_INIT;
618619
struct string_list reference = STRING_LIST_INIT_NODUP;
620+
int require_init = 0;
619621
char *sm_alternate = NULL, *error_strategy = NULL;
620622

621623
struct option module_clone_options[] = {
@@ -640,6 +642,8 @@ static int module_clone(int argc, const char **argv, const char *prefix)
640642
OPT__QUIET(&quiet, "Suppress output for cloning a submodule"),
641643
OPT_BOOL(0, "progress", &progress,
642644
N_("force cloning progress")),
645+
OPT_BOOL(0, "require-init", &require_init,
646+
N_("disallow cloning into non-empty directory")),
643647
OPT_END()
644648
};
645649

@@ -667,6 +671,10 @@ static int module_clone(int argc, const char **argv, const char *prefix)
667671
} else
668672
path = xstrdup(path);
669673

674+
if (validate_submodule_git_dir(sm_gitdir, name) < 0)
675+
die(_("refusing to create/use '%s' in another submodule's "
676+
"git dir"), sm_gitdir);
677+
670678
if (!file_exists(sm_gitdir)) {
671679
if (safe_create_leading_directories_const(sm_gitdir) < 0)
672680
die(_("could not create directory '%s'"), sm_gitdir);
@@ -678,6 +686,8 @@ static int module_clone(int argc, const char **argv, const char *prefix)
678686
die(_("clone of '%s' into submodule path '%s' failed"),
679687
url, path);
680688
} else {
689+
if (require_init && !access(path, X_OK) && !is_empty_dir(path))
690+
die(_("directory not empty: '%s'"), path);
681691
if (safe_create_leading_directories_const(path) < 0)
682692
die(_("could not create directory '%s'"), path);
683693
strbuf_addf(&sb, "%s/index", sm_gitdir);
@@ -726,6 +736,7 @@ struct submodule_update_clone {
726736
int quiet;
727737
int recommend_shallow;
728738
struct string_list references;
739+
unsigned require_init;
729740
const char *depth;
730741
const char *recursive_prefix;
731742
const char *prefix;
@@ -741,7 +752,7 @@ struct submodule_update_clone {
741752
int failed_clones_nr, failed_clones_alloc;
742753
};
743754
#define SUBMODULE_UPDATE_CLONE_INIT {0, MODULE_LIST_INIT, 0, \
744-
SUBMODULE_UPDATE_STRATEGY_INIT, 0, 0, -1, STRING_LIST_INIT_DUP, \
755+
SUBMODULE_UPDATE_STRATEGY_INIT, 0, 0, -1, STRING_LIST_INIT_DUP, 0, \
745756
NULL, NULL, NULL, \
746757
STRING_LIST_INIT_DUP, 0, NULL, 0, 0}
747758

@@ -860,6 +871,8 @@ static int prepare_to_clone_next_submodule(const struct cache_entry *ce,
860871
argv_array_pushl(&child->args, "--prefix", suc->prefix, NULL);
861872
if (suc->recommend_shallow && sub->recommend_shallow == 1)
862873
argv_array_push(&child->args, "--depth=1");
874+
if (suc->require_init)
875+
argv_array_push(&child->args, "--require-init");
863876
argv_array_pushl(&child->args, "--path", sub->path, NULL);
864877
argv_array_pushl(&child->args, "--name", sub->name, NULL);
865878
argv_array_pushl(&child->args, "--url", url, NULL);
@@ -1011,6 +1024,8 @@ static int update_clone(int argc, const char **argv, const char *prefix)
10111024
OPT__QUIET(&suc.quiet, N_("don't print cloning progress")),
10121025
OPT_BOOL(0, "progress", &suc.progress,
10131026
N_("force cloning progress")),
1027+
OPT_BOOL(0, "require-init", &suc.require_init,
1028+
N_("disallow cloning into non-empty directory")),
10141029
OPT_END()
10151030
};
10161031

compat/mingw.c

Lines changed: 96 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -333,6 +333,12 @@ int mingw_mkdir(const char *path, int mode)
333333
{
334334
int ret;
335335
wchar_t wpath[MAX_PATH];
336+
337+
if (!is_valid_win32_path(path)) {
338+
errno = EINVAL;
339+
return -1;
340+
}
341+
336342
if (xutftowcs_path(wpath, path) < 0)
337343
return -1;
338344
ret = _wmkdir(wpath);
@@ -345,13 +351,18 @@ int mingw_open (const char *filename, int oflags, ...)
345351
{
346352
va_list args;
347353
unsigned mode;
348-
int fd;
354+
int fd, create = (oflags & (O_CREAT | O_EXCL)) == (O_CREAT | O_EXCL);
349355
wchar_t wfilename[MAX_PATH];
350356

351357
va_start(args, oflags);
352358
mode = va_arg(args, int);
353359
va_end(args);
354360

361+
if (!is_valid_win32_path(filename)) {
362+
errno = create ? EINVAL : ENOENT;
363+
return -1;
364+
}
365+
355366
if (filename && !strcmp(filename, "/dev/null"))
356367
filename = "nul";
357368

@@ -413,6 +424,11 @@ FILE *mingw_fopen (const char *filename, const char *otype)
413424
int hide = needs_hiding(filename);
414425
FILE *file;
415426
wchar_t wfilename[MAX_PATH], wotype[4];
427+
if (!is_valid_win32_path(filename)) {
428+
int create = otype && strchr(otype, 'w');
429+
errno = create ? EINVAL : ENOENT;
430+
return NULL;
431+
}
416432
if (filename && !strcmp(filename, "/dev/null"))
417433
filename = "nul";
418434
if (xutftowcs_path(wfilename, filename) < 0 ||
@@ -435,6 +451,11 @@ FILE *mingw_freopen (const char *filename, const char *otype, FILE *stream)
435451
int hide = needs_hiding(filename);
436452
FILE *file;
437453
wchar_t wfilename[MAX_PATH], wotype[4];
454+
if (!is_valid_win32_path(filename)) {
455+
int create = otype && strchr(otype, 'w');
456+
errno = create ? EINVAL : ENOENT;
457+
return NULL;
458+
}
438459
if (filename && !strcmp(filename, "/dev/null"))
439460
filename = "nul";
440461
if (xutftowcs_path(wfilename, filename) < 0 ||
@@ -872,7 +893,7 @@ static const char *quote_arg(const char *arg)
872893
p++;
873894
len++;
874895
}
875-
if (*p == '"')
896+
if (*p == '"' || !*p)
876897
n += count*2 + 1;
877898
continue;
878899
}
@@ -894,16 +915,19 @@ static const char *quote_arg(const char *arg)
894915
count++;
895916
*d++ = *arg++;
896917
}
897-
if (*arg == '"') {
918+
if (*arg == '"' || !*arg) {
898919
while (count-- > 0)
899920
*d++ = '\\';
921+
/* don't escape the surrounding end quote */
922+
if (!*arg)
923+
break;
900924
*d++ = '\\';
901925
}
902926
}
903927
*d++ = *arg++;
904928
}
905929
*d++ = '"';
906-
*d++ = 0;
930+
*d++ = '\0';
907931
return q;
908932
}
909933

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

1992+
int mingw_has_dos_drive_prefix(const char *path)
1993+
{
1994+
int i;
1995+
1996+
/*
1997+
* Does it start with an ASCII letter (i.e. highest bit not set),
1998+
* followed by a colon?
1999+
*/
2000+
if (!(0x80 & (unsigned char)*path))
2001+
return *path && path[1] == ':' ? 2 : 0;
2002+
2003+
/*
2004+
* While drive letters must be letters of the English alphabet, it is
2005+
* possible to assign virtually _any_ Unicode character via `subst` as
2006+
* a drive letter to "virtual drives". Even `1`, or `ä`. Or fun stuff
2007+
* like this:
2008+
*
2009+
* subst ֍: %USERPROFILE%\Desktop
2010+
*/
2011+
for (i = 1; i < 4 && (0x80 & (unsigned char)path[i]); i++)
2012+
; /* skip first UTF-8 character */
2013+
return path[i] == ':' ? i + 1 : 0;
2014+
}
2015+
19682016
int mingw_skip_dos_drive_prefix(char **path)
19692017
{
19702018
int ret = has_dos_drive_prefix(*path);
@@ -2106,6 +2154,50 @@ static void setup_windows_environment(void)
21062154
setenv("TERM", "cygwin", 1);
21072155
}
21082156

2157+
int is_valid_win32_path(const char *path)
2158+
{
2159+
int preceding_space_or_period = 0, i = 0, periods = 0;
2160+
2161+
if (!protect_ntfs)
2162+
return 1;
2163+
2164+
skip_dos_drive_prefix((char **)&path);
2165+
2166+
for (;;) {
2167+
char c = *(path++);
2168+
switch (c) {
2169+
case '\0':
2170+
case '/': case '\\':
2171+
/* cannot end in ` ` or `.`, except for `.` and `..` */
2172+
if (preceding_space_or_period &&
2173+
(i != periods || periods > 2))
2174+
return 0;
2175+
if (!c)
2176+
return 1;
2177+
2178+
i = periods = preceding_space_or_period = 0;
2179+
continue;
2180+
case '.':
2181+
periods++;
2182+
/* fallthru */
2183+
case ' ':
2184+
preceding_space_or_period = 1;
2185+
i++;
2186+
continue;
2187+
case ':': /* DOS drive prefix was already skipped */
2188+
case '<': case '>': case '"': case '|': case '?': case '*':
2189+
/* illegal character */
2190+
return 0;
2191+
default:
2192+
if (c > '\0' && c < '\x20')
2193+
/* illegal character */
2194+
return 0;
2195+
}
2196+
preceding_space_or_period = 0;
2197+
i++;
2198+
}
2199+
}
2200+
21092201
/*
21102202
* Disable MSVCRT command line wildcard expansion (__getmainargs called from
21112203
* mingw startup code, see init.c in mingw runtime).

compat/mingw.h

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -394,8 +394,8 @@ HANDLE winansi_get_osfhandle(int fd);
394394
* git specific compatibility
395395
*/
396396

397-
#define has_dos_drive_prefix(path) \
398-
(isalpha(*(path)) && (path)[1] == ':' ? 2 : 0)
397+
int mingw_has_dos_drive_prefix(const char *path);
398+
#define has_dos_drive_prefix mingw_has_dos_drive_prefix
399399
int mingw_skip_dos_drive_prefix(char **path);
400400
#define skip_dos_drive_prefix mingw_skip_dos_drive_prefix
401401
static inline int mingw_is_dir_sep(int c)
@@ -428,6 +428,20 @@ int mingw_offset_1st_component(const char *path);
428428
#include <inttypes.h>
429429
#endif
430430

431+
/**
432+
* Verifies that the given path is a valid one on Windows.
433+
*
434+
* In particular, path segments are disallowed which
435+
*
436+
* - end in a period or a space (except the special directories `.` and `..`).
437+
*
438+
* - contain any of the reserved characters, e.g. `:`, `;`, `*`, etc
439+
*
440+
* Returns 1 upon success, otherwise 0.
441+
*/
442+
int is_valid_win32_path(const char *path);
443+
#define is_valid_path(path) is_valid_win32_path(path)
444+
431445
/**
432446
* Converts UTF-8 encoded string to UTF-16LE.
433447
*

config.mak.uname

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -381,7 +381,6 @@ ifeq ($(uname_S),Windows)
381381
EXTLIBS = user32.lib advapi32.lib shell32.lib wininet.lib ws2_32.lib invalidcontinue.obj
382382
PTHREAD_LIBS =
383383
lib =
384-
BASIC_CFLAGS += -DPROTECT_NTFS_DEFAULT=1
385384
ifndef DEBUG
386385
BASIC_CFLAGS += -GL -Os -MD
387386
BASIC_LDFLAGS += -LTCG
@@ -519,7 +518,6 @@ ifneq (,$(findstring MINGW,$(uname_S)))
519518
COMPAT_OBJS += compat/mingw.o compat/winansi.o \
520519
compat/win32/pthread.o compat/win32/syslog.o \
521520
compat/win32/dirent.o
522-
BASIC_CFLAGS += -DPROTECT_NTFS_DEFAULT=1
523521
EXTLIBS += -lws2_32
524522
GITLIBS += git.res
525523
PTHREAD_LIBS =

connect.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -264,7 +264,7 @@ int url_is_local_not_ssh(const char *url)
264264
const char *colon = strchr(url, ':');
265265
const char *slash = strchr(url, '/');
266266
return !colon || (slash && slash < colon) ||
267-
has_dos_drive_prefix(url);
267+
(has_dos_drive_prefix(url) && is_valid_path(url));
268268
}
269269

270270
static const char *prot_name(enum protocol protocol)

0 commit comments

Comments
 (0)