Skip to content

Commit 6b15cea

Browse files
committed
submodule: ignore submodules which include path traversal in their name
If the we decide that the "name" of the submodule (i.e. its path inside `.git/modules/`) is trying to escape that directory or otherwise trick us, we ignore the configuration for that submodule. This leaves us with a half-configured submodule when looking it up by path, but it's the same result as if the configuration really were missing. The name check is potentially more strict than it needs to be, but it lets us re-use the check we're doing for the checkout. The function that encapsulates this logic is ready to be exported but we don't want to do that in a security release so it remains internal for now.
1 parent 7553763 commit 6b15cea

File tree

3 files changed

+55
-15
lines changed

3 files changed

+55
-15
lines changed

src/submodule.c

Lines changed: 25 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -214,7 +214,7 @@ static void free_submodule_names(git_strmap *names)
214214
* TODO: for some use-cases, this might need case-folding on a
215215
* case-insensitive filesystem
216216
*/
217-
static int load_submodule_names(git_strmap *out, git_config *cfg)
217+
static int load_submodule_names(git_strmap *out, git_repository *repo, git_config *cfg)
218218
{
219219
const char *key = "submodule\\..*\\.path";
220220
git_config_iterator *iter;
@@ -232,6 +232,9 @@ static int load_submodule_names(git_strmap *out, git_config *cfg)
232232
ldot = strrchr(entry->name, '.');
233233

234234
git_buf_put(&buf, fdot + 1, ldot - fdot - 1);
235+
if (!git_submodule_name_is_valid(repo, buf.ptr, 0))
236+
continue;
237+
235238
git_strmap_insert(out, entry->value, git_buf_detach(&buf), &rval);
236239
if (rval < 0) {
237240
giterr_set(GITERR_NOMEMORY, "error inserting submodule into hash table");
@@ -359,6 +362,15 @@ int git_submodule_lookup(
359362
return 0;
360363
}
361364

365+
int git_submodule_name_is_valid(const git_repository *repo, const char *name, int flags)
366+
{
367+
if (flags == 0)
368+
flags = GIT_PATH_REJECT_FILESYSTEM_DEFAULTS;
369+
370+
/* FIXME: Un-consting it to reduce the amount of diff */
371+
return git_path_isvalid((git_repository *)repo, name, flags);
372+
}
373+
362374
static void submodule_free_dup(void *sm)
363375
{
364376
git_submodule_free(sm);
@@ -404,7 +416,7 @@ static int submodules_from_index(git_strmap *map, git_index *idx, git_config *cf
404416
git_strmap *names = 0;
405417

406418
git_strmap_alloc(&names);
407-
if ((error = load_submodule_names(names, cfg)))
419+
if ((error = load_submodule_names(names, git_index_owner(idx), cfg)))
408420
goto done;
409421

410422
if ((error = git_iterator_for_index(&i, git_index_owner(idx), idx, NULL)) < 0)
@@ -456,7 +468,7 @@ static int submodules_from_head(git_strmap *map, git_tree *head, git_config *cfg
456468
const git_index_entry *entry;
457469
git_strmap *names = 0;
458470
git_strmap_alloc(&names);
459-
if ((error = load_submodule_names(names, cfg)))
471+
if ((error = load_submodule_names(names, git_tree_owner(head), cfg)))
460472
goto done;
461473

462474
if ((error = git_iterator_for_tree(&i, head, NULL)) < 0)
@@ -1566,6 +1578,11 @@ int git_submodule_reload(git_submodule *sm, int force)
15661578

15671579
assert(sm);
15681580

1581+
if (!git_submodule_name_is_valid(sm->repo, sm->name, 0)) {
1582+
/* This should come with a warning, but we've no API for that */
1583+
return 0;
1584+
}
1585+
15691586
if (!git_repository_is_bare(sm->repo)) {
15701587
/* refresh config data */
15711588
if ((error = gitmodules_snapshot(&mods, sm->repo)) < 0 && error != GIT_ENOTFOUND)
@@ -1923,6 +1940,11 @@ static int submodule_load_each(const git_config_entry *entry, void *payload)
19231940
if ((error = git_buf_set(&name, namestart, property - namestart -1)) < 0)
19241941
return error;
19251942

1943+
if (!git_path_isvalid(data->repo, name.ptr, GIT_PATH_REJECT_TRAVERSAL)) {
1944+
error = 0;
1945+
goto done;
1946+
}
1947+
19261948
/*
19271949
* Now that we have the submodule's name, we can use that to
19281950
* figure out whether it's in the map. If it's not, we create

src/submodule.h

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -148,4 +148,17 @@ extern int git_submodule_parse_update(
148148
extern int git_submodule__map(
149149
git_repository *repo,
150150
git_strmap *map);
151+
152+
/**
153+
* Check whether a submodule's name is valid.
154+
*
155+
* Check the path against the path validity rules, either the filesystem
156+
* defaults (like checkout does) or whichever you want to compare against.
157+
*
158+
* @param repo the repository which contains the submodule
159+
* @param name the name to check
160+
* @param flags the `GIT_PATH` flags to use for the check (0 to use filesystem defaults)
161+
*/
162+
extern int git_submodule_name_is_valid(const git_repository *repo, const char *name, int flags);
163+
151164
#endif

tests/submodule/escape.c

Lines changed: 17 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -32,29 +32,34 @@ void test_submodule_escape__from_gitdir(void)
3232
git_config *cfg;
3333
git_submodule *sm;
3434
git_buf buf = GIT_BUF_INIT;
35+
unsigned int sm_location;
3536

3637
g_repo = setup_fixture_submodule_simple();
3738

3839
cl_git_pass(git_buf_joinpath(&buf, git_repository_workdir(g_repo), ".gitmodules"));
39-
cl_git_pass(git_config_open_ondisk(&cfg, git_buf_cstr(&buf)));
40-
41-
/* We don't have a function to rename a subsection so we do it manually */
42-
cl_git_pass(git_submodule_lookup(&sm, g_repo, "testrepo"));
43-
cl_git_pass(git_config_set_string(cfg, "submodule." EVIL_SM_NAME ".path", git_submodule_path(sm)));
44-
cl_git_pass(git_config_set_string(cfg, "submodule." EVIL_SM_NAME ".url", git_submodule_url(sm)));
45-
cl_git_pass(git_config_delete_entry(cfg, "submodule.testrepo.path"));
46-
cl_git_pass(git_config_delete_entry(cfg, "submodule.testrepo.url"));
47-
git_config_free(cfg);
40+
cl_git_rewritefile(buf.ptr,
41+
"[submodule \"" EVIL_SM_NAME "\"]\n"
42+
" path = testrepo\n"
43+
" url = ../testrepo.git\n");
4844

4945
/* We also need to update the value in the config */
5046
cl_git_pass(git_repository_config__weakptr(&cfg, g_repo));
51-
cl_git_pass(git_config_set_string(cfg, "submodule." EVIL_SM_NAME ".url", git_submodule_url(sm)));
5247
cfg = NULL;
5348

5449
/* Find it all the different ways we know about it */
55-
cl_git_fail_with(GIT_ENOTFOUND, git_submodule_lookup(&sm, g_repo, EVIL_SM_NAME));
56-
cl_git_fail_with(GIT_ENOTFOUND, git_submodule_lookup(&sm, g_repo, "testrepo"));
5750
foundit = 0;
5851
cl_git_pass(git_submodule_foreach(g_repo, find_evil, &foundit));
5952
cl_assert_equal_i(0, foundit);
53+
cl_git_fail_with(GIT_ENOTFOUND, git_submodule_lookup(&sm, g_repo, EVIL_SM_NAME));
54+
/*
55+
* We do know about this as it's in the index and HEAD, but the data is
56+
* incomplete as there is no configured data for it (we pretend it
57+
* doesn't exist). This leaves us with an odd situation but it's
58+
* consistent with what we would do if we did add a submodule with no
59+
* configuration.
60+
*/
61+
cl_git_pass(git_submodule_lookup(&sm, g_repo, "testrepo"));
62+
cl_git_pass(git_submodule_location(&sm_location, sm));
63+
cl_assert_equal_i(GIT_SUBMODULE_STATUS_IN_INDEX | GIT_SUBMODULE_STATUS_IN_HEAD, sm_location);
64+
git_submodule_free(sm);
6065
}

0 commit comments

Comments
 (0)