Skip to content

Commit 6777db8

Browse files
authored
Merge pull request libgit2#5331 from pks-t/security-fixes
Security fixes for master
2 parents 6bd37c3 + b8b8eee commit 6777db8

File tree

13 files changed

+254
-51
lines changed

13 files changed

+254
-51
lines changed

docs/changelog.md

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,63 @@
11
v0.28 + 1
22
---------
33

4+
# Security Fixes
5+
6+
- CVE-2019-1348: the fast-import stream command "feature
7+
export-marks=path" allows writing to arbitrary file paths. As
8+
libgit2 does not offer any interface for fast-import, it is not
9+
susceptible to this vulnerability.
10+
11+
- CVE-2019-1349: by using NTFS 8.3 short names, backslashes or
12+
alternate filesystreams, it is possible to cause submodules to
13+
be written into pre-existing directories during a recursive
14+
clone using git. As libgit2 rejects cloning into non-empty
15+
directories by default, it is not susceptible to this
16+
vulnerability.
17+
18+
- CVE-2019-1350: recursive clones may lead to arbitrary remote
19+
code executing due to improper quoting of command line
20+
arguments. As libgit2 uses libssh2, which does not require us
21+
to perform command line parsing, it is not susceptible to this
22+
vulnerability.
23+
24+
- CVE-2019-1351: Windows provides the ability to substitute
25+
drive letters with arbitrary letters, including multi-byte
26+
Unicode letters. To fix any potential issues arising from
27+
interpreting such paths as relative paths, we have extended
28+
detection of DOS drive prefixes to accomodate for such cases.
29+
30+
- CVE-2019-1352: by using NTFS-style alternative file streams for
31+
the ".git" directory, it is possible to overwrite parts of the
32+
repository. While this has been fixed in the past for Windows,
33+
the same vulnerability may also exist on other systems that
34+
write to NTFS filesystems. We now reject any paths starting
35+
with ".git:" on all systems.
36+
37+
- CVE-2019-1353: by using NTFS-style 8.3 short names, it was
38+
possible to write to the ".git" directory and thus overwrite
39+
parts of the repository, leading to possible remote code
40+
execution. While this problem was already fixed in the past for
41+
Windows, other systems accessing NTFS filesystems are
42+
vulnerable to this issue too. We now enable NTFS protecions by
43+
default on all systems to fix this attack vector.
44+
45+
- CVE-2019-1354: on Windows, backslashes are not a valid part of
46+
a filename but are instead interpreted as directory separators.
47+
As other platforms allowed to use such paths, it was possible
48+
to write such invalid entries into a Git repository and was
49+
thus an attack vector to write into the ".git" dierctory. We
50+
now reject any entries starting with ".git\" on all systems.
51+
52+
- CVE-2019-1387: it is possible to let a submodule's git
53+
directory point into a sibling's submodule directory, which may
54+
result in overwriting parts of the Git repository and thus lead
55+
to arbitrary command execution. As libgit2 doesn't provide any
56+
way to do submodule clones natively, it is not susceptible to
57+
this vulnerability. Users of libgit2 that have implemented
58+
recursive submodule clones manually are encouraged to review
59+
their implementation for this vulnerability.
60+
461
### Breaking API changes
562

663
* The "private" implementation details of the `git_cred` structure have been

src/path.c

Lines changed: 52 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,29 @@
2121
#include <stdio.h>
2222
#include <ctype.h>
2323

24-
#define LOOKS_LIKE_DRIVE_PREFIX(S) (git__isalpha((S)[0]) && (S)[1] == ':')
24+
static int dos_drive_prefix_length(const char *path)
25+
{
26+
int i;
27+
28+
/*
29+
* Does it start with an ASCII letter (i.e. highest bit not set),
30+
* followed by a colon?
31+
*/
32+
if (!(0x80 & (unsigned char)*path))
33+
return *path && path[1] == ':' ? 2 : 0;
34+
35+
/*
36+
* While drive letters must be letters of the English alphabet, it is
37+
* possible to assign virtually _any_ Unicode character via `subst` as
38+
* a drive letter to "virtual drives". Even `1`, or `ä`. Or fun stuff
39+
* like this:
40+
*
41+
* subst ֍: %USERPROFILE%\Desktop
42+
*/
43+
for (i = 1; i < 4 && (0x80 & (unsigned char)path[i]); i++)
44+
; /* skip first UTF-8 character */
45+
return path[i] == ':' ? i + 1 : 0;
46+
}
2547

2648
#ifdef GIT_WIN32
2749
static bool looks_like_network_computer_name(const char *path, int pos)
@@ -123,11 +145,11 @@ static int win32_prefix_length(const char *path, int len)
123145
GIT_UNUSED(len);
124146
#else
125147
/*
126-
* Mimic unix behavior where '/.git' returns '/': 'C:/.git' will return
127-
* 'C:/' here
148+
* Mimic unix behavior where '/.git' returns '/': 'C:/.git'
149+
* will return 'C:/' here
128150
*/
129-
if (len == 2 && LOOKS_LIKE_DRIVE_PREFIX(path))
130-
return 2;
151+
if (dos_drive_prefix_length(path) == len)
152+
return len;
131153

132154
/*
133155
* Similarly checks if we're dealing with a network computer name
@@ -272,11 +294,11 @@ const char *git_path_topdir(const char *path)
272294

273295
int git_path_root(const char *path)
274296
{
275-
int offset = 0;
297+
int offset = 0, prefix_len;
276298

277299
/* Does the root of the path look like a windows drive ? */
278-
if (LOOKS_LIKE_DRIVE_PREFIX(path))
279-
offset += 2;
300+
if ((prefix_len = dos_drive_prefix_length(path)))
301+
offset += prefix_len;
280302

281303
#ifdef GIT_WIN32
282304
/* Are we dealing with a windows network path? */
@@ -1624,8 +1646,12 @@ GIT_INLINE(bool) verify_dotgit_ntfs(git_repository *repo, const char *path, size
16241646
if (!start)
16251647
return true;
16261648

1627-
/* Reject paths like ".git\" */
1628-
if (path[start] == '\\')
1649+
/*
1650+
* Reject paths that start with Windows-style directory separators
1651+
* (".git\") or NTFS alternate streams (".git:") and could be used
1652+
* to write to the ".git" directory on Windows platforms.
1653+
*/
1654+
if (path[start] == '\\' || path[start] == ':')
16291655
return false;
16301656

16311657
/* Reject paths like '.git ' or '.git.' */
@@ -1637,12 +1663,21 @@ GIT_INLINE(bool) verify_dotgit_ntfs(git_repository *repo, const char *path, size
16371663
return false;
16381664
}
16391665

1640-
GIT_INLINE(bool) only_spaces_and_dots(const char *path)
1666+
/*
1667+
* Windows paths that end with spaces and/or dots are elided to the
1668+
* path without them for backward compatibility. That is to say
1669+
* that opening file "foo ", "foo." or even "foo . . ." will all
1670+
* map to a filename of "foo". This function identifies spaces and
1671+
* dots at the end of a filename, whether the proper end of the
1672+
* filename (end of string) or a colon (which would indicate a
1673+
* Windows alternate data stream.)
1674+
*/
1675+
GIT_INLINE(bool) ntfs_end_of_filename(const char *path)
16411676
{
16421677
const char *c = path;
16431678

16441679
for (;; c++) {
1645-
if (*c == '\0')
1680+
if (*c == '\0' || *c == ':')
16461681
return true;
16471682
if (*c != ' ' && *c != '.')
16481683
return false;
@@ -1657,13 +1692,13 @@ GIT_INLINE(bool) verify_dotgit_ntfs_generic(const char *name, size_t len, const
16571692

16581693
if (name[0] == '.' && len >= dotgit_len &&
16591694
!strncasecmp(name + 1, dotgit_name, dotgit_len)) {
1660-
return !only_spaces_and_dots(name + dotgit_len + 1);
1695+
return !ntfs_end_of_filename(name + dotgit_len + 1);
16611696
}
16621697

16631698
/* Detect the basic NTFS shortname with the first six chars */
16641699
if (!strncasecmp(name, dotgit_name, 6) && name[6] == '~' &&
16651700
name[7] >= '1' && name[7] <= '4')
1666-
return !only_spaces_and_dots(name + 8);
1701+
return !ntfs_end_of_filename(name + 8);
16671702

16681703
/* Catch fallback names */
16691704
for (i = 0, saw_tilde = 0; i < 8; i++) {
@@ -1685,7 +1720,7 @@ GIT_INLINE(bool) verify_dotgit_ntfs_generic(const char *name, size_t len, const
16851720
}
16861721
}
16871722

1688-
return !only_spaces_and_dots(name + i);
1723+
return !ntfs_end_of_filename(name + i);
16891724
}
16901725

16911726
GIT_INLINE(bool) verify_char(unsigned char c, unsigned int flags)
@@ -1819,7 +1854,7 @@ GIT_INLINE(unsigned int) dotgit_flags(
18191854
git_repository *repo,
18201855
unsigned int flags)
18211856
{
1822-
int protectHFS = 0, protectNTFS = 0;
1857+
int protectHFS = 0, protectNTFS = 1;
18231858
int error = 0;
18241859

18251860
flags |= GIT_PATH_REJECT_DOT_GIT_LITERAL;
@@ -1828,16 +1863,12 @@ GIT_INLINE(unsigned int) dotgit_flags(
18281863
protectHFS = 1;
18291864
#endif
18301865

1831-
#ifdef GIT_WIN32
1832-
protectNTFS = 1;
1833-
#endif
1834-
18351866
if (repo && !protectHFS)
18361867
error = git_repository__configmap_lookup(&protectHFS, repo, GIT_CONFIGMAP_PROTECTHFS);
18371868
if (!error && protectHFS)
18381869
flags |= GIT_PATH_REJECT_DOT_GIT_HFS;
18391870

1840-
if (repo && !protectNTFS)
1871+
if (repo)
18411872
error = git_repository__configmap_lookup(&protectNTFS, repo, GIT_CONFIGMAP_PROTECTNTFS);
18421873
if (!error && protectNTFS)
18431874
flags |= GIT_PATH_REJECT_DOT_GIT_NTFS;

src/repository.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -113,7 +113,7 @@ typedef enum {
113113
/* core.protectHFS */
114114
GIT_PROTECTHFS_DEFAULT = GIT_CONFIGMAP_FALSE,
115115
/* core.protectNTFS */
116-
GIT_PROTECTNTFS_DEFAULT = GIT_CONFIGMAP_FALSE,
116+
GIT_PROTECTNTFS_DEFAULT = GIT_CONFIGMAP_TRUE,
117117
/* core.fsyncObjectFiles */
118118
GIT_FSYNCOBJECTFILES_DEFAULT = GIT_CONFIGMAP_FALSE,
119119
} git_configmap_value;

tests/checkout/nasty.c

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -206,9 +206,8 @@ void test_checkout_nasty__dot_git_dot(void)
206206
*/
207207
void test_checkout_nasty__git_tilde1(void)
208208
{
209-
#ifdef GIT_WIN32
210209
test_checkout_fails("refs/heads/git_tilde1", ".git/foobar");
211-
#endif
210+
test_checkout_fails("refs/heads/git_tilde1", "git~1/foobar");
212211
}
213212

214213
/* A tree that contains an entry "git~2", when we have forced the short
@@ -274,6 +273,16 @@ void test_checkout_nasty__dot_git_colon_stuff(void)
274273
#endif
275274
}
276275

276+
/* A tree that contains an entry ".git::$INDEX_ALLOCATION" because NTFS
277+
* will interpret that as a synonym to ".git", even when mounted via SMB
278+
* on macOS.
279+
*/
280+
void test_checkout_nasty__dotgit_alternate_data_stream(void)
281+
{
282+
test_checkout_fails("refs/heads/dotgit_alternate_data_stream", ".git/dummy-file");
283+
test_checkout_fails("refs/heads/dotgit_alternate_data_stream", ".git::$INDEX_ALLOCATION/dummy-file");
284+
}
285+
277286
/* Trees that contains entries with a tree ".git" that contain
278287
* byte sequences:
279288
* { 0xe2, 0x80, 0x8c }

tests/clar_libgit2.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,8 +29,8 @@
2929
* calls that are supposed to fail!
3030
*/
3131
#define cl_git_fail(expr) do { \
32-
git_error_clear(); \
3332
if ((expr) == 0) \
33+
git_error_clear(), \
3434
cl_git_report_failure(0, 0, __FILE__, __LINE__, "Function call succeeded: " #expr); \
3535
} while (0)
3636

0 commit comments

Comments
 (0)