Skip to content

Commit

Permalink
Merge branch 'gc/bare-repo-discovery'
Browse files Browse the repository at this point in the history
Introduce a discovery.barerepository configuration variable that
allows users to forbid discovery of bare repositories.

* gc/bare-repo-discovery:
  setup.c: create `safe.bareRepository`
  safe.directory: use git_protected_config()
  config: learn `git_protected_config()`
  Documentation: define protected configuration
  Documentation/git-config.txt: add SCOPES section
  • Loading branch information
gitster committed Jul 22, 2022
2 parents e72d93e + 8d1a744 commit 18bbc79
Show file tree
Hide file tree
Showing 10 changed files with 300 additions and 56 deletions.
25 changes: 22 additions & 3 deletions Documentation/config/safe.txt
@@ -1,3 +1,22 @@
safe.bareRepository::
Specifies which bare repositories Git will work with. The currently
supported values are:
+
* `all`: Git works with all bare repositories. This is the default.
* `explicit`: Git only works with bare repositories specified via
the top-level `--git-dir` command-line option, or the `GIT_DIR`
environment variable (see linkgit:git[1]).
+
If you do not use bare repositories in your workflow, then it may be
beneficial to set `safe.bareRepository` to `explicit` 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.
+
This config setting is only respected in protected configuration (see
<<SCOPES>>). This prevents the untrusted repository from tampering with
this value.

safe.directory::
These config entries specify Git-tracked directories that are
considered safe even if they are owned by someone other than the
Expand All @@ -12,9 +31,9 @@ via `git config --add`. To reset the list of safe directories (e.g. to
override any such directories specified in the system config), add a
`safe.directory` entry with an empty value.
+
This config setting is only respected when specified in a system or global
config, not when it is specified in a repository config, via the command
line option `-c safe.directory=<path>`, or in environment variables.
This config setting is only respected in protected configuration (see
<<SCOPES>>). This prevents the untrusted repository from tampering with this
value.
+
The value of this setting is interpolated, i.e. `~/<path>` expands to a
path relative to the home directory and `%(prefix)/<path>` expands to a
Expand Down
6 changes: 3 additions & 3 deletions Documentation/config/uploadpack.txt
Expand Up @@ -49,9 +49,9 @@ uploadpack.packObjectsHook::
`pack-objects` to the hook, and expects a completed packfile on
stdout.
+
Note that this configuration variable is ignored if it is seen in the
repository-level config (this is a safety measure against fetching from
untrusted repositories).
Note that this configuration variable is only respected when it is specified
in protected configuration (see <<SCOPES>>). This is a safety measure
against fetching from untrusted repositories.

uploadpack.allowFilter::
If this option is set, `upload-pack` will support partial
Expand Down
95 changes: 72 additions & 23 deletions Documentation/git-config.txt
Expand Up @@ -297,23 +297,20 @@ The default is to use a pager.
FILES
-----

If not set explicitly with `--file`, there are four files where
'git config' will search for configuration options:
By default, 'git config' will read configuration options from multiple
files:

$(prefix)/etc/gitconfig::
System-wide configuration file.

$XDG_CONFIG_HOME/git/config::
Second user-specific configuration file. If $XDG_CONFIG_HOME is not set
or empty, `$HOME/.config/git/config` will be used. Any single-valued
variable set in this file will be overwritten by whatever is in
`~/.gitconfig`. It is a good idea not to create this file if
you sometimes use older versions of Git, as support for this
file was added fairly recently.

~/.gitconfig::
User-specific configuration file. Also called "global"
configuration file.
User-specific configuration files. When the XDG_CONFIG_HOME environment
variable is not set or empty, $HOME/.config/ is used as
$XDG_CONFIG_HOME.
+
These are also called "global" configuration files. If both files exist, both
files are read in the order given above.

$GIT_DIR/config::
Repository specific configuration file.
Expand All @@ -322,28 +319,80 @@ $GIT_DIR/config.worktree::
This is optional and is only searched when
`extensions.worktreeConfig` is present in $GIT_DIR/config.

If no further options are given, all reading options will read all of these
files that are available. If the global or the system-wide configuration
file are not available they will be ignored. If the repository configuration
file is not available or readable, 'git config' will exit with a non-zero
error code. However, in neither case will an error message be issued.
You may also provide additional configuration parameters when running any
git command by using the `-c` option. See linkgit:git[1] for details.

Options will be read from all of these files that are available. If the
global or the system-wide configuration files are missing or unreadable they
will be ignored. If the repository configuration file is missing or unreadable,
'git config' will exit with a non-zero error code. An error message is produced
if the file is unreadable, but not if it is missing.

The files are read in the order given above, with last value found taking
precedence over values read earlier. When multiple values are taken then all
values of a key from all files will be used.

You may override individual configuration parameters when running any git
command by using the `-c` option. See linkgit:git[1] for details.

All writing options will per default write to the repository specific
By default, options are only written to the repository specific
configuration file. Note that this also affects options like `--replace-all`
and `--unset`. *'git config' will only ever change one file at a time*.

You can override these rules using the `--global`, `--system`,
`--local`, `--worktree`, and `--file` command-line options; see
<<OPTIONS>> above.
You can limit which configuration sources are read from or written to by
specifying the path of a file with the `--file` option, or by specifying a
configuration scope with `--system`, `--global`, `--local`, or `--worktree`.
For more, see <<OPTIONS>> above.

[[SCOPES]]
SCOPES
------

Each configuration source falls within a configuration scope. The scopes
are:

system::
$(prefix)/etc/gitconfig

global::
$XDG_CONFIG_HOME/git/config
+
~/.gitconfig

local::
$GIT_DIR/config

worktree::
$GIT_DIR/config.worktree

command::
GIT_CONFIG_{COUNT,KEY,VALUE} environment variables (see <<ENVIRONMENT>>
below)
+
the `-c` option

With the exception of 'command', each scope corresponds to a command line
option: `--system`, `--global`, `--local`, `--worktree`.

When reading options, specifying a scope will only read options from the
files within that scope. When writing options, specifying a scope will write
to the files within that scope (instead of the repository specific
configuration file). See <<OPTIONS>> above for a complete description.

Most configuration options are respected regardless of the scope it is
defined in, but some options are only respected in certain scopes. See the
respective option's documentation for the full details.

Protected configuration
~~~~~~~~~~~~~~~~~~~~~~~

Protected configuration refers to the 'system', 'global', and 'command' scopes.
For security reasons, certain options are only respected when they are
specified in protected configuration, and ignored otherwise.

Git treats these scopes as if they are controlled by the user or a trusted
administrator. This is because an attacker who controls these scopes can do
substantial harm without using Git, so it is assumed that the user's environment
protects these scopes against attackers.

[[ENVIRONMENT]]
ENVIRONMENT
-----------

Expand Down
43 changes: 43 additions & 0 deletions config.c
Expand Up @@ -81,6 +81,17 @@ static enum config_scope current_parsing_scope;
static int pack_compression_seen;
static int zlib_compression_seen;

/*
* Config that comes from trusted scopes, namely:
* - CONFIG_SCOPE_SYSTEM (e.g. /etc/gitconfig)
* - CONFIG_SCOPE_GLOBAL (e.g. $HOME/.gitconfig, $XDG_CONFIG_HOME/git)
* - CONFIG_SCOPE_COMMAND (e.g. "-c" option, environment variables)
*
* This is declared here for code cleanliness, but unlike the other
* static variables, this does not hold config parser state.
*/
static struct config_set protected_config;

static int config_file_fgetc(struct config_source *conf)
{
return getc_unlocked(conf->u.file);
Expand Down Expand Up @@ -2378,6 +2389,11 @@ int git_configset_add_file(struct config_set *cs, const char *filename)
return git_config_from_file(config_set_callback, filename, cs);
}

int git_configset_add_parameters(struct config_set *cs)
{
return git_config_from_parameters(config_set_callback, cs);
}

int git_configset_get_value(struct config_set *cs, const char *key, const char **value)
{
const struct string_list *values = NULL;
Expand Down Expand Up @@ -2619,6 +2635,33 @@ int repo_config_get_pathname(struct repository *repo,
return ret;
}

/* Read values into protected_config. */
static void read_protected_config(void)
{
char *xdg_config = NULL, *user_config = NULL, *system_config = NULL;

git_configset_init(&protected_config);

system_config = git_system_config();
git_global_config(&user_config, &xdg_config);

git_configset_add_file(&protected_config, system_config);
git_configset_add_file(&protected_config, xdg_config);
git_configset_add_file(&protected_config, user_config);
git_configset_add_parameters(&protected_config);

free(system_config);
free(xdg_config);
free(user_config);
}

void git_protected_config(config_fn_t fn, void *data)
{
if (!protected_config.hash_initialized)
read_protected_config();
configset_iter(&protected_config, fn, data);
}

/* Functions used historically to read configuration from 'the_repository' */
void git_config(config_fn_t fn, void *data)
{
Expand Down
16 changes: 16 additions & 0 deletions config.h
Expand Up @@ -446,6 +446,15 @@ void git_configset_init(struct config_set *cs);
*/
int git_configset_add_file(struct config_set *cs, const char *filename);

/**
* Parses command line options and environment variables, and adds the
* variable-value pairs to the `config_set`. Returns 0 on success, or -1
* if there is an error in parsing. The caller decides whether to free
* the incomplete configset or continue using it when the function
* returns -1.
*/
int git_configset_add_parameters(struct config_set *cs);

/**
* Finds and returns the value list, sorted in order of increasing priority
* for the configuration variable `key` and config set `cs`. When the
Expand Down Expand Up @@ -505,6 +514,13 @@ int repo_config_get_maybe_bool(struct repository *repo,
int repo_config_get_pathname(struct repository *repo,
const char *key, const char **dest);

/*
* Functions for reading protected config. By definition, protected
* config ignores repository config, so these do not take a `struct
* repository` parameter.
*/
void git_protected_config(config_fn_t fn, void *data);

/**
* Querying For Specific Variables
* -------------------------------
Expand Down
59 changes: 57 additions & 2 deletions setup.c
Expand Up @@ -10,6 +10,10 @@
static int inside_git_dir = -1;
static int inside_work_tree = -1;
static int work_tree_config_is_bogus;
enum allowed_bare_repo {
ALLOWED_BARE_REPO_EXPLICIT = 0,
ALLOWED_BARE_REPO_ALL,
};

static struct startup_info the_startup_info;
struct startup_info *startup_info = &the_startup_info;
Expand Down Expand Up @@ -1155,11 +1159,51 @@ static int ensure_valid_ownership(const char *gitfile,
* constant regardless of what failed above. data.is_safe should be
* initialized to false, and might be changed by the callback.
*/
read_very_early_config(safe_directory_cb, &data);
git_protected_config(safe_directory_cb, &data);

return data.is_safe;
}

static int allowed_bare_repo_cb(const char *key, const char *value, void *d)
{
enum allowed_bare_repo *allowed_bare_repo = d;

if (strcasecmp(key, "safe.bareRepository"))
return 0;

if (!strcmp(value, "explicit")) {
*allowed_bare_repo = ALLOWED_BARE_REPO_EXPLICIT;
return 0;
}
if (!strcmp(value, "all")) {
*allowed_bare_repo = ALLOWED_BARE_REPO_ALL;
return 0;
}
return -1;
}

static enum allowed_bare_repo get_allowed_bare_repo(void)
{
enum allowed_bare_repo result = ALLOWED_BARE_REPO_ALL;
git_protected_config(allowed_bare_repo_cb, &result);
return result;
}

static const char *allowed_bare_repo_to_string(
enum allowed_bare_repo allowed_bare_repo)
{
switch (allowed_bare_repo) {
case ALLOWED_BARE_REPO_EXPLICIT:
return "explicit";
case ALLOWED_BARE_REPO_ALL:
return "all";
default:
BUG("invalid allowed_bare_repo %d",
allowed_bare_repo);
}
return NULL;
}

enum discovery_result {
GIT_DIR_NONE = 0,
GIT_DIR_EXPLICIT,
Expand All @@ -1169,7 +1213,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,
};

/*
Expand Down Expand Up @@ -1297,6 +1342,8 @@ static enum discovery_result setup_git_directory_gently_1(struct strbuf *dir,
}

if (is_git_directory(dir->buf)) {
if (get_allowed_bare_repo() == ALLOWED_BARE_REPO_EXPLICIT)
return GIT_DIR_DISALLOWED_BARE;
if (!ensure_valid_ownership(NULL, NULL, dir->buf))
return GIT_DIR_INVALID_OWNERSHIP;
strbuf_addstr(gitdir, ".");
Expand Down Expand Up @@ -1443,6 +1490,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' (safe.bareRepository is '%s')"),
dir.buf,
allowed_bare_repo_to_string(get_allowed_bare_repo()));
}
*nongit_ok = 1;
break;
case GIT_DIR_NONE:
/*
* As a safeguard against setup_git_directory_gently_1 returning
Expand Down

0 comments on commit 18bbc79

Please sign in to comment.