mirror of
https://github.com/git/git.git
synced 2024-10-28 12:59:41 +01:00
clone: prevent hooks from running during a clone
Critical security issues typically combine relatively common vulnerabilities such as case confusion in file paths with other weaknesses in order to raise the severity of the attack. One such weakness that has haunted the Git project in many a submodule-related CVE is that any hooks that are found are executed during a clone operation. Examples are the `post-checkout` and `fsmonitor` hooks. However, Git's design calls for hooks to be disabled by default, as only disabled example hooks are copied over from the templates in `<prefix>/share/git-core/templates/`. As a defense-in-depth measure, let's prevent those hooks from running. Obviously, administrators can choose to drop enabled hooks into the template directory, though, _and_ it is also possible to override `core.hooksPath`, in which case the new check needs to be disabled. Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
This commit is contained in:
parent
584de0b4c2
commit
8db1e8743c
3 changed files with 94 additions and 1 deletions
|
@ -908,6 +908,8 @@ int cmd_clone(int argc, const char **argv, const char *prefix)
|
|||
int err = 0, complete_refs_before_fetch = 1;
|
||||
int submodule_progress;
|
||||
int filter_submodules = 0;
|
||||
const char *template_dir;
|
||||
char *template_dir_dup = NULL;
|
||||
|
||||
struct transport_ls_refs_options transport_ls_refs_options =
|
||||
TRANSPORT_LS_REFS_OPTIONS_INIT;
|
||||
|
@ -927,6 +929,13 @@ int cmd_clone(int argc, const char **argv, const char *prefix)
|
|||
usage_msg_opt(_("You must specify a repository to clone."),
|
||||
builtin_clone_usage, builtin_clone_options);
|
||||
|
||||
xsetenv("GIT_CLONE_PROTECTION_ACTIVE", "true", 0 /* allow user override */);
|
||||
template_dir = get_template_dir(option_template);
|
||||
if (*template_dir && !is_absolute_path(template_dir))
|
||||
template_dir = template_dir_dup =
|
||||
absolute_pathdup(template_dir);
|
||||
xsetenv("GIT_CLONE_TEMPLATE_DIR", template_dir, 1);
|
||||
|
||||
if (option_depth || option_since || option_not.nr)
|
||||
deepen = 1;
|
||||
if (option_single_branch == -1)
|
||||
|
@ -1074,7 +1083,7 @@ int cmd_clone(int argc, const char **argv, const char *prefix)
|
|||
}
|
||||
}
|
||||
|
||||
init_db(git_dir, real_git_dir, option_template, GIT_HASH_UNKNOWN, NULL,
|
||||
init_db(git_dir, real_git_dir, template_dir, GIT_HASH_UNKNOWN, NULL,
|
||||
INIT_DB_QUIET);
|
||||
|
||||
if (real_git_dir) {
|
||||
|
@ -1392,6 +1401,7 @@ int cmd_clone(int argc, const char **argv, const char *prefix)
|
|||
free(unborn_head);
|
||||
free(dir);
|
||||
free(path);
|
||||
free(template_dir_dup);
|
||||
UNLEAK(repo);
|
||||
junk_mode = JUNK_LEAVE_ALL;
|
||||
|
||||
|
|
32
hook.c
32
hook.c
|
@ -3,6 +3,30 @@
|
|||
#include "run-command.h"
|
||||
#include "config.h"
|
||||
|
||||
static int identical_to_template_hook(const char *name, const char *path)
|
||||
{
|
||||
const char *env = getenv("GIT_CLONE_TEMPLATE_DIR");
|
||||
const char *template_dir = get_template_dir(env && *env ? env : NULL);
|
||||
struct strbuf template_path = STRBUF_INIT;
|
||||
int found_template_hook, ret;
|
||||
|
||||
strbuf_addf(&template_path, "%s/hooks/%s", template_dir, name);
|
||||
found_template_hook = access(template_path.buf, X_OK) >= 0;
|
||||
#ifdef STRIP_EXTENSION
|
||||
if (!found_template_hook) {
|
||||
strbuf_addstr(&template_path, STRIP_EXTENSION);
|
||||
found_template_hook = access(template_path.buf, X_OK) >= 0;
|
||||
}
|
||||
#endif
|
||||
if (!found_template_hook)
|
||||
return 0;
|
||||
|
||||
ret = do_files_match(template_path.buf, path);
|
||||
|
||||
strbuf_release(&template_path);
|
||||
return ret;
|
||||
}
|
||||
|
||||
const char *find_hook(const char *name)
|
||||
{
|
||||
static struct strbuf path = STRBUF_INIT;
|
||||
|
@ -38,6 +62,14 @@ const char *find_hook(const char *name)
|
|||
}
|
||||
return NULL;
|
||||
}
|
||||
if (!git_hooks_path && git_env_bool("GIT_CLONE_PROTECTION_ACTIVE", 0) &&
|
||||
!identical_to_template_hook(name, path.buf))
|
||||
die(_("active `%s` hook found during `git clone`:\n\t%s\n"
|
||||
"For security reasons, this is disallowed by default.\n"
|
||||
"If this is intentional and the hook should actually "
|
||||
"be run, please\nrun the command again with "
|
||||
"`GIT_CLONE_PROTECTION_ACTIVE=false`"),
|
||||
name, path.buf);
|
||||
return path.buf;
|
||||
}
|
||||
|
||||
|
|
|
@ -771,6 +771,57 @@ test_expect_success 'batch missing blob request does not inadvertently try to fe
|
|||
git clone --filter=blob:limit=0 "file://$(pwd)/server" client
|
||||
'
|
||||
|
||||
test_expect_success 'clone with init.templatedir runs hooks' '
|
||||
git init tmpl/hooks &&
|
||||
write_script tmpl/hooks/post-checkout <<-EOF &&
|
||||
echo HOOK-RUN >&2
|
||||
echo I was here >hook.run
|
||||
EOF
|
||||
git -C tmpl/hooks add . &&
|
||||
test_tick &&
|
||||
git -C tmpl/hooks commit -m post-checkout &&
|
||||
|
||||
test_when_finished "git config --global --unset init.templateDir || :" &&
|
||||
test_when_finished "git config --unset init.templateDir || :" &&
|
||||
(
|
||||
sane_unset GIT_TEMPLATE_DIR &&
|
||||
NO_SET_GIT_TEMPLATE_DIR=t &&
|
||||
export NO_SET_GIT_TEMPLATE_DIR &&
|
||||
|
||||
git -c core.hooksPath="$(pwd)/tmpl/hooks" \
|
||||
clone tmpl/hooks hook-run-hookspath 2>err &&
|
||||
! grep "active .* hook found" err &&
|
||||
test_path_is_file hook-run-hookspath/hook.run &&
|
||||
|
||||
git -c init.templateDir="$(pwd)/tmpl" \
|
||||
clone tmpl/hooks hook-run-config 2>err &&
|
||||
! grep "active .* hook found" err &&
|
||||
test_path_is_file hook-run-config/hook.run &&
|
||||
|
||||
git clone --template=tmpl tmpl/hooks hook-run-option 2>err &&
|
||||
! grep "active .* hook found" err &&
|
||||
test_path_is_file hook-run-option/hook.run &&
|
||||
|
||||
git config --global init.templateDir "$(pwd)/tmpl" &&
|
||||
git clone tmpl/hooks hook-run-global-config 2>err &&
|
||||
git config --global --unset init.templateDir &&
|
||||
! grep "active .* hook found" err &&
|
||||
test_path_is_file hook-run-global-config/hook.run &&
|
||||
|
||||
# clone ignores local `init.templateDir`; need to create
|
||||
# a new repository because we deleted `.git/` in the
|
||||
# `setup` test case above
|
||||
git init local-clone &&
|
||||
cd local-clone &&
|
||||
|
||||
git config init.templateDir "$(pwd)/../tmpl" &&
|
||||
git clone ../tmpl/hooks hook-run-local-config 2>err &&
|
||||
git config --unset init.templateDir &&
|
||||
! grep "active .* hook found" err &&
|
||||
test_path_is_missing hook-run-local-config/hook.run
|
||||
)
|
||||
'
|
||||
|
||||
. "$TEST_DIRECTORY"/lib-httpd.sh
|
||||
start_httpd
|
||||
|
||||
|
|
Loading…
Reference in a new issue