summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorVicent Marti <tanoku@gmail.com>2014-11-25 00:58:03 +0100
committerEdward Thomson <ethomson@microsoft.com>2014-12-16 10:08:49 -0600
commit0d388adc86946f3c8cddc2493b470d47a653c4a5 (patch)
treede6f47185316bf6c003d886b2bd4fdec3475b7e7
parent62155257d2d30d8f8d7108539df0681dce27ff61 (diff)
downloadlibgit2-0d388adc86946f3c8cddc2493b470d47a653c4a5.tar.gz
index: Check for valid paths before creating an index entry
-rw-r--r--src/index.c110
-rw-r--r--tests/index/tests.c101
2 files changed, 188 insertions, 23 deletions
diff --git a/src/index.c b/src/index.c
index d3bc081a5..644f0c8a7 100644
--- a/src/index.c
+++ b/src/index.c
@@ -762,19 +762,96 @@ void git_index_entry__init_from_stat(
entry->file_size = st->st_size;
}
-static git_index_entry *index_entry_alloc(const char *path)
+/*
+ * We fundamentally don't like some paths: we don't want
+ * dot or dot-dot anywhere, and for obvious reasons don't
+ * want to recurse into ".git" either.
+ *
+ * Also, we don't want double slashes or slashes at the
+ * end that can make pathnames ambiguous.
+ */
+static int verify_dotfile(const char *rest)
+{
+ /*
+ * The first character was '.', but that
+ * has already been discarded, we now test
+ * the rest.
+ */
+
+ /* "." is not allowed */
+ if (*rest == '\0' || *rest == '/')
+ return -1;
+
+ switch (*rest) {
+ /*
+ * ".git" followed by NUL or slash is bad. This
+ * shares the path end test with the ".." case.
+ */
+ case 'g':
+ case 'G':
+ if (rest[1] != 'i' && rest[1] != 'I')
+ break;
+ if (rest[2] != 't' && rest[2] != 'T')
+ break;
+ rest += 2;
+ /* fallthrough */
+ case '.':
+ if (rest[1] == '\0' || rest[1] == '/')
+ return -1;
+ }
+ return 0;
+}
+
+static int verify_component(char c, const char *rest)
+{
+ if ((c == '.' && verify_dotfile(rest)) < 0 || c == '/' || c == '\0') {
+ giterr_set(GITERR_INDEX, "Invalid path component in index: '%c%s'", c, rest);
+ return -1;
+ }
+ return 0;
+}
+
+static int verify_path(const char *path)
+{
+ char c;
+
+ /* TODO: should we check this? */
+ /*
+ if (has_dos_drive_prefix(path))
+ return -1;
+ */
+
+ c = *path++;
+ if (verify_component(c, path) < 0)
+ return -1;
+
+ while ((c = *path++) != '\0') {
+ if (c == '/') {
+ c = *path++;
+ if (verify_component(c, path) < 0)
+ return -1;
+ }
+ }
+ return 0;
+}
+
+static int index_entry_create(git_index_entry **out, const char *path)
{
size_t pathlen = strlen(path);
- struct entry_internal *entry =
- git__calloc(sizeof(struct entry_internal) + pathlen + 1, 1);
- if (!entry)
- return NULL;
+ struct entry_internal *entry;
+
+ if (verify_path(path) < 0)
+ return -1;
+
+ entry = git__calloc(sizeof(struct entry_internal) + pathlen + 1, 1);
+ GITERR_CHECK_ALLOC(entry);
entry->pathlen = pathlen;
memcpy(entry->path, path, pathlen);
entry->entry.path = entry->path;
- return (git_index_entry *)entry;
+ *out = (git_index_entry *)entry;
+ return 0;
}
static int index_entry_init(
@@ -790,14 +867,17 @@ static int index_entry_init(
"Could not initialize index entry. "
"Index is not backed up by an existing repository.");
+ if (index_entry_create(&entry, rel_path) < 0)
+ return -1;
+
/* write the blob to disk and get the oid and stat info */
error = git_blob__create_from_paths(
&oid, &st, INDEX_OWNER(index), NULL, rel_path, 0, true);
- if (error < 0)
- return error;
- entry = index_entry_alloc(rel_path);
- GITERR_CHECK_ALLOC(entry);
+ if (error < 0) {
+ index_entry_free(entry);
+ return error;
+ }
entry->id = oid;
git_index_entry__init_from_stat(entry, &st, !index->distrust_filemode);
@@ -862,11 +942,11 @@ static int index_entry_dup(git_index_entry **out, const git_index_entry *src)
return 0;
}
- *out = entry = index_entry_alloc(src->path);
- GITERR_CHECK_ALLOC(entry);
+ if (index_entry_create(&entry, src->path) < 0)
+ return -1;
index_entry_cpy(entry, src);
-
+ *out = entry;
return 0;
}
@@ -2316,8 +2396,8 @@ static int read_tree_cb(
if (git_buf_joinpath(&path, root, tentry->filename) < 0)
return -1;
- entry = index_entry_alloc(path.ptr);
- GITERR_CHECK_ALLOC(entry);
+ if (index_entry_create(&entry, path.ptr) < 0)
+ return -1;
entry->mode = tentry->attr;
entry->id = tentry->oid;
diff --git a/tests/index/tests.c b/tests/index/tests.c
index 7d544e8f3..0464e7337 100644
--- a/tests/index/tests.c
+++ b/tests/index/tests.c
@@ -309,31 +309,116 @@ void test_index_tests__add_bypath_to_a_bare_repository_returns_EBAREPO(void)
git_repository_free(bare_repo);
}
+static void add_invalid_filename(git_repository *repo, const char *fn)
+{
+ git_index *index;
+ git_buf path = GIT_BUF_INIT;
+
+ cl_git_pass(git_repository_index(&index, repo));
+ cl_assert(git_index_entrycount(index) == 0);
+
+ git_buf_joinpath(&path, "./invalid", fn);
+
+ cl_git_mkfile(path.ptr, NULL);
+ cl_git_fail(git_index_add_bypath(index, fn));
+ cl_must_pass(p_unlink(path.ptr));
+
+ cl_assert(git_index_entrycount(index) == 0);
+
+ git_index_free(index);
+}
+
/* Test that writing an invalid filename fails */
-void test_index_tests__write_invalid_filename(void)
+void test_index_tests__add_invalid_filename(void)
{
git_repository *repo;
+
+ p_mkdir("invalid", 0700);
+
+ cl_git_pass(git_repository_init(&repo, "./invalid", 0));
+ cl_must_pass(p_mkdir("./invalid/subdir", 0777));
+
+ add_invalid_filename(repo, ".git/hello");
+ add_invalid_filename(repo, ".GIT/hello");
+ add_invalid_filename(repo, ".GiT/hello");
+ add_invalid_filename(repo, "./.git/hello");
+ add_invalid_filename(repo, "./foo");
+ add_invalid_filename(repo, "./bar");
+ add_invalid_filename(repo, "subdir/../bar");
+
+ git_repository_free(repo);
+
+ cl_fixture_cleanup("invalid");
+}
+
+static void replace_char(char *str, char in, char out)
+{
+ char *c = str;
+
+ while (*c++)
+ if (*c == in)
+ *c = out;
+}
+
+static void write_invalid_filename(git_repository *repo, const char *fn_orig)
+{
git_index *index;
git_oid expected;
+ const git_index_entry *entry;
+ git_buf path = GIT_BUF_INIT;
+ char *fn;
- p_mkdir("read_tree", 0700);
-
- cl_git_pass(git_repository_init(&repo, "./read_tree", 0));
cl_git_pass(git_repository_index(&index, repo));
-
cl_assert(git_index_entrycount(index) == 0);
- cl_git_mkfile("./read_tree/.git/hello", NULL);
+ /*
+ * Sneak a valid path into the index, we'll update it
+ * to an invalid path when we try to write the index.
+ */
+ fn = git__strdup(fn_orig);
+ replace_char(fn, '/', '_');
+
+ git_buf_joinpath(&path, "./invalid", fn);
+
+ cl_git_mkfile(path.ptr, NULL);
+
+ cl_git_pass(git_index_add_bypath(index, fn));
+
+ cl_assert(entry = git_index_get_bypath(index, fn, 0));
- cl_git_pass(git_index_add_bypath(index, ".git/hello"));
+ /* kids, don't try this at home */
+ replace_char((char *)entry->path, '_', '/');
/* write-tree */
cl_git_fail(git_index_write_tree(&expected, index));
+ p_unlink(path.ptr);
+
+ cl_git_pass(git_index_remove_all(index, NULL, NULL, NULL));
git_index_free(index);
+ git__free(fn);
+}
+
+/* Test that writing an invalid filename fails */
+void test_index_tests__write_invalid_filename(void)
+{
+ git_repository *repo;
+
+ p_mkdir("invalid", 0700);
+
+ cl_git_pass(git_repository_init(&repo, "./invalid", 0));
+
+ write_invalid_filename(repo, ".git/hello");
+ write_invalid_filename(repo, ".GIT/hello");
+ write_invalid_filename(repo, ".GiT/hello");
+ write_invalid_filename(repo, "./.git/hello");
+ write_invalid_filename(repo, "./foo");
+ write_invalid_filename(repo, "./bar");
+ write_invalid_filename(repo, "foo/../bar");
+
git_repository_free(repo);
- cl_fixture_cleanup("read_tree");
+ cl_fixture_cleanup("invalid");
}
void test_index_tests__remove_entry(void)