blob: 1bf9004933b27e9633f474321ec49ed9ce451798 [file] [log] [blame]
From 392f99a5d2174e6124f829d034bac6755c33119d Mon Sep 17 00:00:00 2001
From: Johannes Schindelin <johannes.schindelin@gmx.de>
Date: Tue, 1 Oct 2019 23:27:18 +0200
Subject: Disallow dubiously-nested submodule git directories
commit a8dee3ca610f5a1d403634492136c887f83b59d2 upstream.
Currently it is technically possible to let a submodule's git
directory point right into the git dir of a sibling submodule.
Example: the git directories of two submodules with the names `hippo`
and `hippo/hooks` would be `.git/modules/hippo/` and
`.git/modules/hippo/hooks/`, respectively, but the latter is already
intended to house the former's hooks.
In most cases, this is just confusing, but there is also a (quite
contrived) attack vector where Git can be fooled into mistaking remote
content for file contents it wrote itself during a recursive clone.
Let's plug this bug.
To do so, we introduce the new function `validate_submodule_git_dir()`
which simply verifies that no git dir exists for any leading directories
of the submodule name (if there are any).
Note: this patch specifically continues to allow sibling modules names
of the form `core/lib`, `core/doc`, etc, as long as `core` is not a
submodule name.
This fixes CVE-2019-1387.
[jn: backported to 2.11.y:
- port to git-submodule.sh
- use explicit chdir to emulate test_commit -C in test]
Reported-by: Nicolas Joly <Nicolas.Joly@microsoft.com>
Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
Signed-off-by: Jonathan Nieder <jrnieder@gmail.com>
---
builtin/submodule--helper.c | 24 ++++++++++++++++++++++
git-submodule.sh | 5 +++++
submodule.c | 41 +++++++++++++++++++++++++++++++++++++
submodule.h | 5 +++++
t/t7415-submodule-names.sh | 23 +++++++++++++++++++++
5 files changed, 98 insertions(+)
diff --git a/builtin/submodule--helper.c b/builtin/submodule--helper.c
index 9e0f985501..6f33a665df 100644
--- a/builtin/submodule--helper.c
+++ b/builtin/submodule--helper.c
@@ -638,6 +638,10 @@ static int module_clone(int argc, const char **argv, const char *prefix)
} else
path = xstrdup(path);
+ if (validate_submodule_git_dir(sm_gitdir, name) < 0)
+ die(_("refusing to create/use '%s' in another submodule's "
+ "git dir"), sm_gitdir);
+
if (!file_exists(sm_gitdir)) {
if (safe_create_leading_directories_const(sm_gitdir) < 0)
die(_("could not create directory '%s'"), sm_gitdir);
@@ -1111,6 +1115,25 @@ static int check_name(int argc, const char **argv, const char *prefix)
return 0;
}
+/*
+ * Exit non-zero if the proposed submodule repository path is inside
+ * another submodules' git dir.
+ */
+static int validate_git_dir(int argc, const char **argv, const char *prefix)
+{
+ char *sm_gitdir;
+
+ if (argc != 3)
+ usage("git submodule--helper validate-git-dir <path> <name>");
+ sm_gitdir = xstrdup(argv[1]);
+ if (validate_submodule_git_dir(sm_gitdir, argv[2]) < 0) {
+ free(sm_gitdir);
+ return 1;
+ }
+ free(sm_gitdir);
+ return 0;
+}
+
struct cmd_struct {
const char *cmd;
int (*fn)(int, const char **, const char *);
@@ -1127,6 +1150,7 @@ static struct cmd_struct commands[] = {
{"init", module_init},
{"remote-branch", resolve_remote_submodule_branch},
{"check-name", check_name},
+ {"validate-git-dir", validate_git_dir},
};
int cmd_submodule__helper(int argc, const char **argv, const char *prefix)
diff --git a/git-submodule.sh b/git-submodule.sh
index 81495c018f..f4e6b0204d 100755
--- a/git-submodule.sh
+++ b/git-submodule.sh
@@ -242,6 +242,11 @@ Use -f if you really want to add it." >&2
fi
else
+ sm_gitdir=".git/modules/$sm_name"
+ if ! git submodule--helper validate-git-dir "$sm_gitdir" "$sm_name"
+ then
+ die "$(eval_gettextln "refusing to create/use '\$sm_gitdir' in another submodule's git dir")"
+ fi
if test -d ".git/modules/$sm_name"
then
if test -z "$force"
diff --git a/submodule.c b/submodule.c
index 6f7d883de9..060e67f7e2 100644
--- a/submodule.c
+++ b/submodule.c
@@ -1251,6 +1251,47 @@ int parallel_submodules(void)
return parallel_jobs;
}
+int validate_submodule_git_dir(char *git_dir, const char *submodule_name)
+{
+ size_t len = strlen(git_dir), suffix_len = strlen(submodule_name);
+ char *p;
+ int ret = 0;
+
+ if (len <= suffix_len || (p = git_dir + len - suffix_len)[-1] != '/' ||
+ strcmp(p, submodule_name))
+ die("BUG: submodule name '%s' not a suffix of git dir '%s'",
+ submodule_name, git_dir);
+
+ /*
+ * We prevent the contents of sibling submodules' git directories to
+ * clash.
+ *
+ * Example: having a submodule named `hippo` and another one named
+ * `hippo/hooks` would result in the git directories
+ * `.git/modules/hippo/` and `.git/modules/hippo/hooks/`, respectively,
+ * but the latter directory is already designated to contain the hooks
+ * of the former.
+ */
+ for (; *p; p++) {
+ if (is_dir_sep(*p)) {
+ char c = *p;
+
+ *p = '\0';
+ if (is_git_directory(git_dir))
+ ret = -1;
+ *p = c;
+
+ if (ret < 0)
+ return error(_("submodule git dir '%s' is "
+ "inside git dir '%.*s'"),
+ git_dir,
+ (int)(p - git_dir), git_dir);
+ }
+ }
+
+ return 0;
+}
+
void prepare_submodule_repo_env(struct argv_array *out)
{
const char * const *var;
diff --git a/submodule.h b/submodule.h
index d9e197a948..4e7d6f12f1 100644
--- a/submodule.h
+++ b/submodule.h
@@ -68,6 +68,11 @@ int push_unpushed_submodules(unsigned char new_sha1[20], const char *remotes_nam
void connect_work_tree_and_git_dir(const char *work_tree, const char *git_dir);
int parallel_submodules(void);
+/*
+ * Make sure that no submodule's git dir is nested in a sibling submodule's.
+ */
+int validate_submodule_git_dir(char *git_dir, const char *submodule_name);
+
/*
* Prepare the "env_array" parameter of a "struct child_process" for executing
* a submodule by clearing any repo-specific envirionment variables, but
diff --git a/t/t7415-submodule-names.sh b/t/t7415-submodule-names.sh
index b3a421d4fd..fd50ae1fb3 100755
--- a/t/t7415-submodule-names.sh
+++ b/t/t7415-submodule-names.sh
@@ -183,4 +183,27 @@ test_expect_success MINGW 'prevent git~1 squatting on Windows' '
! grep gitdir squatting-clone/d/a/git~2
'
+test_expect_success 'git dirs of sibling submodules must not be nested' '
+ git init nested &&
+ (
+ cd nested &&
+ test_commit nested &&
+ cat >.gitmodules <<-EOF &&
+ [submodule "hippo"]
+ url = .
+ path = thing1
+ [submodule "hippo/hooks"]
+ url = .
+ path = thing2
+ EOF
+ git clone . thing1 &&
+ git clone . thing2 &&
+ git add .gitmodules thing1 thing2 &&
+ test_tick &&
+ git commit -m nested
+ ) &&
+ test_must_fail git clone --recurse-submodules nested clone 2>err &&
+ test_i18ngrep "is inside git dir" err
+'
+
test_done
--
2.24.0.393.g34dc348eaf