Re: [PATCH 2/2] setup: make bareRepository=explicit work in GIT_DIR of a secondary worktree
- From
Kyle Lippincott <spectral@google.com>
- Date
- Mar 8, 2024, 23:10 UTC
- Message-ID
- <CAO_smVjrKJeKr7QgQWryZRErStFk=Y+1T=dwrR_boXQD_X9_Mg@mail.gmail.com>
- In-Reply-To
- <20240308211957.3758770-3-gitster@pobox.com>
On Fri, Mar 8, 2024 at 1:20 PM Junio C Hamano <gitster@pobox.com> wrote:
Show 12 quoted lines
> > 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
Nit: typo, should be `secondary` (missing the `d`)
Show 78 quoted lines
> 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@pobox.com>
> ---
> 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/"))Do we need to be concerned about path separators being different on Windows? Or have they already been normalized here?
Show 6 quoted lines
> + goto out; > + > + /* > + * Does gitdir that points at the ".git" file at the root of > + * the secondary worktree roundtrip here? > + */
What loss of security do we have if we don't have as stringent of a check? i.e. if we just did `return !!strstr(path, "/.git/worktrees/)`? Or maybe we even combine the existing ends_with(.git) check with this and do something like:
static int is_under_dotgit_dir(const char *path) {
char *dotgit = strstr(path, "/.git");
return dotgit && (dotgit[5] == '\0' || dotgit[5] == '/');
}Show 62 quoted lines
> + 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
>