The risks of embedded bare repositories in Git
Running code from inside a cloned Git repository is potentially risky, but normally just inspecting such a repository is considered to be safe. As a recent posting to the Git mailing list shows, however, there are still risks lurking inside these repositories; code that lives in them can be triggered in unexpected ways. In particular, malicious "bare" repositories can be added as a subdirectory of a repository; they can be configured to run code whenever Git commands are executed there, which is something that can happen in surprising ways. There is now an effort underway to try to address the problem in Git, without breaking the legitimate need for including bare repositories into a Git tree.
In early April, Glen Choo posted to the list about the security risk of bare repositories in Git working tree. He linked to an admirably detailed advisory from Justin Steven that documents the problem and how it can be triggered by a wide variety of tools, including shells, integrated development environments (IDEs), editors, and more. The advisory has proof-of-concept (PoC) code for a whole slew of different scenarios, including ones that can be used to reproduce the problem locally, if desired.
The risks from automatically running code that comes from a remote (possibly untrusted) repository are well-known, so git clone does not copy the configuration file (normally .git/config) to the local system. Git can be configured via the file (or by using the git config command) in a wide variety of ways, including such things as changing the meaning of certain git subcommands. That is clearly dangerous, which is why the configuration file is excluded from the clone operation.
Bare repositories inside regular ones
But bare repositories are different than regular repositories; instead of storing all of the housekeeping information (including config) in the .git subdirectory of the repository, a bare repository stores all of those files directly in the directory where the repository is created. The difference can be easily seen by comparing the contents of the two directories created by the following:
$ mkdir tmp1 tmp2
$ cd tmp1; git init
Initialized empty Git repository in .../tmp1/.git/
$ cd ../tmp2; git init --bare
Initialized empty Git repository in .../tmp2/
The tmp1/.git and tmp2 directories will have much the
same content, including a config file.
But a bare repository does not have a "work tree" so many Git commands run in tmp2 will fail, but that is easily rectified. In tmp2:
$ git status
fatal: this operation must be run in a work tree
$ mkdir worktree
$ echo $'\tworktree = "worktree"' >> config
$ git status
warning: core.bare and core.worktree do not make sense
fatal: unable to set up work tree using invalid config
That error message points to another difference between a regular repository and a bare one; the config file has a different setting for core.bare. For a bare repository, as one might guess, it is set to "true", but that is easily fixed with an editor or other tool:
$ $EDITOR config # change bare = true to bare = false
$ git status
On branch master
No commits yet
nothing to commit (create/copy files and use "git add" to track)
Now any Git command that is run in tmp2 will refer to the bare
repository there; it will consult the config file there and use
whatever options have been set for it. Now if we move
that directory (and rename it to better describe its nature), we might have
the following:
$ cd ../tmp1
$ mv ../tmp2 mal
$ git status
# shows untracked file mal/
$ cd mal
$ git status
# shows the same empty repository as above
We can, of course, add and commit mal/ and then we have a
repository with a bare repository in it. Anyone who clones the
tmp1 repository, will get mal/ and any malicious
configuration that comes along for the ride. Triggering it is only a
matter of somehow causing a Git command to be executed in mal/, which might
happen as easily as simply trying to set the shell prompt. For example,
Steven cites the git-prompt.sh
file, which is included with Git; users of the script who cd into a malicious
bare repository will (perhaps unknowingly) run git and fall into
this hole.
The perils of fsmonitor
So far, though, mal/ is lacking in the malicious department. As mentioned, there are a number of Git configuration directives and hooks that can be used to potentially do malicious things, but for the most part those require that the victim execute specific Git commands in the bare repository. The core.fsmonitor directive is used more widely by Git, though, making it a useful primitive for code execution.
The idea behind fsmonitor is to reduce the search space for commands like git status by returning a list of files that may have changed since a given date and time. The directive can be set to a command to run that should return the list; if it returns a failure exit code, Git assumes all files could have changed and acts accordingly. Steven listed five fairly common Git commands that invoke the fsmonitor program (e.g. git status, git add).
So, using Steven's PoC as a guide, we can do the following (in mal/):
$ echo $'\tfsmonitor = "echo \\"Pwned as $(id)\\">&2; false"' >> config
$ git status
Pwned as uid=1000(jake) gid=1000(jake) groups=...
...
$ cd .. # to tmp1/
$ git add mal/
$ git commit -m "adding mal"
...
Note that the commit does not output the "Pwned..." line, since it is done
in the top-level repository. But that config now lurks in
mal/ waiting for any Git command that uses fsmonitor,
when executed from mal/.
This seems clearly to be a security hole, though whether it can or will be addressed in Git is not entirely clear. Embedded bare repositories are apparently used in benign ways, especially for testing purposes, and the Git project does not want to prohibit them. Choo's message was seeking a way to reduce the danger, which he described this way:
Many `git` commands can be affected by malicious config files, and many users have tools that will run `git` in the current directory or the subdirectories of a repo. Once the malicious repo has been cloned, very little social engineering is needed; the user might only need to open the repo in an editor or `cd` into the correct subdirectory.
He lists several possible fixes ranging from preventing bare repositories
from being added to work trees (or ignoring them in favor of their parent
repository), through checking for them with git fsck, to
educating users but not changing Git. The fsck check seems like
it will be pursued; Choo posted a
patch that will test a tree to see if it contains a bare repository and
warn if it does. "This will help hosting sites detect and
prevent transmission of such malicious repos.
"
There is some interest in putting further guardrails on Git's behavior with respect to these bare repositories, but it is important to ensure that projects can still use embedded bare repositories, especially given that some have them in their Git commit history, which will never go away even if a different solution is found. Johannes Schindelin pointed to the libgit2 repository as one example of a project that has embedded bare repositories.
There was some discussion of various possibilities, which Choo summarized,
noting that he believed: "We all agree that something needs to be
done about embedded bare repos.
" He listed some options (beyond the
fsck change, which he will be working on), but Taylor Blau was not entirely sure that
something needed be done since "there is significant social engineering
required in order to meaningfully exploit this
". However, not much
more than cloning a malicious repository and poking around in it a bit
while using Git-aware tools is all that is really needed to trigger the problem, so it is not
clear why Blau thinks that is a significant hurdle.
In any case, Blau did think
it was worth exploring options to "prevent
this type of attack or make it substantially less likely to have a user
run git commands that execute parts of the config
opportunistically
".
Blau thought that the most promising option was one that Choo described as:
"Detect if the bare repo is embedded and do not read its
config/hooks, but
everything else still 'works'.
" Blau extended that idea to allow
users to explicitly opt into reading the configuration from the embedded
bare repositories with a configuration option that would need to be set by
the user, since it would live in the main repository. As noted,
git clone will not copy the configuration from the remote repository since
it has long been identified as a security hole.
To opt-out (i.e., to allow legitimate use-cases to start reading embedded bare repository config again), the embedding repository would have to set a multi-valued `safe.embeddedRepo` configuration. This would specify a list of paths relative to the embedding repository's root of known-safe bare repositories.
The advantage of that approach is that it would likely disrupt few projects or workflows, since the number of (legitimate) embedded bare repositories with useful or necessary configuration is probably low. Those projects could provide instructions to their users on how to set up the configuration option, which might be a little annoying, but perhaps not all that disruptive. It looks like work is underway down that path, though there is no huge rush since the problem has been known for quite some time.
There are plenty of other pitfalls when using untrusted Git repositories, but those are already well-known; simply using make or the build script for an untrusted project is a leap of faith unless the repository is carefully scrutinized, for example. Grabbing a tar file of a repository can also bring with it unwanted baggage in the form of .git/config, hooks, or embedded bare repositories.
While Choo and Steven pointed to earlier occurrences of similar or related problems, from as early as 2017, the existence of those types of problems is not really too surprising. Git is a powerful tool, with a lot of configuration knobs that may interact in surprising or unexpected ways. Meanwhile, a bunch of tooling has grown up around it, which also may be doing somewhat unexpected, seemingly harmless, things—liking setting a shell prompt—that can lead to unpleasant outcomes. Any tool, such as an editor or IDE that tries to helpfully display repository information in its interface, may fall prey to attacks of this nature. Users should be alert to the presence of these bare repositories in any projects that they clone.
