@@ -409,6 +409,8 @@ include::config/diff.txt[]
include::config/difftool.txt[]
+include::config/discovery.txt[]
+
include::config/extensions.txt[]
include::config/fastimport.txt[]
new file mode 100644
@@ -0,0 +1,21 @@
+discovery.bare::
+ Specifies whether Git will work with a bare repository that
+ wasn't specified via the top-level `--git-dir` command-line
+ option, or the `GIT_DIR` environment variable (see
+ linkgit:git[1]). If the repository is specified, Git will always
+ use the specified repository, regardless of this value.
++
+This config setting is only respected in protected configuration (see
+<<SCOPES>>). This prevents the untrusted repository from tampering with
+this value.
++
+The currently supported values are:
++
+* `always`: Git always works with bare repositories
+* `never`: Git never works with bare repositories
++
+If you do not use bare repositories in your workflow, then it may be
+beneficial to set `discovery.bare` to `never` in your global config.
+This will protect you from attacks that involve cloning a repository
+that contains a bare repository and running a Git command within that
+directory.
@@ -10,6 +10,10 @@
static int inside_git_dir = -1;
static int inside_work_tree = -1;
static int work_tree_config_is_bogus;
+enum discovery_bare_allowed {
+ DISCOVERY_BARE_NEVER = 0,
+ DISCOVERY_BARE_ALWAYS,
+};
static struct startup_info the_startup_info;
struct startup_info *startup_info = &the_startup_info;
@@ -1142,6 +1146,46 @@ static int ensure_valid_ownership(const char *path)
return data.is_safe;
}
+static int discovery_bare_cb(const char *key, const char *value, void *d)
+{
+ enum discovery_bare_allowed *discovery_bare_allowed = d;
+
+ if (strcmp(key, "discovery.bare"))
+ return 0;
+
+ if (!strcmp(value, "never")) {
+ *discovery_bare_allowed = DISCOVERY_BARE_NEVER;
+ return 0;
+ }
+ if (!strcmp(value, "always")) {
+ *discovery_bare_allowed = DISCOVERY_BARE_ALWAYS;
+ return 0;
+ }
+ return -1;
+}
+
+static enum discovery_bare_allowed get_discovery_bare(void)
+{
+ enum discovery_bare_allowed result = DISCOVERY_BARE_ALWAYS;
+ git_protected_config(discovery_bare_cb, &result);
+ return result;
+}
+
+static const char *discovery_bare_allowed_to_string(
+ enum discovery_bare_allowed discovery_bare_allowed)
+{
+ switch (discovery_bare_allowed) {
+ case DISCOVERY_BARE_NEVER:
+ return "never";
+ case DISCOVERY_BARE_ALWAYS:
+ return "always";
+ default:
+ BUG("invalid discovery_bare_allowed %d",
+ discovery_bare_allowed);
+ }
+ return NULL;
+}
+
enum discovery_result {
GIT_DIR_NONE = 0,
GIT_DIR_EXPLICIT,
@@ -1151,7 +1195,8 @@ enum discovery_result {
GIT_DIR_HIT_CEILING = -1,
GIT_DIR_HIT_MOUNT_POINT = -2,
GIT_DIR_INVALID_GITFILE = -3,
- GIT_DIR_INVALID_OWNERSHIP = -4
+ GIT_DIR_INVALID_OWNERSHIP = -4,
+ GIT_DIR_DISALLOWED_BARE = -5,
};
/*
@@ -1248,6 +1293,8 @@ static enum discovery_result setup_git_directory_gently_1(struct strbuf *dir,
}
if (is_git_directory(dir->buf)) {
+ if (get_discovery_bare() == DISCOVERY_BARE_NEVER)
+ return GIT_DIR_DISALLOWED_BARE;
if (!ensure_valid_ownership(dir->buf))
return GIT_DIR_INVALID_OWNERSHIP;
strbuf_addstr(gitdir, ".");
@@ -1394,6 +1441,14 @@ const char *setup_git_directory_gently(int *nongit_ok)
}
*nongit_ok = 1;
break;
+ case GIT_DIR_DISALLOWED_BARE:
+ if (!nongit_ok) {
+ die(_("cannot use bare repository '%s' (discovery.bare is '%s')"),
+ dir.buf,
+ discovery_bare_allowed_to_string(get_discovery_bare()));
+ }
+ *nongit_ok = 1;
+ break;
case GIT_DIR_NONE:
/*
* As a safeguard against setup_git_directory_gently_1 returning
new file mode 100755
@@ -0,0 +1,52 @@
+#!/bin/sh
+
+test_description='verify discovery.bare checks'
+
+TEST_PASSES_SANITIZE_LEAK=true
+. ./test-lib.sh
+
+pwd="$(pwd)"
+
+expect_accepted () {
+ git "$@" rev-parse --git-dir
+}
+
+expect_rejected () {
+ test_must_fail git "$@" rev-parse --git-dir 2>err &&
+ grep -F "cannot use bare repository" err
+}
+
+test_expect_success 'setup bare repo in worktree' '
+ git init outer-repo &&
+ git init --bare outer-repo/bare-repo
+'
+
+test_expect_success 'discovery.bare unset' '
+ expect_accepted -C outer-repo/bare-repo
+'
+
+test_expect_success 'discovery.bare=always' '
+ test_config_global discovery.bare always &&
+ expect_accepted -C outer-repo/bare-repo
+'
+
+test_expect_success 'discovery.bare=never' '
+ test_config_global discovery.bare never &&
+ expect_rejected -C outer-repo/bare-repo
+'
+
+test_expect_success 'discovery.bare in the repository' '
+ # discovery.bare must not be "never", otherwise git config fails
+ # with "fatal: not in a git directory" (like safe.directory)
+ test_config -C outer-repo/bare-repo discovery.bare always &&
+ test_config_global discovery.bare never &&
+ expect_rejected -C outer-repo/bare-repo
+'
+
+test_expect_success 'discovery.bare on the command line' '
+ test_config_global discovery.bare never &&
+ expect_accepted -C outer-repo/bare-repo \
+ -c discovery.bare=always
+'
+
+test_done