setup.c: create safe.bareRepository
There is a known social engineering attack that takes advantage of the fact that a working tree can include an entire bare repository, including a config file. A user could run a Git command inside the bare repository thinking that the config file of the 'outer' repository would be used, but in reality, the bare repository's config file (which is attacker-controlled) is used, which may result in arbitrary code execution. See [1] for a fuller description and deeper discussion. A simple mitigation is to forbid bare repositories unless specified via `--git-dir` or `GIT_DIR`. In environments that don't use bare repositories, this would be minimally disruptive. Create a config variable, `safe.bareRepository`, that tells Git whether or not to die() when working with a bare repository. This config is an enum of: - "all": allow all bare repositories (this is the default) - "explicit": only allow bare repositories specified via --git-dir or GIT_DIR. If we want to protect users from such attacks by default, neither value will suffice - "all" provides no protection, but "explicit" is impractical for bare repository users. A more usable default would be to allow only non-embedded bare repositories ([2] contains one such proposal), but detecting if a repository is embedded is potentially non-trivial, so this work is not implemented in this series. [1]: https://lore.kernel.org/git/kl6lsfqpygsj.fsf@chooglen-macbookpro.roam.corp.google.com [2]: https://lore.kernel.org/git/5b969c5e-e802-c447-ad25-6acc0b784582@github.com Signed-off-by: Glen Choo <chooglen@google.com> Signed-off-by: Junio C Hamano <gitster@pobox.com>
This commit is contained in:
parent
6061601d9f
commit
8d1a744820
@ -1,3 +1,22 @@
|
||||
safe.bareRepository::
|
||||
Specifies which bare repositories Git will work with. The currently
|
||||
supported values are:
|
||||
+
|
||||
* `all`: Git works with all bare repositories. This is the default.
|
||||
* `explicit`: Git only works with bare repositories specified via
|
||||
the top-level `--git-dir` command-line option, or the `GIT_DIR`
|
||||
environment variable (see linkgit:git[1]).
|
||||
+
|
||||
If you do not use bare repositories in your workflow, then it may be
|
||||
beneficial to set `safe.bareRepository` to `explicit` in your global
|
||||
config. This will protect you from attacks that involve cloning a
|
||||
repository that contains a bare repository and running a Git command
|
||||
within that directory.
|
||||
+
|
||||
This config setting is only respected in protected configuration (see
|
||||
<<SCOPES>>). This prevents the untrusted repository from tampering with
|
||||
this value.
|
||||
|
||||
safe.directory::
|
||||
These config entries specify Git-tracked directories that are
|
||||
considered safe even if they are owned by someone other than the
|
||||
|
57
setup.c
57
setup.c
@ -10,6 +10,10 @@
|
||||
static int inside_git_dir = -1;
|
||||
static int inside_work_tree = -1;
|
||||
static int work_tree_config_is_bogus;
|
||||
enum allowed_bare_repo {
|
||||
ALLOWED_BARE_REPO_EXPLICIT = 0,
|
||||
ALLOWED_BARE_REPO_ALL,
|
||||
};
|
||||
|
||||
static struct startup_info the_startup_info;
|
||||
struct startup_info *startup_info = &the_startup_info;
|
||||
@ -1160,6 +1164,46 @@ static int ensure_valid_ownership(const char *gitfile,
|
||||
return data.is_safe;
|
||||
}
|
||||
|
||||
static int allowed_bare_repo_cb(const char *key, const char *value, void *d)
|
||||
{
|
||||
enum allowed_bare_repo *allowed_bare_repo = d;
|
||||
|
||||
if (strcasecmp(key, "safe.bareRepository"))
|
||||
return 0;
|
||||
|
||||
if (!strcmp(value, "explicit")) {
|
||||
*allowed_bare_repo = ALLOWED_BARE_REPO_EXPLICIT;
|
||||
return 0;
|
||||
}
|
||||
if (!strcmp(value, "all")) {
|
||||
*allowed_bare_repo = ALLOWED_BARE_REPO_ALL;
|
||||
return 0;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
static enum allowed_bare_repo get_allowed_bare_repo(void)
|
||||
{
|
||||
enum allowed_bare_repo result = ALLOWED_BARE_REPO_ALL;
|
||||
git_protected_config(allowed_bare_repo_cb, &result);
|
||||
return result;
|
||||
}
|
||||
|
||||
static const char *allowed_bare_repo_to_string(
|
||||
enum allowed_bare_repo allowed_bare_repo)
|
||||
{
|
||||
switch (allowed_bare_repo) {
|
||||
case ALLOWED_BARE_REPO_EXPLICIT:
|
||||
return "explicit";
|
||||
case ALLOWED_BARE_REPO_ALL:
|
||||
return "all";
|
||||
default:
|
||||
BUG("invalid allowed_bare_repo %d",
|
||||
allowed_bare_repo);
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
enum discovery_result {
|
||||
GIT_DIR_NONE = 0,
|
||||
GIT_DIR_EXPLICIT,
|
||||
@ -1169,7 +1213,8 @@ enum discovery_result {
|
||||
GIT_DIR_HIT_CEILING = -1,
|
||||
GIT_DIR_HIT_MOUNT_POINT = -2,
|
||||
GIT_DIR_INVALID_GITFILE = -3,
|
||||
GIT_DIR_INVALID_OWNERSHIP = -4
|
||||
GIT_DIR_INVALID_OWNERSHIP = -4,
|
||||
GIT_DIR_DISALLOWED_BARE = -5,
|
||||
};
|
||||
|
||||
/*
|
||||
@ -1297,6 +1342,8 @@ static enum discovery_result setup_git_directory_gently_1(struct strbuf *dir,
|
||||
}
|
||||
|
||||
if (is_git_directory(dir->buf)) {
|
||||
if (get_allowed_bare_repo() == ALLOWED_BARE_REPO_EXPLICIT)
|
||||
return GIT_DIR_DISALLOWED_BARE;
|
||||
if (!ensure_valid_ownership(NULL, NULL, dir->buf))
|
||||
return GIT_DIR_INVALID_OWNERSHIP;
|
||||
strbuf_addstr(gitdir, ".");
|
||||
@ -1443,6 +1490,14 @@ const char *setup_git_directory_gently(int *nongit_ok)
|
||||
}
|
||||
*nongit_ok = 1;
|
||||
break;
|
||||
case GIT_DIR_DISALLOWED_BARE:
|
||||
if (!nongit_ok) {
|
||||
die(_("cannot use bare repository '%s' (safe.bareRepository is '%s')"),
|
||||
dir.buf,
|
||||
allowed_bare_repo_to_string(get_allowed_bare_repo()));
|
||||
}
|
||||
*nongit_ok = 1;
|
||||
break;
|
||||
case GIT_DIR_NONE:
|
||||
/*
|
||||
* As a safeguard against setup_git_directory_gently_1 returning
|
||||
|
54
t/t0035-safe-bare-repository.sh
Executable file
54
t/t0035-safe-bare-repository.sh
Executable file
@ -0,0 +1,54 @@
|
||||
#!/bin/sh
|
||||
|
||||
test_description='verify safe.bareRepository checks'
|
||||
|
||||
TEST_PASSES_SANITIZE_LEAK=true
|
||||
. ./test-lib.sh
|
||||
|
||||
pwd="$(pwd)"
|
||||
|
||||
expect_accepted () {
|
||||
git "$@" rev-parse --git-dir
|
||||
}
|
||||
|
||||
expect_rejected () {
|
||||
test_must_fail git "$@" rev-parse --git-dir 2>err &&
|
||||
grep -F "cannot use bare repository" err
|
||||
}
|
||||
|
||||
test_expect_success 'setup bare repo in worktree' '
|
||||
git init outer-repo &&
|
||||
git init --bare outer-repo/bare-repo
|
||||
'
|
||||
|
||||
test_expect_success 'safe.bareRepository unset' '
|
||||
expect_accepted -C outer-repo/bare-repo
|
||||
'
|
||||
|
||||
test_expect_success 'safe.bareRepository=all' '
|
||||
test_config_global safe.bareRepository all &&
|
||||
expect_accepted -C outer-repo/bare-repo
|
||||
'
|
||||
|
||||
test_expect_success 'safe.bareRepository=explicit' '
|
||||
test_config_global safe.bareRepository explicit &&
|
||||
expect_rejected -C outer-repo/bare-repo
|
||||
'
|
||||
|
||||
test_expect_success 'safe.bareRepository in the repository' '
|
||||
# safe.bareRepository must not be "explicit", otherwise
|
||||
# git config fails with "fatal: not in a git directory" (like
|
||||
# safe.directory)
|
||||
test_config -C outer-repo/bare-repo safe.bareRepository \
|
||||
all &&
|
||||
test_config_global safe.bareRepository explicit &&
|
||||
expect_rejected -C outer-repo/bare-repo
|
||||
'
|
||||
|
||||
test_expect_success 'safe.bareRepository on the command line' '
|
||||
test_config_global safe.bareRepository explicit &&
|
||||
expect_accepted -C outer-repo/bare-repo \
|
||||
-c safe.bareRepository=all
|
||||
'
|
||||
|
||||
test_done
|
Loading…
Reference in New Issue
Block a user