diff mbox series

setup: allow cwd=.git w/ bareRepository=explicit

Message ID pull.1645.git.1705709303098.gitgitgadget@gmail.com (mailing list archive)
State Accepted
Commit 45bb91624804d3e3a70cfc1ba0eae5577f81fc38
Headers show
Series setup: allow cwd=.git w/ bareRepository=explicit | expand

Commit Message

Kyle Lippincott Jan. 20, 2024, 12:08 a.m. UTC
From: Kyle Lippincott <spectral@google.com>

The safe.bareRepository setting can be set to 'explicit' to disallow
implicit uses of bare repositories, preventing an attack [1] where an
artificial and malicious bare repository is embedded in another git
repository. Unfortunately, some tooling uses myrepo/.git/ as the cwd
when executing commands, and this is blocked when
safe.bareRepository=explicit. Blocking is unnecessary, as git already
prevents nested .git directories.

Teach git to not reject uses of git inside of the .git directory: check
if cwd is .git (or a subdirectory of it) and allow it even if
safe.bareRepository=explicit.

[1] https://github.com/justinsteven/advisories/blob/main/2022_git_buried_bare_repos_and_fsmonitor_various_abuses.md

Signed-off-by: Kyle Lippincott <spectral@google.com>
---
    setup: allow cwd=.git w/ bareRepository=explicit
    
    Please be aware that I'm a new contributor (this is my first patch to
    git's code), so any style nits, suggestions about how to make this more
    idiomatic, or any other suggestions are strongly encouraged.
    
    My primary concern with this patch is that I'm unsure if we need to
    worry about case-insensitive filesystems (ex: cwd=my_repo/.GIT instead
    of my_repo/.git, it might not trigger this logic and end up allowed).
    I'm assuming this isn't a significant concern, for two reasons:
    
     * most filesystems/OSes in use today (by number of users) are at least
       case-preserving, so users/tools will have had to type out .GIT
       instead of getting it from readdir/wherever.
     * this is primarily a "quality of life" change to the feature, and if
       we get it wrong we still fail closed.

Published-As: https://github.com/gitgitgadget/git/releases/tag/pr-1645%2Fspectral54%2Fbare-repo-fix-v1
Fetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-1645/spectral54/bare-repo-fix-v1
Pull-Request: https://github.com/gitgitgadget/git/pull/1645

 setup.c                         | 3 ++-
 t/t0035-safe-bare-repository.sh | 8 ++++++++
 2 files changed, 10 insertions(+), 1 deletion(-)


base-commit: 186b115d3062e6230ee296d1ddaa0c4b72a464b5

Comments

Junio C Hamano Jan. 20, 2024, 10:26 p.m. UTC | #1
"Kyle Lippincott via GitGitGadget" <gitgitgadget@gmail.com> writes:

> From: Kyle Lippincott <spectral@google.com>
>
> The safe.bareRepository setting can be set to 'explicit' to disallow
> implicit uses of bare repositories, preventing an attack [1] where an
> artificial and malicious bare repository is embedded in another git
> repository. Unfortunately, some tooling uses myrepo/.git/ as the cwd
> when executing commands, and this is blocked when
> safe.bareRepository=explicit. Blocking is unnecessary, as git already
> prevents nested .git directories.

In other words, if the directory $D that is the top level of the
working tree of a non-bare repository, you should be able to chdir
to "$D/.git" and run your git command without explicitly saying that
you are inside $GIT_DIR (e.g. "git --git-dir=$(pwd) cmd")?

It makes very good sense.

I briefly wondered if this would give us a great usability
improvement especially for hook scripts, but they are given GIT_DIR
when called already so that is not a big upside, I guess.

> Teach git to not reject uses of git inside of the .git directory: check
> if cwd is .git (or a subdirectory of it) and allow it even if
> safe.bareRepository=explicit.


>     My primary concern with this patch is that I'm unsure if we need to
>     worry about case-insensitive filesystems (ex: cwd=my_repo/.GIT instead
>     of my_repo/.git, it might not trigger this logic and end up allowed).

You are additionally allowing ".git" so even if somebody has ".GIT"
that won't be allowed by this change, no?

>     I'm assuming this isn't a significant concern, for two reasons:
>     
>      * most filesystems/OSes in use today (by number of users) are at least
>        case-preserving, so users/tools will have had to type out .GIT
>        instead of getting it from readdir/wherever.
>      * this is primarily a "quality of life" change to the feature, and if
>        we get it wrong we still fail closed.

Yup.

If we really cared (which I doubt), we could resort to checking with
is_ntfs_dotgit() and is_hfs_dotgit(), but that would work in the
direction of loosening the check even further, which I do not know
is needed.

> -			if (get_allowed_bare_repo() == ALLOWED_BARE_REPO_EXPLICIT)
> +			if (get_allowed_bare_repo() == ALLOWED_BARE_REPO_EXPLICIT &&
> +			    !ends_with_path_components(dir->buf, ".git"))
>  				return GIT_DIR_DISALLOWED_BARE;

Thanks.
Kyle Lippincott Jan. 22, 2024, 8:50 p.m. UTC | #2
On Sat, Jan 20, 2024 at 2:26 PM Junio C Hamano <gitster@pobox.com> wrote:
>
> "Kyle Lippincott via GitGitGadget" <gitgitgadget@gmail.com> writes:
>
> > From: Kyle Lippincott <spectral@google.com>
> >
> > The safe.bareRepository setting can be set to 'explicit' to disallow
> > implicit uses of bare repositories, preventing an attack [1] where an
> > artificial and malicious bare repository is embedded in another git
> > repository. Unfortunately, some tooling uses myrepo/.git/ as the cwd
> > when executing commands, and this is blocked when
> > safe.bareRepository=explicit. Blocking is unnecessary, as git already
> > prevents nested .git directories.
>
> In other words, if the directory $D that is the top level of the
> working tree of a non-bare repository, you should be able to chdir
> to "$D/.git" and run your git command without explicitly saying that
> you are inside $GIT_DIR (e.g. "git --git-dir=$(pwd) cmd")?

Correct.

>
> It makes very good sense.
>
> I briefly wondered if this would give us a great usability
> improvement especially for hook scripts, but they are given GIT_DIR
> when called already so that is not a big upside, I guess.
>
> > Teach git to not reject uses of git inside of the .git directory: check
> > if cwd is .git (or a subdirectory of it) and allow it even if
> > safe.bareRepository=explicit.
>
>
> >     My primary concern with this patch is that I'm unsure if we need to
> >     worry about case-insensitive filesystems (ex: cwd=my_repo/.GIT instead
> >     of my_repo/.git, it might not trigger this logic and end up allowed).
>
> You are additionally allowing ".git" so even if somebody has ".GIT"
> that won't be allowed by this change, no?

My concern was what happens if someone on a case-insensitive
filesystem does `cd .GIT` and then attempts to use it. If the cwd path
isn't case-normalized at some point, we'll have a string like
/path/to/my-repo/.GIT from getcwd() and it won't be allowed by this
code, since this code is checking for `.git` in a case sensitive
fashion.

>
> >     I'm assuming this isn't a significant concern, for two reasons:
> >
> >      * most filesystems/OSes in use today (by number of users) are at least
> >        case-preserving, so users/tools will have had to type out .GIT
> >        instead of getting it from readdir/wherever.
> >      * this is primarily a "quality of life" change to the feature, and if
> >        we get it wrong we still fail closed.
>
> Yup.
>
> If we really cared (which I doubt), we could resort to checking with
> is_ntfs_dotgit() and is_hfs_dotgit(), but that would work in the
> direction of loosening the check even further, which I do not know
> is needed.

Agreed, it's not worth the additional complexity.

>
> > -                     if (get_allowed_bare_repo() == ALLOWED_BARE_REPO_EXPLICIT)
> > +                     if (get_allowed_bare_repo() == ALLOWED_BARE_REPO_EXPLICIT &&
> > +                         !ends_with_path_components(dir->buf, ".git"))
> >                               return GIT_DIR_DISALLOWED_BARE;
>
> Thanks.
Junio C Hamano March 6, 2024, 5:27 p.m. UTC | #3
"Kyle Lippincott via GitGitGadget" <gitgitgadget@gmail.com> writes:

> Teach git to not reject uses of git inside of the .git directory: check
> if cwd is .git (or a subdirectory of it) and allow it even if
> safe.bareRepository=explicit.

> diff --git a/setup.c b/setup.c
> index b38702718fb..b095e284979 100644
> --- a/setup.c
> +++ b/setup.c
> @@ -1371,7 +1371,8 @@ static enum discovery_result setup_git_directory_gently_1(struct strbuf *dir,
>  
>  		if (is_git_directory(dir->buf)) {
>  			trace2_data_string("setup", NULL, "implicit-bare-repository", dir->buf);
> -			if (get_allowed_bare_repo() == ALLOWED_BARE_REPO_EXPLICIT)
> +			if (get_allowed_bare_repo() == ALLOWED_BARE_REPO_EXPLICIT &&
> +			    !ends_with_path_components(dir->buf, ".git"))
>  				return GIT_DIR_DISALLOWED_BARE;
>  			if (!ensure_valid_ownership(NULL, NULL, dir->buf, report))
>  				return GIT_DIR_INVALID_OWNERSHIP;

I wish we had caught it much before we added DISALLOWED_BARE thing,
but I wonder how well would this escape-hatch interact with
secondary worktrees, where their git directory is not named ".git"
and not immediately below the root level of the working tree?

In a secondary worktree the root level of its working tree has a
file ".git", whose contents may look like

    gitdir: /home/gitster/git.git/.git/worktrees/git.old

where

 - /home/gitster/git.git/ is the primary worktree with the
   repository.

 - /home/gitster/git.git/.git/worktrees/git.old looks like a bare
   repository.

 - /home/gitster/git.git/.git/worktrees/git.old/gitdir gives a way
   to discover the secondary worktree, whose contents just records
   the path to the ".git" file, e.g., "/home/gitster/git.old/.git"
   that had "gitdir: ..." in it.

So perhaps we can also use the presence of "gitdir" file, check the
contents of it tn ensure that ".git" file there takes us back to
this (not quite) bare repository we are looking at, and allow access
to it, or something?

Thoughts?
diff mbox series

Patch

diff --git a/setup.c b/setup.c
index b38702718fb..b095e284979 100644
--- a/setup.c
+++ b/setup.c
@@ -1371,7 +1371,8 @@  static enum discovery_result setup_git_directory_gently_1(struct strbuf *dir,
 
 		if (is_git_directory(dir->buf)) {
 			trace2_data_string("setup", NULL, "implicit-bare-repository", dir->buf);
-			if (get_allowed_bare_repo() == ALLOWED_BARE_REPO_EXPLICIT)
+			if (get_allowed_bare_repo() == ALLOWED_BARE_REPO_EXPLICIT &&
+			    !ends_with_path_components(dir->buf, ".git"))
 				return GIT_DIR_DISALLOWED_BARE;
 			if (!ensure_valid_ownership(NULL, NULL, dir->buf, report))
 				return GIT_DIR_INVALID_OWNERSHIP;
diff --git a/t/t0035-safe-bare-repository.sh b/t/t0035-safe-bare-repository.sh
index 038b8b788d7..80488563795 100755
--- a/t/t0035-safe-bare-repository.sh
+++ b/t/t0035-safe-bare-repository.sh
@@ -78,4 +78,12 @@  test_expect_success 'no trace when GIT_DIR is explicitly provided' '
 	expect_accepted_explicit "$pwd/outer-repo/bare-repo"
 '
 
+test_expect_success 'no trace when "bare repository" is .git' '
+	expect_accepted_implicit -C outer-repo/.git
+'
+
+test_expect_success 'no trace when "bare repository" is a subdir of .git' '
+	expect_accepted_implicit -C outer-repo/.git/objects
+'
+
 test_done