[PATCH 2/2] setup: make bareRepository=explicit work in GIT_DIR of a secondary worktree

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



If you have /var/tmp/primary/ as a repository, and if you create a
secondary worktree of it at /var/tmp/secondary/, the layout would
look like this:

    $ cd /var/tmp/
    $ git init primary
    $ cd primary
    $ pwd
    /var/tmp/primary
    $ git worktree add ../secondary
    $ cat ../seconary/.git
    gitdir: /var/tmp/primary/.git/worktrees/secondary
    $ ls /var/tmp/primary/.git/worktrees/secondary
    commondir  gitdir  HEAD  index  refs
    $ cat /var/tmp/primary/.git/worktrees/secondary/gitdir
    /var/tmp/secondary/.git

When the configuration variable 'safe.bareRepository=explicit' is
set to explicit, the change made by 45bb9162 (setup: allow cwd=.git
w/ bareRepository=explicit, 2024-01-20) allows you to work in the
/var/tmp/primary/.git directory (i.e., $GIT_DIR of the primary
worktree).  The idea is that if it is safe to work in the repository
in its working tree, it should be equally safe to work in the
".git/" directory of that working tree, too.

Now, for the same reason, let's allow command execution from within
the $GIT_DIR directory of a secondary worktree.  This is useful for
tools working with secondary worktrees when the 'bareRepository'
setting is set to 'explicit'.

In the previous commit, we created a helper function to house the
logic that checks if a directory that looks like a bare repository
is actually a part of a non-bare repository.  Extend the helper
function to also check if the apparent bare-repository is a $GIT_DIR
of a secondary worktree, by checking three things:

 * The path to the $GIT_DIR must be a subdirectory of
   ".git/worktrees/", which is the primary worktree [*].

 * Such $GIT_DIR must have file "gitdir", that records the path of
   the ".git" file that is at the root level of the secondary
   worktree.

 * That ".git" file in turn points back at the $GIT_DIR we are
   inspecting.

The latter two points are merely for checking sanity.  The security
lies in the first requirement.

Remember that a tree object with an entry whose pathname component
is ".git" is forbidden at various levels (fsck, object transfer and
checkout), so malicious projects cannot cause users to clone and
checkout a crafted ".git" directory in a shell directory that
pretends to be a working tree with that ".git" thing at its root
level.  That is where 45bb9162 (setup: allow cwd=.git w/
bareRepository=explicit, 2024-01-20) draws its security guarantee
from.  And the solution for secondary worktrees in this commit draws
its security guarantee from the same place.

Signed-off-by: Junio C Hamano <gitster@xxxxxxxxx>
---
 setup.c                         | 52 ++++++++++++++++++++++++++++++++-
 t/t0035-safe-bare-repository.sh |  8 ++++-
 2 files changed, 58 insertions(+), 2 deletions(-)

diff --git a/setup.c b/setup.c
index 3081be4970..68860dcd18 100644
--- a/setup.c
+++ b/setup.c
@@ -1231,9 +1231,59 @@ static const char *allowed_bare_repo_to_string(
 	return NULL;
 }
 
+static int is_git_dir_of_secondary_worktree(const char *path)
+{
+	int result = 0; /* assume not */
+	struct strbuf gitfile_here = STRBUF_INIT;
+	struct strbuf gitfile_there = STRBUF_INIT;
+	const char *gitfile_contents;
+	int error_code = 0;
+
+	/*
+	 * We should be a subdirectory of /.git/worktrees inside
+	 * the $GIT_DIR of the primary worktree.
+	 *
+	 * NEEDSWORK: some folks create secondary worktrees out of a
+	 * bare repository; they don't count ;-), at least not yet.
+	 */
+	if (!strstr(path, "/.git/worktrees/"))
+		goto out;
+
+	/*
+	 * Does gitdir that points at the ".git" file at the root of
+	 * the secondary worktree roundtrip here?
+	 */
+	strbuf_addf(&gitfile_here, "%s/gitdir", path);
+	if (!file_exists(gitfile_here.buf))
+		goto out;
+	if (strbuf_read_file(&gitfile_there, gitfile_here.buf, 0) < 0)
+		goto out;
+	strbuf_trim_trailing_newline(&gitfile_there);
+
+	gitfile_contents = read_gitfile_gently(gitfile_there.buf, &error_code);
+	if ((!gitfile_contents) || strcmp(gitfile_contents, path))
+		goto out;
+
+	/* OK, we are happy */
+	result = 1;
+
+out:
+	strbuf_release(&gitfile_here);
+	strbuf_release(&gitfile_there);
+	return result;
+}
+
 static int is_repo_with_working_tree(const char *path)
 {
-	return ends_with_path_components(path, ".git");
+	/* $GIT_DIR immediately below the primary working tree */
+	if (ends_with_path_components(path, ".git"))
+		return 1;
+
+	/* Are we in $GIT_DIR of a secondary worktree? */
+	if (is_git_dir_of_secondary_worktree(path))
+		return 1;
+
+	return 0;
 }
 
 /*
diff --git a/t/t0035-safe-bare-repository.sh b/t/t0035-safe-bare-repository.sh
index 8048856379..62cdfcefc1 100755
--- a/t/t0035-safe-bare-repository.sh
+++ b/t/t0035-safe-bare-repository.sh
@@ -31,7 +31,9 @@ expect_rejected () {
 
 test_expect_success 'setup bare repo in worktree' '
 	git init outer-repo &&
-	git init --bare outer-repo/bare-repo
+	git init --bare outer-repo/bare-repo &&
+	git -C outer-repo worktree add ../outer-secondary &&
+	test_path_is_dir outer-secondary
 '
 
 test_expect_success 'safe.bareRepository unset' '
@@ -86,4 +88,8 @@ test_expect_success 'no trace when "bare repository" is a subdir of .git' '
 	expect_accepted_implicit -C outer-repo/.git/objects
 '
 
+test_expect_success 'no trace in $GIT_DIR of secondary worktree' '
+	expect_accepted_implicit -C outer-repo/.git/worktrees/outer-secondary
+'
+
 test_done
-- 
2.44.0-165-ge09f1254c5





[Index of Archives]     [Linux Kernel Development]     [Gcc Help]     [IETF Annouce]     [DCCP]     [Netdev]     [Networking]     [Security]     [V4L]     [Bugtraq]     [Yosemite]     [MIPS Linux]     [ARM Linux]     [Linux Security]     [Linux RAID]     [Linux SCSI]     [Fedora Users]

  Powered by Linux