Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit a4bb75a

Browse files
vmgEdward Thomson
authored and
Edward Thomson
committed
index: Check for valid paths before creating an index entry
1 parent 44ebc08 commit a4bb75a

File tree

2 files changed

+188
-23
lines changed

2 files changed

+188
-23
lines changed

src/index.c

Lines changed: 95 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -762,19 +762,96 @@ void git_index_entry__init_from_stat(
762762
entry->file_size = st->st_size;
763763
}
764764

765-
static git_index_entry *index_entry_alloc(const char *path)
765+
/*
766+
* We fundamentally don't like some paths: we don't want
767+
* dot or dot-dot anywhere, and for obvious reasons don't
768+
* want to recurse into ".git" either.
769+
*
770+
* Also, we don't want double slashes or slashes at the
771+
* end that can make pathnames ambiguous.
772+
*/
773+
static int verify_dotfile(const char *rest)
774+
{
775+
/*
776+
* The first character was '.', but that
777+
* has already been discarded, we now test
778+
* the rest.
779+
*/
780+
781+
/* "." is not allowed */
782+
if (*rest == '\0' || *rest == '/')
783+
return -1;
784+
785+
switch (*rest) {
786+
/*
787+
* ".git" followed by NUL or slash is bad. This
788+
* shares the path end test with the ".." case.
789+
*/
790+
case 'g':
791+
case 'G':
792+
if (rest[1] != 'i' && rest[1] != 'I')
793+
break;
794+
if (rest[2] != 't' && rest[2] != 'T')
795+
break;
796+
rest += 2;
797+
/* fallthrough */
798+
case '.':
799+
if (rest[1] == '\0' || rest[1] == '/')
800+
return -1;
801+
}
802+
return 0;
803+
}
804+
805+
static int verify_component(char c, const char *rest)
806+
{
807+
if ((c == '.' && verify_dotfile(rest)) < 0 || c == '/' || c == '\0') {
808+
giterr_set(GITERR_INDEX, "Invalid path component in index: '%c%s'", c, rest);
809+
return -1;
810+
}
811+
return 0;
812+
}
813+
814+
static int verify_path(const char *path)
815+
{
816+
char c;
817+
818+
/* TODO: should we check this? */
819+
/*
820+
if (has_dos_drive_prefix(path))
821+
return -1;
822+
*/
823+
824+
c = *path++;
825+
if (verify_component(c, path) < 0)
826+
return -1;
827+
828+
while ((c = *path++) != '\0') {
829+
if (c == '/') {
830+
c = *path++;
831+
if (verify_component(c, path) < 0)
832+
return -1;
833+
}
834+
}
835+
return 0;
836+
}
837+
838+
static int index_entry_create(git_index_entry **out, const char *path)
766839
{
767840
size_t pathlen = strlen(path);
768-
struct entry_internal *entry =
769-
git__calloc(sizeof(struct entry_internal) + pathlen + 1, 1);
770-
if (!entry)
771-
return NULL;
841+
struct entry_internal *entry;
842+
843+
if (verify_path(path) < 0)
844+
return -1;
845+
846+
entry = git__calloc(sizeof(struct entry_internal) + pathlen + 1, 1);
847+
GITERR_CHECK_ALLOC(entry);
772848

773849
entry->pathlen = pathlen;
774850
memcpy(entry->path, path, pathlen);
775851
entry->entry.path = entry->path;
776852

777-
return (git_index_entry *)entry;
853+
*out = (git_index_entry *)entry;
854+
return 0;
778855
}
779856

780857
static int index_entry_init(
@@ -790,14 +867,17 @@ static int index_entry_init(
790867
"Could not initialize index entry. "
791868
"Index is not backed up by an existing repository.");
792869

870+
if (index_entry_create(&entry, rel_path) < 0)
871+
return -1;
872+
793873
/* write the blob to disk and get the oid and stat info */
794874
error = git_blob__create_from_paths(
795875
&oid, &st, INDEX_OWNER(index), NULL, rel_path, 0, true);
796-
if (error < 0)
797-
return error;
798876

799-
entry = index_entry_alloc(rel_path);
800-
GITERR_CHECK_ALLOC(entry);
877+
if (error < 0) {
878+
index_entry_free(entry);
879+
return error;
880+
}
801881

802882
entry->id = oid;
803883
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)
862942
return 0;
863943
}
864944

865-
*out = entry = index_entry_alloc(src->path);
866-
GITERR_CHECK_ALLOC(entry);
945+
if (index_entry_create(&entry, src->path) < 0)
946+
return -1;
867947

868948
index_entry_cpy(entry, src);
869-
949+
*out = entry;
870950
return 0;
871951
}
872952

@@ -2316,8 +2396,8 @@ static int read_tree_cb(
23162396
if (git_buf_joinpath(&path, root, tentry->filename) < 0)
23172397
return -1;
23182398

2319-
entry = index_entry_alloc(path.ptr);
2320-
GITERR_CHECK_ALLOC(entry);
2399+
if (index_entry_create(&entry, path.ptr) < 0)
2400+
return -1;
23212401

23222402
entry->mode = tentry->attr;
23232403
entry->id = tentry->oid;

tests/index/tests.c

Lines changed: 93 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -309,31 +309,116 @@ void test_index_tests__add_bypath_to_a_bare_repository_returns_EBAREPO(void)
309309
git_repository_free(bare_repo);
310310
}
311311

312+
static void add_invalid_filename(git_repository *repo, const char *fn)
313+
{
314+
git_index *index;
315+
git_buf path = GIT_BUF_INIT;
316+
317+
cl_git_pass(git_repository_index(&index, repo));
318+
cl_assert(git_index_entrycount(index) == 0);
319+
320+
git_buf_joinpath(&path, "./invalid", fn);
321+
322+
cl_git_mkfile(path.ptr, NULL);
323+
cl_git_fail(git_index_add_bypath(index, fn));
324+
cl_must_pass(p_unlink(path.ptr));
325+
326+
cl_assert(git_index_entrycount(index) == 0);
327+
328+
git_index_free(index);
329+
}
330+
312331
/* Test that writing an invalid filename fails */
313-
void test_index_tests__write_invalid_filename(void)
332+
void test_index_tests__add_invalid_filename(void)
314333
{
315334
git_repository *repo;
335+
336+
p_mkdir("invalid", 0700);
337+
338+
cl_git_pass(git_repository_init(&repo, "./invalid", 0));
339+
cl_must_pass(p_mkdir("./invalid/subdir", 0777));
340+
341+
add_invalid_filename(repo, ".git/hello");
342+
add_invalid_filename(repo, ".GIT/hello");
343+
add_invalid_filename(repo, ".GiT/hello");
344+
add_invalid_filename(repo, "./.git/hello");
345+
add_invalid_filename(repo, "./foo");
346+
add_invalid_filename(repo, "./bar");
347+
add_invalid_filename(repo, "subdir/../bar");
348+
349+
git_repository_free(repo);
350+
351+
cl_fixture_cleanup("invalid");
352+
}
353+
354+
static void replace_char(char *str, char in, char out)
355+
{
356+
char *c = str;
357+
358+
while (*c++)
359+
if (*c == in)
360+
*c = out;
361+
}
362+
363+
static void write_invalid_filename(git_repository *repo, const char *fn_orig)
364+
{
316365
git_index *index;
317366
git_oid expected;
367+
const git_index_entry *entry;
368+
git_buf path = GIT_BUF_INIT;
369+
char *fn;
318370

319-
p_mkdir("read_tree", 0700);
320-
321-
cl_git_pass(git_repository_init(&repo, "./read_tree", 0));
322371
cl_git_pass(git_repository_index(&index, repo));
323-
324372
cl_assert(git_index_entrycount(index) == 0);
325373

326-
cl_git_mkfile("./read_tree/.git/hello", NULL);
374+
/*
375+
* Sneak a valid path into the index, we'll update it
376+
* to an invalid path when we try to write the index.
377+
*/
378+
fn = git__strdup(fn_orig);
379+
replace_char(fn, '/', '_');
380+
381+
git_buf_joinpath(&path, "./invalid", fn);
382+
383+
cl_git_mkfile(path.ptr, NULL);
384+
385+
cl_git_pass(git_index_add_bypath(index, fn));
386+
387+
cl_assert(entry = git_index_get_bypath(index, fn, 0));
327388

328-
cl_git_pass(git_index_add_bypath(index, ".git/hello"));
389+
/* kids, don't try this at home */
390+
replace_char((char *)entry->path, '_', '/');
329391

330392
/* write-tree */
331393
cl_git_fail(git_index_write_tree(&expected, index));
332394

395+
p_unlink(path.ptr);
396+
397+
cl_git_pass(git_index_remove_all(index, NULL, NULL, NULL));
333398
git_index_free(index);
399+
git__free(fn);
400+
}
401+
402+
/* Test that writing an invalid filename fails */
403+
void test_index_tests__write_invalid_filename(void)
404+
{
405+
git_repository *repo;
406+
407+
p_mkdir("invalid", 0700);
408+
409+
cl_git_pass(git_repository_init(&repo, "./invalid", 0));
410+
411+
write_invalid_filename(repo, ".git/hello");
412+
write_invalid_filename(repo, ".GIT/hello");
413+
write_invalid_filename(repo, ".GiT/hello");
414+
write_invalid_filename(repo, "./.git/hello");
415+
write_invalid_filename(repo, "./foo");
416+
write_invalid_filename(repo, "./bar");
417+
write_invalid_filename(repo, "foo/../bar");
418+
334419
git_repository_free(repo);
335420

336-
cl_fixture_cleanup("read_tree");
421+
cl_fixture_cleanup("invalid");
337422
}
338423

339424
void test_index_tests__remove_entry(void)

0 commit comments

Comments
 (0)