From 91fa31fb6f44919d5dcbaa157cfac9fb49dc44df Mon Sep 17 00:00:00 2001 From: Edward Thomson Date: Tue, 16 Dec 2014 18:53:55 -0600 Subject: Introduce core.protectHFS and core.protectNTFS Validate HFS ignored char ".git" paths when `core.protectHFS` is specified. Validate NTFS invalid ".git" paths when `core.protectNTFS` is specified. --- src/config_cache.c | 2 + src/path.c | 113 +++++++++++++++++++++++++++++++++++-------------- src/path.h | 20 ++++----- src/repository.h | 6 +++ tests/checkout/nasty.c | 32 ++++++++++++++ tests/path/core.c | 22 ++++++++-- 6 files changed, 151 insertions(+), 44 deletions(-) diff --git a/src/config_cache.c b/src/config_cache.c index 45c39ce17..d397a4bab 100644 --- a/src/config_cache.c +++ b/src/config_cache.c @@ -76,6 +76,8 @@ static struct map_data _cvar_maps[] = { {"core.precomposeunicode", NULL, 0, GIT_PRECOMPOSE_DEFAULT }, {"core.safecrlf", _cvar_map_safecrlf, ARRAY_SIZE(_cvar_map_safecrlf), GIT_SAFE_CRLF_DEFAULT}, {"core.logallrefupdates", NULL, 0, GIT_LOGALLREFUPDATES_DEFAULT }, + {"core.protecthfs", NULL, 0, GIT_PROTECTHFS_DEFAULT }, + {"core.protectntfs", NULL, 0, GIT_PROTECTNTFS_DEFAULT }, }; int git_config__cvar(int *out, git_config *config, git_cvar_cached cvar) diff --git a/src/path.c b/src/path.c index b9c9729c1..768a7e188 100644 --- a/src/path.c +++ b/src/path.c @@ -1240,26 +1240,6 @@ int git_path_from_url_or_path(git_buf *local_path_out, const char *url_or_path) return git_buf_sets(local_path_out, url_or_path); } -GIT_INLINE(bool) verify_shortname( - git_repository *repo, - const char *component, - size_t len) -{ - const char *shortname_repo; - - if (len == git_repository__8dot3_default_len && - strncasecmp(git_repository__8dot3_default, component, len) == 0) - return false; - - if (repo && - (shortname_repo = git_repository__8dot3_name(repo)) && - shortname_repo != git_repository__8dot3_default && - git__prefixncmp_icase(component, len, shortname_repo) == 0) - return false; - - return true; -} - /* Reject paths like AUX or COM1, or those versions that end in a dot or * colon. ("AUX." or "AUX:") */ @@ -1335,11 +1315,48 @@ static bool verify_dotgit_hfs(const char *path, size_t len) return false; } +GIT_INLINE(bool) verify_dotgit_ntfs(git_repository *repo, const char *path, size_t len) +{ + const char *shortname = NULL; + size_t i, start, shortname_len = 0; + + /* See if the repo has a custom shortname (not "GIT~1") */ + if (repo && + (shortname = git_repository__8dot3_name(repo)) && + shortname != git_repository__8dot3_default) + shortname_len = strlen(shortname); + + if (len >= 4 && strncasecmp(path, ".git", 4) == 0) + start = 4; + else if (len >= git_repository__8dot3_default_len && + strncasecmp(path, git_repository__8dot3_default, git_repository__8dot3_default_len) == 0) + start = git_repository__8dot3_default_len; + else if (shortname_len && len >= shortname_len && + strncasecmp(path, shortname, shortname_len) == 0) + start = shortname_len; + else + return true; + + /* Reject paths beginning with ".git\" */ + if (path[start] == '\\') + return false; + + for (i = start; i < len; i++) { + if (path[i] != ' ' && path[i] != '.') + return true; + } + + return false; +} + GIT_INLINE(bool) verify_char(unsigned char c, unsigned int flags) { if ((flags & GIT_PATH_REJECT_BACKSLASH) && c == '\\') return false; + if ((flags & GIT_PATH_REJECT_SLASH) && c == '/') + return false; + if (flags & GIT_PATH_REJECT_NT_CHARS) { if (c < 32) return false; @@ -1385,13 +1402,6 @@ static bool verify_component( len == 2 && component[0] == '.' && component[1] == '.') return false; - if ((flags & GIT_PATH_REJECT_DOT_GIT) && len == 4 && - component[0] == '.' && - (component[1] == 'g' || component[1] == 'G') && - (component[2] == 'i' || component[2] == 'I') && - (component[3] == 't' || component[3] == 'T')) - return false; - if ((flags & GIT_PATH_REJECT_TRAILING_DOT) && component[len-1] == '.') return false; @@ -1401,10 +1411,6 @@ static bool verify_component( if ((flags & GIT_PATH_REJECT_TRAILING_COLON) && component[len-1] == ':') return false; - if ((flags & GIT_PATH_REJECT_DOS_GIT_SHORTNAME) && - !verify_shortname(repo, component, len)) - return false; - if (flags & GIT_PATH_REJECT_DOS_PATHS) { if (!verify_dospath(component, len, "CON", false) || !verify_dospath(component, len, "PRN", false) || @@ -1419,9 +1425,50 @@ static bool verify_component( !verify_dotgit_hfs(component, len)) return false; + if (flags & GIT_PATH_REJECT_DOT_GIT_NTFS && + !verify_dotgit_ntfs(repo, component, len)) + return false; + + if ((flags & GIT_PATH_REJECT_DOT_GIT_HFS) == 0 && + (flags & GIT_PATH_REJECT_DOT_GIT_NTFS) == 0 && + (flags & GIT_PATH_REJECT_DOT_GIT) && + len == 4 && + component[0] == '.' && + (component[1] == 'g' || component[1] == 'G') && + (component[2] == 'i' || component[2] == 'I') && + (component[3] == 't' || component[3] == 'T')) + return false; + return true; } +GIT_INLINE(unsigned int) dotgit_flags( + git_repository *repo, + unsigned int flags) +{ + int protectHFS = 0, protectNTFS = 0; + +#ifdef __APPLE__ + protectHFS = 1; +#endif + +#ifdef GIT_WIN32 + protectNTFS = 1; +#endif + + if (repo && !protectHFS) + git_repository__cvar(&protectHFS, repo, GIT_CVAR_PROTECTHFS); + if (protectHFS) + flags |= GIT_PATH_REJECT_DOT_GIT_HFS; + + if (repo && !protectNTFS) + git_repository__cvar(&protectNTFS, repo, GIT_CVAR_PROTECTNTFS); + if (protectNTFS) + flags |= GIT_PATH_REJECT_DOT_GIT_NTFS; + + return flags; +} + bool git_path_isvalid( git_repository *repo, const char *path, @@ -1429,6 +1476,10 @@ bool git_path_isvalid( { const char *start, *c; + /* Upgrade the ".git" checks based on platform */ + if ((flags & GIT_PATH_REJECT_DOT_GIT)) + flags = dotgit_flags(repo, flags); + for (start = c = path; *c; c++) { if (!verify_char(*c, flags)) return false; diff --git a/src/path.h b/src/path.h index 752f59bd3..c32f2173c 100644 --- a/src/path.h +++ b/src/path.h @@ -465,15 +465,20 @@ extern int git_path_from_url_or_path(git_buf *local_path_out, const char *url_or /* Flags to determine path validity in `git_path_isvalid` */ #define GIT_PATH_REJECT_TRAVERSAL (1 << 0) #define GIT_PATH_REJECT_DOT_GIT (1 << 1) -#define GIT_PATH_REJECT_BACKSLASH (1 << 2) -#define GIT_PATH_REJECT_TRAILING_DOT (1 << 3) -#define GIT_PATH_REJECT_TRAILING_SPACE (1 << 4) -#define GIT_PATH_REJECT_TRAILING_COLON (1 << 5) -#define GIT_PATH_REJECT_DOS_GIT_SHORTNAME (1 << 6) +#define GIT_PATH_REJECT_SLASH (1 << 2) +#define GIT_PATH_REJECT_BACKSLASH (1 << 3) +#define GIT_PATH_REJECT_TRAILING_DOT (1 << 4) +#define GIT_PATH_REJECT_TRAILING_SPACE (1 << 5) +#define GIT_PATH_REJECT_TRAILING_COLON (1 << 6) #define GIT_PATH_REJECT_DOS_PATHS (1 << 7) #define GIT_PATH_REJECT_NT_CHARS (1 << 8) #define GIT_PATH_REJECT_DOT_GIT_HFS (1 << 9) +#define GIT_PATH_REJECT_DOT_GIT_NTFS (1 << 10) +/* Default path safety for writing files to disk: since we use the + * Win32 "File Namespace" APIs ("\\?\") we need to protect from + * paths that the normal Win32 APIs would not write. + */ #ifdef GIT_WIN32 # define GIT_PATH_REJECT_DEFAULTS \ GIT_PATH_REJECT_TRAVERSAL | \ @@ -481,13 +486,8 @@ extern int git_path_from_url_or_path(git_buf *local_path_out, const char *url_or GIT_PATH_REJECT_TRAILING_DOT | \ GIT_PATH_REJECT_TRAILING_SPACE | \ GIT_PATH_REJECT_TRAILING_COLON | \ - GIT_PATH_REJECT_DOS_GIT_SHORTNAME | \ GIT_PATH_REJECT_DOS_PATHS | \ GIT_PATH_REJECT_NT_CHARS -#elif __APPLE__ -# define GIT_PATH_REJECT_DEFAULTS \ - GIT_PATH_REJECT_TRAVERSAL | \ - GIT_PATH_REJECT_DOT_GIT_HFS #else # define GIT_PATH_REJECT_DEFAULTS GIT_PATH_REJECT_TRAVERSAL #endif diff --git a/src/repository.h b/src/repository.h index 4b73b68cb..e38292a7c 100644 --- a/src/repository.h +++ b/src/repository.h @@ -40,6 +40,8 @@ typedef enum { GIT_CVAR_PRECOMPOSE, /* core.precomposeunicode */ GIT_CVAR_SAFE_CRLF, /* core.safecrlf */ GIT_CVAR_LOGALLREFUPDATES, /* core.logallrefupdates */ + GIT_CVAR_PROTECTHFS, /* core.protectHFS */ + GIT_CVAR_PROTECTNTFS, /* core.protectNTFS */ GIT_CVAR_CACHE_MAX } git_cvar_cached; @@ -96,6 +98,10 @@ typedef enum { /* core.logallrefupdates */ GIT_LOGALLREFUPDATES_UNSET = 2, GIT_LOGALLREFUPDATES_DEFAULT = GIT_LOGALLREFUPDATES_UNSET, + /* core.protectHFS */ + GIT_PROTECTHFS_DEFAULT = GIT_CVAR_FALSE, + /* core.protectNTFS */ + GIT_PROTECTNTFS_DEFAULT = GIT_CVAR_FALSE, } git_cvar_value; /* internal repository init flags */ diff --git a/tests/checkout/nasty.c b/tests/checkout/nasty.c index a667dcd8f..c07d9382a 100644 --- a/tests/checkout/nasty.c +++ b/tests/checkout/nasty.c @@ -291,3 +291,35 @@ void test_checkout_nasty__dot_git_hfs_ignorable(void) test_checkout_fails("refs/heads/dotgit_hfs_ignorable_16", ".git/foobar"); #endif } + +void test_checkout_nasty__honors_core_protecthfs(void) +{ + cl_repo_set_bool(repo, "core.protectHFS", true); + + 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"); +} + +void test_checkout_nasty__honors_core_protectntfs(void) +{ + cl_repo_set_bool(repo, "core.protectNTFS", true); + + test_checkout_fails("refs/heads/dotgit_backslash_path", ".git/foobar"); + test_checkout_fails("refs/heads/dotcapitalgit_backslash_path", ".GIT/foobar"); + test_checkout_fails("refs/heads/dot_git_dot", ".git/foobar"); + test_checkout_fails("refs/heads/git_tilde1", ".git/foobar"); +} diff --git a/tests/path/core.c b/tests/path/core.c index 528108bea..85fee820a 100644 --- a/tests/path/core.c +++ b/tests/path/core.c @@ -172,11 +172,27 @@ void test_path_core__isvalid_trailing_colon(void) cl_assert_equal_b(false, git_path_isvalid(NULL, "foo:/bar", GIT_PATH_REJECT_TRAILING_COLON)); } -void test_path_core__isvalid_dos_git_shortname(void) +void test_path_core__isvalid_dotgit_ntfs(void) { - cl_assert_equal_b(true, git_path_isvalid(NULL, "git~1", 0)); + cl_assert_equal_b(true, git_path_isvalid(NULL, ".git", 0)); + cl_assert_equal_b(true, git_path_isvalid(NULL, ".git ", 0)); + cl_assert_equal_b(true, git_path_isvalid(NULL, ".git.", 0)); + cl_assert_equal_b(true, git_path_isvalid(NULL, ".git.. .", 0)); - cl_assert_equal_b(false, git_path_isvalid(NULL, "git~1", GIT_PATH_REJECT_DOS_GIT_SHORTNAME)); + cl_assert_equal_b(true, git_path_isvalid(NULL, "git~1", 0)); + cl_assert_equal_b(true, git_path_isvalid(NULL, "git~1 ", 0)); + cl_assert_equal_b(true, git_path_isvalid(NULL, "git~1.", 0)); + cl_assert_equal_b(true, git_path_isvalid(NULL, "git~1.. .", 0)); + + cl_assert_equal_b(false, git_path_isvalid(NULL, ".git", GIT_PATH_REJECT_DOT_GIT_NTFS)); + cl_assert_equal_b(false, git_path_isvalid(NULL, ".git ", GIT_PATH_REJECT_DOT_GIT_NTFS)); + cl_assert_equal_b(false, git_path_isvalid(NULL, ".git.", GIT_PATH_REJECT_DOT_GIT_NTFS)); + cl_assert_equal_b(false, git_path_isvalid(NULL, ".git.. .", GIT_PATH_REJECT_DOT_GIT_NTFS)); + + cl_assert_equal_b(false, git_path_isvalid(NULL, "git~1", GIT_PATH_REJECT_DOT_GIT_NTFS)); + cl_assert_equal_b(false, git_path_isvalid(NULL, "git~1 ", GIT_PATH_REJECT_DOT_GIT_NTFS)); + cl_assert_equal_b(false, git_path_isvalid(NULL, "git~1.", GIT_PATH_REJECT_DOT_GIT_NTFS)); + cl_assert_equal_b(false, git_path_isvalid(NULL, "git~1.. .", GIT_PATH_REJECT_DOT_GIT_NTFS)); } void test_path_core__isvalid_dos_paths(void) -- cgit v1.2.1