setup: notice more types of implicit bare repositories

Setting the safe.bareRepository configuration variable to explicit stops git from using a bare repository, unless the repository is explicitly specified, either by the "--git-dir=<path>" command line option, or by exporting $GIT_DIR environment variable. This may be a reasonable measure to safeguard users from accidentally straying into a bare repository in unexpected places, but often gets in the way of users who need valid accesses to the repository. Earlier, 45bb9162 (setup: allow cwd=.git w/ bareRepository=explicit, 2024-01-20) loosened the rule such that being inside the ".git" directory of a non-bare repository does not really count as accessing a "bare" repository. The reason why such a loosening is needed is because often hooks and third-party tools run from within $GIT_DIR while working with a non-bare repository. More importantly, the reason why this is safe is because a directory whose contents look like that of a "bare" repository cannot be a bare repository that came embedded within a checkout of a malicious project, as long as its directory name is ".git", because ".git" is not a name allowed for a directory in payload. There are at least two other cases where tools have to work in a bare-repository looking directory that is not an embedded bare repository, and accesses to them are still not allowed by the recent change. - A secondary worktree (whose name is $name) has its $GIT_DIR inside "worktrees/$name/" subdirectory of the $GIT_DIR of the primary worktree of the same repository. - A submodule worktree (whose name is $name) has its $GIT_DIR inside "modules/$name/" subdirectory of the $GIT_DIR of its superproject. As long as the primary worktree or the superproject in these cases are not bare, the pathname of these "looks like bare but not really" directories will have "/.git/worktrees/" and "/.git/modules/" as a substring in its leading part, and we can take advantage of the same security guarantee allow git to work from these places. Extend the earlier "in a directory called '.git' we are OK" logic used for the primary worktree to also cover the secondary worktree's and non-embedded submodule's $GIT_DIR, by moving the logic to a helper function "is_implicit_bare_repo()". We deliberately exclude secondary worktrees and submodules of a bare repository, as these are exactly what safe.bareRepository=explicit setting is designed to forbid accesses to without an explicit GIT_DIR/--git-dir=<path> Helped-by: Kyle Lippincott <spectral@google.com> Helped-by: Kyle Meyer <kyle@kyleam.com> Signed-off-by: Junio C Hamano <gitster@pobox.com>

Junio C Hamano committed Mar 9, 2024 at 15:27 UTC 30b7c4bdcac27ac08a6caf2c70aff55763b9b3ab
2 files changed +49 -5
setup.c
+27 -1
@@ -1231,6 +1231,32 @@ static const char *allowed_bare_repo_to_string(
1231 return NULL;
1232 }
1233
1234 +static int is_implicit_bare_repo(const char *path)
1235 +{
1236 + /*
1237 + * what we found is a ".git" directory at the root of
1238 + * the working tree.
1239 + */
1240 + if (ends_with_path_components(path, ".git"))
1241 + return 1;
1242 +
1243 + /*
1244 + * we are inside $GIT_DIR of a secondary worktree of a
1245 + * non-bare repository.
1246 + */
1247 + if (strstr(path, "/.git/worktrees/"))
1248 + return 1;
1249 +
1250 + /*
1251 + * we are inside $GIT_DIR of a worktree of a non-embedded
1252 + * submodule, whose superproject is not a bare repository.
1253 + */
1254 + if (strstr(path, "/.git/modules/"))
1255 + return 1;
1256 +
1257 + return 0;
1258 +}
1259 +
1260 /*
1261 * We cannot decide in this function whether we are in the work tree or
1262 * not, since the config can only be read _after_ this function was called.
@@ -1360,7 +1386,7 @@ static enum discovery_result setup_git_directory_gently_1(struct strbuf *dir,
1386 if (is_git_directory(dir->buf)) {
1387 trace2_data_string("setup", NULL, "implicit-bare-repository", dir->buf);
1388 if (get_allowed_bare_repo() == ALLOWED_BARE_REPO_EXPLICIT &&
1363 - !ends_with_path_components(dir->buf, ".git"))
1389 + !is_implicit_bare_repo(dir->buf))
1390 return GIT_DIR_DISALLOWED_BARE;
1391 if (!ensure_valid_ownership(NULL, NULL, dir->buf, report))
1392 return GIT_DIR_INVALID_OWNERSHIP;
t/t0035-safe-bare-repository.sh
+22 -4
@@ -29,9 +29,20 @@ expect_rejected () {
29 grep -F "implicit-bare-repository:$pwd" "$pwd/trace.perf"
30 }
31
32 -test_expect_success 'setup bare repo in worktree' '
32 +test_expect_success 'setup an embedded bare repo, secondary worktree and submodule' '
33 git init outer-repo &&
34 - git init --bare outer-repo/bare-repo
34 + git init --bare --initial-branch=main outer-repo/bare-repo &&
35 + git -C outer-repo worktree add ../outer-secondary &&
36 + test_path_is_dir outer-secondary &&
37 + (
38 + cd outer-repo &&
39 + test_commit A &&
40 + git push bare-repo +HEAD:refs/heads/main &&
41 + git -c protocol.file.allow=always \
42 + submodule add --name subn -- ./bare-repo subd
43 + ) &&
44 + test_path_is_dir outer-repo/.git/worktrees/outer-secondary &&
45 + test_path_is_dir outer-repo/.git/modules/subn
46 '
47
48 test_expect_success 'safe.bareRepository unset' '
@@ -53,8 +64,7 @@ test_expect_success 'safe.bareRepository in the repository' '
64 # safe.bareRepository must not be "explicit", otherwise
65 # git config fails with "fatal: not in a git directory" (like
66 # safe.directory)
56 - test_config -C outer-repo/bare-repo safe.bareRepository \
57 - all &&
67 + test_config -C outer-repo/bare-repo safe.bareRepository all &&
68 test_config_global safe.bareRepository explicit &&
69 expect_rejected -C outer-repo/bare-repo
70 '
@@ -86,4 +96,12 @@ test_expect_success 'no trace when "bare repository" is a subdir of .git' '
96 expect_accepted_implicit -C outer-repo/.git/objects
97 '
98
99 +test_expect_success 'no trace in $GIT_DIR of secondary worktree' '
100 + expect_accepted_implicit -C outer-repo/.git/worktrees/outer-secondary
101 +'
102 +
103 +test_expect_success 'no trace in $GIT_DIR of a submodule' '
104 + expect_accepted_implicit -C outer-repo/.git/modules/subn
105 +'
106 +
107 test_done