Commit 11d67b75 by Edward Thomson

checkout: disallow bad paths on HFS

HFS filesystems ignore some characters like U+200C.  When these
characters are included in a path, they will be ignored for the
purposes of comparison with other paths.  Thus, if you have a ".git"
folder, a folder of ".git<U+200C>" will also match.  Protect our
".git" folder by ensuring that ".git<U+200C>" and friends do not match it.
parent ee5da720
...@@ -1282,6 +1282,95 @@ GIT_INLINE(bool) verify_dospath( ...@@ -1282,6 +1282,95 @@ GIT_INLINE(bool) verify_dospath(
component[last] != ':'); component[last] != ':');
} }
GIT_INLINE(bool) verify_dotgit_hfs(const char *component, size_t len)
{
const unsigned char *c;
int git = 0, ign = 0;
unsigned char one, two;
while (len) {
switch (*(c = (const unsigned char *)component++)) {
case '.':
if (ign || git++ != 0)
return true;
break;
case 'g':
case 'G':
if (ign || git++ != 1)
return true;
break;
case 'i':
case 'I':
if (ign || git++ != 2)
return true;
break;
case 't':
case 'T':
if (ign || git++ != 3)
return true;
break;
case 0xe2:
case 0xef:
if (ign++ != 0)
return true;
one = *c;
break;
case 0x80:
case 0x81:
if (ign++ != 1 || one != 0xe2)
return true;
two = *c;
break;
case 0xbb:
if (ign++ != 1 || one != 0xef)
return true;
two = *c;
break;
case 0x8c:
case 0x8d:
case 0x8e:
case 0x8f:
if (ign != 2 || two != 0x80)
return true;
ign = 0;
break;
case 0xaa:
case 0xab:
case 0xac:
case 0xad:
case 0xae:
if (ign != 2 || (two != 0x80 && two != 0x81))
return true;
ign = 0;
break;
case 0xaf:
if (ign != 2 || two != 0x81)
return true;
ign = 0;
break;
case 0xbf:
if (ign != 2 || two != 0xbb)
return true;
ign = 0;
break;
default:
return true;
}
len--;
}
return (ign || git != 4);
}
GIT_INLINE(bool) verify_char(unsigned char c, unsigned int flags) GIT_INLINE(bool) verify_char(unsigned char c, unsigned int flags)
{ {
if ((flags & GIT_PATH_REJECT_BACKSLASH) && c == '\\') if ((flags & GIT_PATH_REJECT_BACKSLASH) && c == '\\')
...@@ -1362,6 +1451,10 @@ static bool verify_component( ...@@ -1362,6 +1451,10 @@ static bool verify_component(
return false; return false;
} }
if (flags & GIT_PATH_REJECT_DOT_GIT_HFS &&
!verify_dotgit_hfs(component, len))
return false;
return true; return true;
} }
......
...@@ -472,6 +472,7 @@ extern int git_path_from_url_or_path(git_buf *local_path_out, const char *url_or ...@@ -472,6 +472,7 @@ extern int git_path_from_url_or_path(git_buf *local_path_out, const char *url_or
#define GIT_PATH_REJECT_DOS_GIT_SHORTNAME (1 << 6) #define GIT_PATH_REJECT_DOS_GIT_SHORTNAME (1 << 6)
#define GIT_PATH_REJECT_DOS_PATHS (1 << 7) #define GIT_PATH_REJECT_DOS_PATHS (1 << 7)
#define GIT_PATH_REJECT_NT_CHARS (1 << 8) #define GIT_PATH_REJECT_NT_CHARS (1 << 8)
#define GIT_PATH_REJECT_DOT_GIT_HFS (1 << 9)
#ifdef GIT_WIN32 #ifdef GIT_WIN32
# define GIT_PATH_REJECT_DEFAULTS \ # define GIT_PATH_REJECT_DEFAULTS \
...@@ -483,6 +484,10 @@ extern int git_path_from_url_or_path(git_buf *local_path_out, const char *url_or ...@@ -483,6 +484,10 @@ extern int git_path_from_url_or_path(git_buf *local_path_out, const char *url_or
GIT_PATH_REJECT_DOS_GIT_SHORTNAME | \ GIT_PATH_REJECT_DOS_GIT_SHORTNAME | \
GIT_PATH_REJECT_DOS_PATHS | \ GIT_PATH_REJECT_DOS_PATHS | \
GIT_PATH_REJECT_NT_CHARS GIT_PATH_REJECT_NT_CHARS
#elif __APPLE__
# define GIT_PATH_REJECT_DEFAULTS \
GIT_PATH_REJECT_TRAVERSAL | \
GIT_PATH_REJECT_DOT_GIT_HFS
#else #else
# define GIT_PATH_REJECT_DEFAULTS GIT_PATH_REJECT_TRAVERSAL # define GIT_PATH_REJECT_DEFAULTS GIT_PATH_REJECT_TRAVERSAL
#endif #endif
......
...@@ -249,3 +249,45 @@ void test_checkout_nasty__dot_git_colon_stuff(void) ...@@ -249,3 +249,45 @@ void test_checkout_nasty__dot_git_colon_stuff(void)
#endif #endif
} }
/* Trees that contains entries with a tree ".git" that contain
* byte sequences:
* { 0xe2, 0x80, 0x8c }
* { 0xe2, 0x80, 0x8d }
* { 0xe2, 0x80, 0x8e }
* { 0xe2, 0x80, 0x8f }
* { 0xe2, 0x80, 0xaa }
* { 0xe2, 0x80, 0xab }
* { 0xe2, 0x80, 0xac }
* { 0xe2, 0x80, 0xad }
* { 0xe2, 0x81, 0xae }
* { 0xe2, 0x81, 0xaa }
* { 0xe2, 0x81, 0xab }
* { 0xe2, 0x81, 0xac }
* { 0xe2, 0x81, 0xad }
* { 0xe2, 0x81, 0xae }
* { 0xe2, 0x81, 0xaf }
* { 0xef, 0xbb, 0xbf }
* Because these map to characters that HFS filesystems "ignore". Thus
* ".git<U+200C>" will map to ".git".
*/
void test_checkout_nasty__dot_git_hfs_ignorable(void)
{
#ifdef __APPLE__
test_checkout_fails("refs/heads/dotgit_hfs_ignorable_1", ".git/foobar");
test_checkout_fails("refs/heads/dotgit_hfs_ignorable_2", ".git/foobar");
test_checkout_fails("refs/heads/dotgit_hfs_ignorable_3", ".git/foobar");
test_checkout_fails("refs/heads/dotgit_hfs_ignorable_4", ".git/foobar");
test_checkout_fails("refs/heads/dotgit_hfs_ignorable_5", ".git/foobar");
test_checkout_fails("refs/heads/dotgit_hfs_ignorable_6", ".git/foobar");
test_checkout_fails("refs/heads/dotgit_hfs_ignorable_7", ".git/foobar");
test_checkout_fails("refs/heads/dotgit_hfs_ignorable_8", ".git/foobar");
test_checkout_fails("refs/heads/dotgit_hfs_ignorable_9", ".git/foobar");
test_checkout_fails("refs/heads/dotgit_hfs_ignorable_10", ".git/foobar");
test_checkout_fails("refs/heads/dotgit_hfs_ignorable_11", ".git/foobar");
test_checkout_fails("refs/heads/dotgit_hfs_ignorable_12", ".git/foobar");
test_checkout_fails("refs/heads/dotgit_hfs_ignorable_13", ".git/foobar");
test_checkout_fails("refs/heads/dotgit_hfs_ignorable_14", ".git/foobar");
test_checkout_fails("refs/heads/dotgit_hfs_ignorable_15", ".git/foobar");
test_checkout_fails("refs/heads/dotgit_hfs_ignorable_16", ".git/foobar");
#endif
}
...@@ -223,7 +223,7 @@ void test_path_core__isvalid_dos_paths_withnum(void) ...@@ -223,7 +223,7 @@ void test_path_core__isvalid_dos_paths_withnum(void)
cl_assert_equal_b(true, git_path_isvalid(NULL, "com1\\foo", GIT_PATH_REJECT_DOS_PATHS)); cl_assert_equal_b(true, git_path_isvalid(NULL, "com1\\foo", GIT_PATH_REJECT_DOS_PATHS));
} }
void test_core_path__isvalid_nt_chars(void) void test_path_core__isvalid_nt_chars(void)
{ {
cl_assert_equal_b(true, git_path_isvalid(NULL, "asdf\001foo", 0)); cl_assert_equal_b(true, git_path_isvalid(NULL, "asdf\001foo", 0));
cl_assert_equal_b(true, git_path_isvalid(NULL, "asdf\037bar", 0)); cl_assert_equal_b(true, git_path_isvalid(NULL, "asdf\037bar", 0));
...@@ -245,3 +245,32 @@ void test_core_path__isvalid_nt_chars(void) ...@@ -245,3 +245,32 @@ void test_core_path__isvalid_nt_chars(void)
cl_assert_equal_b(false, git_path_isvalid(NULL, "asdf?bar", GIT_PATH_REJECT_NT_CHARS)); cl_assert_equal_b(false, git_path_isvalid(NULL, "asdf?bar", GIT_PATH_REJECT_NT_CHARS));
cl_assert_equal_b(false, git_path_isvalid(NULL, "asdf*bar", GIT_PATH_REJECT_NT_CHARS)); cl_assert_equal_b(false, git_path_isvalid(NULL, "asdf*bar", GIT_PATH_REJECT_NT_CHARS));
} }
void test_path_core__isvalid_dotgit_with_hfs_ignorables(void)
{
cl_assert_equal_b(false, git_path_isvalid(NULL, ".git", GIT_PATH_REJECT_DOT_GIT_HFS));
cl_assert_equal_b(false, git_path_isvalid(NULL, ".git\xe2\x80\x8c", GIT_PATH_REJECT_DOT_GIT_HFS));
cl_assert_equal_b(false, git_path_isvalid(NULL, ".gi\xe2\x80\x8dT", GIT_PATH_REJECT_DOT_GIT_HFS));
cl_assert_equal_b(false, git_path_isvalid(NULL, ".g\xe2\x80\x8eIt", GIT_PATH_REJECT_DOT_GIT_HFS));
cl_assert_equal_b(false, git_path_isvalid(NULL, ".\xe2\x80\x8fgIt", GIT_PATH_REJECT_DOT_GIT_HFS));
cl_assert_equal_b(false, git_path_isvalid(NULL, "\xe2\x80\xaa.gIt", GIT_PATH_REJECT_DOT_GIT_HFS));
cl_assert_equal_b(false, git_path_isvalid(NULL, "\xe2\x80\xab.\xe2\x80\xacG\xe2\x80\xadI\xe2\x80\xaet", GIT_PATH_REJECT_DOT_GIT_HFS));
cl_assert_equal_b(false, git_path_isvalid(NULL, "\xe2\x81\xab.\xe2\x80\xaaG\xe2\x81\xabI\xe2\x80\xact", GIT_PATH_REJECT_DOT_GIT_HFS));
cl_assert_equal_b(false, git_path_isvalid(NULL, "\xe2\x81\xad.\xe2\x80\xaeG\xef\xbb\xbfIT", GIT_PATH_REJECT_DOT_GIT_HFS));
cl_assert_equal_b(true, git_path_isvalid(NULL, ".", GIT_PATH_REJECT_DOT_GIT_HFS));
cl_assert_equal_b(true, git_path_isvalid(NULL, ".g", GIT_PATH_REJECT_DOT_GIT_HFS));
cl_assert_equal_b(true, git_path_isvalid(NULL, ".gi", GIT_PATH_REJECT_DOT_GIT_HFS));
cl_assert_equal_b(true, git_path_isvalid(NULL, " .git", GIT_PATH_REJECT_DOT_GIT_HFS));
cl_assert_equal_b(true, git_path_isvalid(NULL, "..git\xe2\x80\x8c", GIT_PATH_REJECT_DOT_GIT_HFS));
cl_assert_equal_b(true, git_path_isvalid(NULL, ".gi\xe2\x80\x8dT.", GIT_PATH_REJECT_DOT_GIT_HFS));
cl_assert_equal_b(true, git_path_isvalid(NULL, ".g\xe2\x80It", GIT_PATH_REJECT_DOT_GIT_HFS));
cl_assert_equal_b(true, git_path_isvalid(NULL, ".\xe2gIt", GIT_PATH_REJECT_DOT_GIT_HFS));
cl_assert_equal_b(true, git_path_isvalid(NULL, "\xe2\x80\xaa.gi", GIT_PATH_REJECT_DOT_GIT_HFS));
cl_assert_equal_b(true, git_path_isvalid(NULL, ".gi\x80\x8dT", GIT_PATH_REJECT_DOT_GIT_HFS));
cl_assert_equal_b(true, git_path_isvalid(NULL, ".gi\x8dT", GIT_PATH_REJECT_DOT_GIT_HFS));
cl_assert_equal_b(true, git_path_isvalid(NULL, ".g\xe2i\x80T\x8e", GIT_PATH_REJECT_DOT_GIT_HFS));
cl_assert_equal_b(true, git_path_isvalid(NULL, ".git\xe2\x80\xbf", GIT_PATH_REJECT_DOT_GIT_HFS));
cl_assert_equal_b(true, git_path_isvalid(NULL, ".git\xe2\xab\x81", GIT_PATH_REJECT_DOT_GIT_HFS));
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment