diff options
author | Edward Thomson <ethomson@github.com> | 2016-02-16 17:11:46 +0000 |
---|---|---|
committer | Edward Thomson <ethomson@github.com> | 2016-02-17 13:10:33 +0000 |
commit | 318b825e76a9dc8afefc8274c5271747ad64d5a9 (patch) | |
tree | 3d800c50d67c7ed818060da8d155f1c006b62f00 | |
parent | 4fea9cffbda89562bfdcdd58b0162d31ca4feb3e (diff) | |
download | libgit2-318b825e76a9dc8afefc8274c5271747ad64d5a9.tar.gz |
index: allow read of index w/ illegal entries
Allow `git_index_read` to handle reading existing indexes with
illegal entries. Allow the low-level `git_index_add` to add
properly formed `git_index_entry`s even if they contain paths
that would be illegal for the current filesystem (eg, `AUX`).
Continue to disallow `git_index_add_bypath` from adding entries
that are illegal universally illegal (eg, `.git`, `foo/../bar`).
-rw-r--r-- | src/checkout.c | 2 | ||||
-rw-r--r-- | src/index.c | 32 | ||||
-rw-r--r-- | src/path.c | 7 | ||||
-rw-r--r-- | src/path.h | 18 | ||||
-rw-r--r-- | src/refdb_fs.c | 4 | ||||
-rw-r--r-- | tests/path/core.c | 12 |
6 files changed, 52 insertions, 23 deletions
diff --git a/src/checkout.c b/src/checkout.c index a92ad0825..07b6bd633 100644 --- a/src/checkout.c +++ b/src/checkout.c @@ -1226,7 +1226,7 @@ static int checkout_verify_paths( int action, git_diff_delta *delta) { - unsigned int flags = GIT_PATH_REJECT_DEFAULTS | GIT_PATH_REJECT_DOT_GIT; + unsigned int flags = GIT_PATH_REJECT_WORKDIR_DEFAULTS; if (action & CHECKOUT_ACTION__REMOVE) { if (!git_path_isvalid(repo, delta->old_file.path, flags)) { diff --git a/src/index.c b/src/index.c index 5c7bd90dd..5728008aa 100644 --- a/src/index.c +++ b/src/index.c @@ -891,17 +891,31 @@ static void index_entry_adjust_namemask( entry->flags |= GIT_IDXENTRY_NAMEMASK; } +/* When `from_workdir` is true, we will validate the paths to avoid placing + * paths that are invalid for the working directory on the current filesystem + * (eg, on Windows, we will disallow `GIT~1`, `AUX`, `COM1`, etc). This + * function will *always* prevent `.git` and directory traversal `../` from + * being added to the index. + */ static int index_entry_create( git_index_entry **out, git_repository *repo, - const char *path) + const char *path, + bool from_workdir) { size_t pathlen = strlen(path), alloclen; struct entry_internal *entry; + unsigned int path_valid_flags = GIT_PATH_REJECT_INDEX_DEFAULTS; + + /* always reject placing `.git` in the index and directory traversal. + * when requested, disallow platform-specific filenames and upgrade to + * the platform-specific `.git` tests (eg, `git~1`, etc). + */ + if (from_workdir) + path_valid_flags |= GIT_PATH_REJECT_WORKDIR_DEFAULTS; - if (!git_path_isvalid(repo, path, - GIT_PATH_REJECT_DEFAULTS | GIT_PATH_REJECT_DOT_GIT)) { - giterr_set(GITERR_INDEX, "Invalid path: '%s'", path); + if (!git_path_isvalid(repo, path, path_valid_flags)) { + giterr_set(GITERR_INDEX, "invalid path: '%s'", path); return -1; } @@ -933,7 +947,7 @@ static int index_entry_init( "Could not initialize index entry. " "Index is not backed up by an existing repository."); - if (index_entry_create(&entry, INDEX_OWNER(index), rel_path) < 0) + if (index_entry_create(&entry, INDEX_OWNER(index), rel_path, true) < 0) return -1; /* write the blob to disk and get the oid and stat info */ @@ -1013,7 +1027,7 @@ static int index_entry_dup( git_index *index, const git_index_entry *src) { - if (index_entry_create(out, INDEX_OWNER(index), src->path) < 0) + if (index_entry_create(out, INDEX_OWNER(index), src->path, false) < 0) return -1; index_entry_cpy(*out, src); @@ -1035,7 +1049,7 @@ static int index_entry_dup_nocache( git_index *index, const git_index_entry *src) { - if (index_entry_create(out, INDEX_OWNER(index), src->path) < 0) + if (index_entry_create(out, INDEX_OWNER(index), src->path, false) < 0) return -1; index_entry_cpy_nocache(*out, src); @@ -1447,7 +1461,7 @@ static int add_repo_as_submodule(git_index_entry **out, git_index *index, const struct stat st; int error; - if (index_entry_create(&entry, INDEX_OWNER(index), path) < 0) + if (index_entry_create(&entry, INDEX_OWNER(index), path, true) < 0) return -1; if ((error = git_buf_joinpath(&abspath, git_repository_workdir(repo), path)) < 0) @@ -2884,7 +2898,7 @@ static int read_tree_cb( if (git_buf_joinpath(&path, root, tentry->filename) < 0) return -1; - if (index_entry_create(&entry, INDEX_OWNER(data->index), path.ptr) < 0) + if (index_entry_create(&entry, INDEX_OWNER(data->index), path.ptr, false) < 0) return -1; entry->mode = tentry->attr; diff --git a/src/path.c b/src/path.c index 18b4f03fd..852ef576a 100644 --- a/src/path.c +++ b/src/path.c @@ -1630,9 +1630,12 @@ static bool verify_component( !verify_dotgit_ntfs(repo, component, len)) return false; + /* don't bother rerunning the `.git` test if we ran the HFS or NTFS + * specific tests, they would have already rejected `.git`. + */ if ((flags & GIT_PATH_REJECT_DOT_GIT_HFS) == 0 && (flags & GIT_PATH_REJECT_DOT_GIT_NTFS) == 0 && - (flags & GIT_PATH_REJECT_DOT_GIT) && + (flags & GIT_PATH_REJECT_DOT_GIT_LITERAL) && len == 4 && component[0] == '.' && (component[1] == 'g' || component[1] == 'G') && @@ -1649,6 +1652,8 @@ GIT_INLINE(unsigned int) dotgit_flags( { int protectHFS = 0, protectNTFS = 0; + flags |= GIT_PATH_REJECT_DOT_GIT_LITERAL; + #ifdef __APPLE__ protectHFS = 1; #endif diff --git a/src/path.h b/src/path.h index 7e156fce8..875c8cb7e 100644 --- a/src/path.h +++ b/src/path.h @@ -564,15 +564,16 @@ extern int git_path_from_url_or_path(git_buf *local_path_out, const char *url_or #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) +#define GIT_PATH_REJECT_DOT_GIT_LITERAL (1 << 9) +#define GIT_PATH_REJECT_DOT_GIT_HFS (1 << 10) +#define GIT_PATH_REJECT_DOT_GIT_NTFS (1 << 11) /* 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 \ +# define GIT_PATH_REJECT_FILESYSTEM_DEFAULTS \ GIT_PATH_REJECT_TRAVERSAL | \ GIT_PATH_REJECT_BACKSLASH | \ GIT_PATH_REJECT_TRAILING_DOT | \ @@ -581,9 +582,18 @@ extern int git_path_from_url_or_path(git_buf *local_path_out, const char *url_or GIT_PATH_REJECT_DOS_PATHS | \ GIT_PATH_REJECT_NT_CHARS #else -# define GIT_PATH_REJECT_DEFAULTS GIT_PATH_REJECT_TRAVERSAL +# define GIT_PATH_REJECT_FILESYSTEM_DEFAULTS \ + GIT_PATH_REJECT_TRAVERSAL #endif + /* Paths that should never be written into the working directory. */ +#define GIT_PATH_REJECT_WORKDIR_DEFAULTS \ + GIT_PATH_REJECT_FILESYSTEM_DEFAULTS | GIT_PATH_REJECT_DOT_GIT + +/* Paths that should never be written to the index. */ +#define GIT_PATH_REJECT_INDEX_DEFAULTS \ + GIT_PATH_REJECT_TRAVERSAL | GIT_PATH_REJECT_DOT_GIT + /* * Determine whether a path is a valid git path or not - this must not contain * a '.' or '..' component, or a component that is ".git" (in any case). diff --git a/src/refdb_fs.c b/src/refdb_fs.c index 85b5034d6..1348c67a1 100644 --- a/src/refdb_fs.c +++ b/src/refdb_fs.c @@ -717,7 +717,7 @@ static int loose_lock(git_filebuf *file, refdb_fs_backend *backend, const char * assert(file && backend && name); - if (!git_path_isvalid(backend->repo, name, GIT_PATH_REJECT_DEFAULTS)) { + if (!git_path_isvalid(backend->repo, name, GIT_PATH_REJECT_FILESYSTEM_DEFAULTS)) { giterr_set(GITERR_INVALID, "Invalid reference name '%s'.", name); return GIT_EINVALIDSPEC; } @@ -1672,7 +1672,7 @@ static int lock_reflog(git_filebuf *file, refdb_fs_backend *backend, const char repo = backend->repo; - if (!git_path_isvalid(backend->repo, refname, GIT_PATH_REJECT_DEFAULTS)) { + if (!git_path_isvalid(backend->repo, refname, GIT_PATH_REJECT_FILESYSTEM_DEFAULTS)) { giterr_set(GITERR_INVALID, "Invalid reference name '%s'.", refname); return GIT_EINVALIDSPEC; } diff --git a/tests/path/core.c b/tests/path/core.c index 064f1492a..3dccfe5fb 100644 --- a/tests/path/core.c +++ b/tests/path/core.c @@ -105,12 +105,12 @@ void test_path_core__isvalid_dot_git(void) cl_assert_equal_b(true, git_path_isvalid(NULL, "foo/.GIT/bar", 0)); cl_assert_equal_b(true, git_path_isvalid(NULL, "foo/bar/.Git", 0)); - cl_assert_equal_b(false, git_path_isvalid(NULL, ".git", GIT_PATH_REJECT_DOT_GIT)); - cl_assert_equal_b(false, git_path_isvalid(NULL, ".git/foo", GIT_PATH_REJECT_DOT_GIT)); - cl_assert_equal_b(false, git_path_isvalid(NULL, "foo/.git", GIT_PATH_REJECT_DOT_GIT)); - cl_assert_equal_b(false, git_path_isvalid(NULL, "foo/.git/bar", GIT_PATH_REJECT_DOT_GIT)); - cl_assert_equal_b(false, git_path_isvalid(NULL, "foo/.GIT/bar", GIT_PATH_REJECT_DOT_GIT)); - cl_assert_equal_b(false, git_path_isvalid(NULL, "foo/bar/.Git", GIT_PATH_REJECT_DOT_GIT)); + cl_assert_equal_b(false, git_path_isvalid(NULL, ".git", GIT_PATH_REJECT_DOT_GIT_LITERAL)); + cl_assert_equal_b(false, git_path_isvalid(NULL, ".git/foo", GIT_PATH_REJECT_DOT_GIT_LITERAL)); + cl_assert_equal_b(false, git_path_isvalid(NULL, "foo/.git", GIT_PATH_REJECT_DOT_GIT_LITERAL)); + cl_assert_equal_b(false, git_path_isvalid(NULL, "foo/.git/bar", GIT_PATH_REJECT_DOT_GIT_LITERAL)); + cl_assert_equal_b(false, git_path_isvalid(NULL, "foo/.GIT/bar", GIT_PATH_REJECT_DOT_GIT_LITERAL)); + cl_assert_equal_b(false, git_path_isvalid(NULL, "foo/bar/.Git", GIT_PATH_REJECT_DOT_GIT_LITERAL)); cl_assert_equal_b(true, git_path_isvalid(NULL, "!git", 0)); cl_assert_equal_b(true, git_path_isvalid(NULL, "foo/!git", 0)); |