Message ID | 14411512783fd4e2cdcc8513690377b29262f6b8.1656354994.git.gitgitgadget@gmail.com (mailing list archive) |
---|---|
State | Superseded |
Headers | show |
Series | config: introduce discovery.bare and protected config | expand |
On Mon, Jun 27 2022, Glen Choo via GitGitGadget wrote: > From: Glen Choo <chooglen@google.com> > diff --git a/t/t0035-discovery-bare.sh b/t/t0035-discovery-bare.sh > new file mode 100755 > index 00000000000..0b345d361e6 > --- /dev/null > +++ b/t/t0035-discovery-bare.sh > @@ -0,0 +1,68 @@ > +#!/bin/sh > + > +test_description='verify discovery.bare checks' > + You're missing a: TEST_PASSES_SANITIZE_LEAK=true Above this line: > +. ./test-lib.sh Which tells us that this new test doesn't leak (yay!) > +expect_accepted () { > + git "$@" rev-parse --git-dir > +} I think we can do away with this helper, we use the argument support once, and for the rest we can inline the trivial command... > + > +expect_rejected () { > + test_must_fail git "$@" rev-parse --git-dir 2>err && > + grep "discovery.bare" err grep -F ? This helper is less trivial, but more obvious would be a "run command and assirt xyz about the output" helper, see e.g. test_stdout_line_count. > +test_expect_success 'discovery.bare unset' ' > + ( > + cd outer-repo/bare-repo && > + expect_accepted > + ) Also: Odd to use a sub-shell when the helper takes -C... > +' > + > +test_expect_success 'discovery.bare=always' ' > + git config --global discovery.bare always && > + ( > + cd outer-repo/bare-repo && > + expect_accepted > + ) > +' > + > +test_expect_success 'discovery.bare=never' ' > + git config --global discovery.bare never && > + ( > + cd outer-repo/bare-repo && > + expect_rejected > + ) ...ditto... > +' > + > +test_expect_success 'discovery.bare in the repository' ' > + ( > + cd outer-repo/bare-repo && > + # Temporarily set discovery.bare=always, otherwise git > + # config fails with "fatal: not in a git directory" > + # (like safe.directory) > + git config --global discovery.bare always && > + git config discovery.bare always && > + git config --global discovery.bare never && > + expect_rejected > + ) Drop the sub-shell and use test_config? > +' > + > +test_expect_success 'discovery.bare on the command line' ' > + git config --global discovery.bare never && > + ( > + cd outer-repo/bare-repo && > + expect_accepted -c discovery.bare=always && > + expect_rejected -c discovery.bare= > + ) > +' > + > +test_done
Ævar Arnfjörð Bjarmason <avarab@gmail.com> writes: > On Mon, Jun 27 2022, Glen Choo via GitGitGadget wrote: > >> From: Glen Choo <chooglen@google.com> > >> diff --git a/t/t0035-discovery-bare.sh b/t/t0035-discovery-bare.sh >> new file mode 100755 >> index 00000000000..0b345d361e6 >> --- /dev/null >> +++ b/t/t0035-discovery-bare.sh >> @@ -0,0 +1,68 @@ >> +#!/bin/sh >> + >> +test_description='verify discovery.bare checks' >> + > > You're missing a: > > TEST_PASSES_SANITIZE_LEAK=true > > Above this line: > >> +. ./test-lib.sh > > Which tells us that this new test doesn't leak (yay!) Ah, thanks! Hooray. >> +expect_accepted () { >> + git "$@" rev-parse --git-dir >> +} > > I think we can do away with this helper, we use the argument support > once, and for the rest we can inline the trivial command... That is true, having fewer test helpers can be a good idea. Though in this case, the helper wins out slightly (IMO at least) because of the readability/refactoring benefit. >> + >> +expect_rejected () { >> + test_must_fail git "$@" rev-parse --git-dir 2>err && >> + grep "discovery.bare" err > > grep -F ? > > This helper is less trivial, but more obvious would be a "run command > and assirt xyz about the output" helper, see > e.g. test_stdout_line_count. This takes precedent from t0033, which does the same "run command and grep the result". And just as I typed this out, I remembered that t0033's corresponding test helper was made more specific in f62563988f (t0033-safe-directory: check the error message without matching the trash dir, 2022-04-27), because just grep-ing for the config variable masked some errors. It turns out the same thing is happening in the last test - I forgot that "-c" doesn't unset the variable (it sets the value to ''), and the test_must_fail passes because we fail to parse "discovery.bare", _not_ because we forbade the repo. So besides -F, I think the only change here would be to grep on the specific "cannot use bare repository" message (instead of grepping for "discovery.bare"). >> +test_expect_success 'discovery.bare unset' ' >> + ( >> + cd outer-repo/bare-repo && >> + expect_accepted >> + ) > > Also: Odd to use a sub-shell when the helper takes -C... > >> +' >> + >> +test_expect_success 'discovery.bare=always' ' >> + git config --global discovery.bare always && >> + ( >> + cd outer-repo/bare-repo && >> + expect_accepted >> + ) >> +' >> + >> +test_expect_success 'discovery.bare=never' ' >> + git config --global discovery.bare never && >> + ( >> + cd outer-repo/bare-repo && >> + expect_rejected >> + ) > > ...ditto... Ok, I'll drop the sub-shell. > >> +' >> + >> +test_expect_success 'discovery.bare in the repository' ' >> + ( >> + cd outer-repo/bare-repo && >> + # Temporarily set discovery.bare=always, otherwise git >> + # config fails with "fatal: not in a git directory" >> + # (like safe.directory) >> + git config --global discovery.bare always && >> + git config discovery.bare always && >> + git config --global discovery.bare never && >> + expect_rejected >> + ) > > Drop the sub-shell and use test_config? Oh, I was so focused on t0033 that I hadn't realized that we had test_config_global. Thanks :) >> +' >> + >> +test_expect_success 'discovery.bare on the command line' ' >> + git config --global discovery.bare never && >> + ( >> + cd outer-repo/bare-repo && >> + expect_accepted -c discovery.bare=always && >> + expect_rejected -c discovery.bare= >> + ) >> +' >> + >> +test_done
diff --git a/Documentation/config.txt b/Documentation/config.txt index e284b042f22..9a5e1329772 100644 --- a/Documentation/config.txt +++ b/Documentation/config.txt @@ -409,6 +409,8 @@ include::config/diff.txt[] include::config/difftool.txt[] +include::config/discovery.txt[] + include::config/extensions.txt[] include::config/fastimport.txt[] diff --git a/Documentation/config/discovery.txt b/Documentation/config/discovery.txt new file mode 100644 index 00000000000..bbcf89bb0b5 --- /dev/null +++ b/Documentation/config/discovery.txt @@ -0,0 +1,23 @@ +discovery.bare:: + Specifies whether Git will work with a bare repository that it + found during repository discovery. If the repository is + specified directly via the --git-dir command-line option or the + GIT_DIR environment variable (see linkgit:git[1]), 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 ++ +This defaults to `always`, but this default may change in the future. ++ +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. diff --git a/setup.c b/setup.c index c8e3c32814d..16938fd5a24 100644 --- a/setup.c +++ b/setup.c @@ -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()) + 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 diff --git a/t/t0035-discovery-bare.sh b/t/t0035-discovery-bare.sh new file mode 100755 index 00000000000..0b345d361e6 --- /dev/null +++ b/t/t0035-discovery-bare.sh @@ -0,0 +1,68 @@ +#!/bin/sh + +test_description='verify discovery.bare checks' + +. ./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 "discovery.bare" 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' ' + ( + cd outer-repo/bare-repo && + expect_accepted + ) +' + +test_expect_success 'discovery.bare=always' ' + git config --global discovery.bare always && + ( + cd outer-repo/bare-repo && + expect_accepted + ) +' + +test_expect_success 'discovery.bare=never' ' + git config --global discovery.bare never && + ( + cd outer-repo/bare-repo && + expect_rejected + ) +' + +test_expect_success 'discovery.bare in the repository' ' + ( + cd outer-repo/bare-repo && + # Temporarily set discovery.bare=always, otherwise git + # config fails with "fatal: not in a git directory" + # (like safe.directory) + git config --global discovery.bare always && + git config discovery.bare always && + git config --global discovery.bare never && + expect_rejected + ) +' + +test_expect_success 'discovery.bare on the command line' ' + git config --global discovery.bare never && + ( + cd outer-repo/bare-repo && + expect_accepted -c discovery.bare=always && + expect_rejected -c discovery.bare= + ) +' + +test_done