Skip to content

Commit 8d1a744

Browse files
chooglengitster
authored andcommitted
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/[email protected] [2]: https://lore.kernel.org/git/[email protected] Signed-off-by: Glen Choo <[email protected]> Signed-off-by: Junio C Hamano <[email protected]>
1 parent 6061601 commit 8d1a744

File tree

3 files changed

+129
-1
lines changed

3 files changed

+129
-1
lines changed

Documentation/config/safe.txt

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,22 @@
1+
safe.bareRepository::
2+
Specifies which bare repositories Git will work with. The currently
3+
supported values are:
4+
+
5+
* `all`: Git works with all bare repositories. This is the default.
6+
* `explicit`: Git only works with bare repositories specified via
7+
the top-level `--git-dir` command-line option, or the `GIT_DIR`
8+
environment variable (see linkgit:git[1]).
9+
+
10+
If you do not use bare repositories in your workflow, then it may be
11+
beneficial to set `safe.bareRepository` to `explicit` in your global
12+
config. This will protect you from attacks that involve cloning a
13+
repository that contains a bare repository and running a Git command
14+
within that directory.
15+
+
16+
This config setting is only respected in protected configuration (see
17+
<<SCOPES>>). This prevents the untrusted repository from tampering with
18+
this value.
19+
120
safe.directory::
221
These config entries specify Git-tracked directories that are
322
considered safe even if they are owned by someone other than the

setup.c

Lines changed: 56 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,10 @@
1010
static int inside_git_dir = -1;
1111
static int inside_work_tree = -1;
1212
static int work_tree_config_is_bogus;
13+
enum allowed_bare_repo {
14+
ALLOWED_BARE_REPO_EXPLICIT = 0,
15+
ALLOWED_BARE_REPO_ALL,
16+
};
1317

1418
static struct startup_info the_startup_info;
1519
struct startup_info *startup_info = &the_startup_info;
@@ -1160,6 +1164,46 @@ static int ensure_valid_ownership(const char *gitfile,
11601164
return data.is_safe;
11611165
}
11621166

1167+
static int allowed_bare_repo_cb(const char *key, const char *value, void *d)
1168+
{
1169+
enum allowed_bare_repo *allowed_bare_repo = d;
1170+
1171+
if (strcasecmp(key, "safe.bareRepository"))
1172+
return 0;
1173+
1174+
if (!strcmp(value, "explicit")) {
1175+
*allowed_bare_repo = ALLOWED_BARE_REPO_EXPLICIT;
1176+
return 0;
1177+
}
1178+
if (!strcmp(value, "all")) {
1179+
*allowed_bare_repo = ALLOWED_BARE_REPO_ALL;
1180+
return 0;
1181+
}
1182+
return -1;
1183+
}
1184+
1185+
static enum allowed_bare_repo get_allowed_bare_repo(void)
1186+
{
1187+
enum allowed_bare_repo result = ALLOWED_BARE_REPO_ALL;
1188+
git_protected_config(allowed_bare_repo_cb, &result);
1189+
return result;
1190+
}
1191+
1192+
static const char *allowed_bare_repo_to_string(
1193+
enum allowed_bare_repo allowed_bare_repo)
1194+
{
1195+
switch (allowed_bare_repo) {
1196+
case ALLOWED_BARE_REPO_EXPLICIT:
1197+
return "explicit";
1198+
case ALLOWED_BARE_REPO_ALL:
1199+
return "all";
1200+
default:
1201+
BUG("invalid allowed_bare_repo %d",
1202+
allowed_bare_repo);
1203+
}
1204+
return NULL;
1205+
}
1206+
11631207
enum discovery_result {
11641208
GIT_DIR_NONE = 0,
11651209
GIT_DIR_EXPLICIT,
@@ -1169,7 +1213,8 @@ enum discovery_result {
11691213
GIT_DIR_HIT_CEILING = -1,
11701214
GIT_DIR_HIT_MOUNT_POINT = -2,
11711215
GIT_DIR_INVALID_GITFILE = -3,
1172-
GIT_DIR_INVALID_OWNERSHIP = -4
1216+
GIT_DIR_INVALID_OWNERSHIP = -4,
1217+
GIT_DIR_DISALLOWED_BARE = -5,
11731218
};
11741219

11751220
/*
@@ -1297,6 +1342,8 @@ static enum discovery_result setup_git_directory_gently_1(struct strbuf *dir,
12971342
}
12981343

12991344
if (is_git_directory(dir->buf)) {
1345+
if (get_allowed_bare_repo() == ALLOWED_BARE_REPO_EXPLICIT)
1346+
return GIT_DIR_DISALLOWED_BARE;
13001347
if (!ensure_valid_ownership(NULL, NULL, dir->buf))
13011348
return GIT_DIR_INVALID_OWNERSHIP;
13021349
strbuf_addstr(gitdir, ".");
@@ -1443,6 +1490,14 @@ const char *setup_git_directory_gently(int *nongit_ok)
14431490
}
14441491
*nongit_ok = 1;
14451492
break;
1493+
case GIT_DIR_DISALLOWED_BARE:
1494+
if (!nongit_ok) {
1495+
die(_("cannot use bare repository '%s' (safe.bareRepository is '%s')"),
1496+
dir.buf,
1497+
allowed_bare_repo_to_string(get_allowed_bare_repo()));
1498+
}
1499+
*nongit_ok = 1;
1500+
break;
14461501
case GIT_DIR_NONE:
14471502
/*
14481503
* As a safeguard against setup_git_directory_gently_1 returning

t/t0035-safe-bare-repository.sh

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
#!/bin/sh
2+
3+
test_description='verify safe.bareRepository checks'
4+
5+
TEST_PASSES_SANITIZE_LEAK=true
6+
. ./test-lib.sh
7+
8+
pwd="$(pwd)"
9+
10+
expect_accepted () {
11+
git "$@" rev-parse --git-dir
12+
}
13+
14+
expect_rejected () {
15+
test_must_fail git "$@" rev-parse --git-dir 2>err &&
16+
grep -F "cannot use bare repository" err
17+
}
18+
19+
test_expect_success 'setup bare repo in worktree' '
20+
git init outer-repo &&
21+
git init --bare outer-repo/bare-repo
22+
'
23+
24+
test_expect_success 'safe.bareRepository unset' '
25+
expect_accepted -C outer-repo/bare-repo
26+
'
27+
28+
test_expect_success 'safe.bareRepository=all' '
29+
test_config_global safe.bareRepository all &&
30+
expect_accepted -C outer-repo/bare-repo
31+
'
32+
33+
test_expect_success 'safe.bareRepository=explicit' '
34+
test_config_global safe.bareRepository explicit &&
35+
expect_rejected -C outer-repo/bare-repo
36+
'
37+
38+
test_expect_success 'safe.bareRepository in the repository' '
39+
# safe.bareRepository must not be "explicit", otherwise
40+
# git config fails with "fatal: not in a git directory" (like
41+
# safe.directory)
42+
test_config -C outer-repo/bare-repo safe.bareRepository \
43+
all &&
44+
test_config_global safe.bareRepository explicit &&
45+
expect_rejected -C outer-repo/bare-repo
46+
'
47+
48+
test_expect_success 'safe.bareRepository on the command line' '
49+
test_config_global safe.bareRepository explicit &&
50+
expect_accepted -C outer-repo/bare-repo \
51+
-c safe.bareRepository=all
52+
'
53+
54+
test_done

0 commit comments

Comments
 (0)