Skip to content

Commit 8fea94e

Browse files
peffgitster
authored andcommitted
submodule-config: verify submodule names as paths
Submodule "names" come from the untrusted .gitmodules file, but we blindly append them to $GIT_DIR/modules to create our on-disk repo paths. This means you can do bad things by putting "../" into the name (among other things). Let's sanity-check these names to avoid building a path that can be exploited. There are two main decisions: 1. What should the allowed syntax be? I've reused verify_path() here, which is the same function that restricts index entries, and includes things like ".git" matching. Technically this is overly strict, as it does currently work to have a .gitmodules like: [submodule "foo/.git/bar"] path = more-sane-path But in practice you'd have to intentionally make such a funny config. By default, the subsection name is derived from the path at which the submodule was added, so it would generaly be subject to the verify_path() rules already. 2. Where should we enforce it? These days most of the .gitmodules reads go through submodule-config.c, so I've put it there in the reading step. That should cover all of the C code. There is one place in git-submodule.sh which still constructs a $GIT_DIR/modules/$name path itself, but the name there comes from either: a. An in-tree path, which we already know satisfies verify_path(). b. The --name parameter on the command line. This is presumably under the user's control. So this patch should close the vulnerability completely. This patch issues a warning and just ignores the config entry completely. This will generally end up producing a sensible error, as it works the same as a .gitmodules file which is missing a submodule entry (so "submodule update" will barf, but "git clone --recurse-submodules" will print an error but not abort the clone. There is one minor oddity, which is that we print the warning once per malformed config key (since that's how the config subsystem gives us the entries). So in the new test, for example, the user would see three warnings. That's OK, since the intent is that this case should never come up. Credit for finding this vulnerability and the proof of concept from which the test script was adapted goes to Etienne Stalmans. Signed-off-by: Jeff King <[email protected]> Signed-off-by: Junio C Hamano <[email protected]>
1 parent 42e6fde commit 8fea94e

File tree

3 files changed

+63
-0
lines changed

3 files changed

+63
-0
lines changed

‎submodule-config.c‎

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -163,6 +163,13 @@ static struct submodule *cache_lookup_name(struct submodule_cache *cache,
163163
return NULL;
164164
}
165165

166+
int check_submodule_name(const char *name)
167+
{
168+
if (!verify_path(name))
169+
return -1;
170+
return 0;
171+
}
172+
166173
static int name_and_item_from_var(const char *var, struct strbuf *name,
167174
struct strbuf *item)
168175
{
@@ -174,6 +181,12 @@ static int name_and_item_from_var(const char *var, struct strbuf *name,
174181
return 0;
175182

176183
strbuf_add(name, subsection, subsection_len);
184+
if (check_submodule_name(name->buf) < 0) {
185+
warning("ignoring suspicious submodule name: %s", name->buf);
186+
strbuf_release(name);
187+
return 0;
188+
}
189+
177190
strbuf_addstr(item, key);
178191

179192
return 1;

‎submodule-config.h‎

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,4 +35,11 @@ extern int gitmodule_sha1_from_commit(const unsigned char *commit_sha1,
3535
struct strbuf *rev);
3636
extern void submodule_free(void);
3737

38+
/*
39+
* Returns 0 if the name is syntactically acceptable as a submodule "name"
40+
* (e.g., that may be found in the subsection of a .gitmodules file) and -1
41+
* otherwise.
42+
*/
43+
int check_submodule_name(const char *name);
44+
3845
#endif /* SUBMODULE_CONFIG_H */

‎t/t7415-submodule-names.sh‎

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
#!/bin/sh
2+
3+
test_description='check handling of .. in submodule names'
4+
. ./test-lib.sh
5+
6+
test_expect_success 'create innocent subrepo' '
7+
git init innocent &&
8+
git -C innocent commit --allow-empty -m foo
9+
'
10+
11+
test_expect_success 'add evil submodule' '
12+
git submodule add "$PWD/innocent" evil &&
13+
14+
mkdir modules &&
15+
cp -r .git/modules/evil modules &&
16+
write_script modules/evil/hooks/post-checkout <<-\EOF &&
17+
echo >&2 "RUNNING POST CHECKOUT"
18+
EOF
19+
20+
git config -f .gitmodules submodule.evil.update checkout &&
21+
git config -f .gitmodules --rename-section \
22+
submodule.evil submodule.../../modules/evil &&
23+
git add modules &&
24+
git commit -am evil
25+
'
26+
27+
# This step seems like it shouldn't be necessary, since the payload is
28+
# contained entirely in the evil submodule. But due to the vagaries of the
29+
# submodule code, checking out the evil module will fail unless ".git/modules"
30+
# exists. Adding another submodule (with a name that sorts before "evil") is an
31+
# easy way to make sure this is the case in the victim clone.
32+
test_expect_success 'add other submodule' '
33+
git submodule add "$PWD/innocent" another-module &&
34+
git add another-module &&
35+
git commit -am another
36+
'
37+
38+
test_expect_success 'clone evil superproject' '
39+
git clone --recurse-submodules . victim >output 2>&1 &&
40+
! grep "RUNNING POST CHECKOUT" output
41+
'
42+
43+
test_done

0 commit comments

Comments
 (0)