Merge branch 'gc/bare-repo-discovery'
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
This commit is contained in:
@ -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
|
||||
@ -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
|
||||
|
@ -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
|
||||
|
@ -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.
|
||||
@ -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
|
||||
-----------
|
||||
|
||||
|
43
config.c
43
config.c
@ -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);
|
||||
@ -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;
|
||||
@ -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)
|
||||
{
|
||||
|
16
config.h
16
config.h
@ -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
|
||||
@ -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
|
||||
* -------------------------------
|
||||
|
59
setup.c
59
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 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;
|
||||
@ -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,
|
||||
@ -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,
|
||||
};
|
||||
|
||||
/*
|
||||
@ -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, ".");
|
||||
@ -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
|
||||
|
@ -16,24 +16,20 @@ test_expect_success 'safe.directory is not set' '
|
||||
expect_rejected_dir
|
||||
'
|
||||
|
||||
test_expect_success 'ignoring safe.directory on the command line' '
|
||||
test_must_fail git -c safe.directory="$(pwd)" status 2>err &&
|
||||
grep "dubious ownership" err
|
||||
test_expect_success 'safe.directory on the command line' '
|
||||
git -c safe.directory="$(pwd)" status
|
||||
'
|
||||
|
||||
test_expect_success 'ignoring safe.directory in the environment' '
|
||||
test_must_fail env GIT_CONFIG_COUNT=1 \
|
||||
GIT_CONFIG_KEY_0="safe.directory" \
|
||||
GIT_CONFIG_VALUE_0="$(pwd)" \
|
||||
git status 2>err &&
|
||||
grep "dubious ownership" err
|
||||
test_expect_success 'safe.directory in the environment' '
|
||||
env GIT_CONFIG_COUNT=1 \
|
||||
GIT_CONFIG_KEY_0="safe.directory" \
|
||||
GIT_CONFIG_VALUE_0="$(pwd)" \
|
||||
git status
|
||||
'
|
||||
|
||||
test_expect_success 'ignoring safe.directory in GIT_CONFIG_PARAMETERS' '
|
||||
test_must_fail env \
|
||||
GIT_CONFIG_PARAMETERS="${SQ}safe.directory${SQ}=${SQ}$(pwd)${SQ}" \
|
||||
git status 2>err &&
|
||||
grep "dubious ownership" err
|
||||
test_expect_success 'safe.directory in GIT_CONFIG_PARAMETERS' '
|
||||
env GIT_CONFIG_PARAMETERS="${SQ}safe.directory${SQ}=${SQ}$(pwd)${SQ}" \
|
||||
git status
|
||||
'
|
||||
|
||||
test_expect_success 'ignoring safe.directory in repo config' '
|
||||
|
54
t/t0035-safe-bare-repository.sh
Executable file
54
t/t0035-safe-bare-repository.sh
Executable file
@ -0,0 +1,54 @@
|
||||
#!/bin/sh
|
||||
|
||||
test_description='verify safe.bareRepository 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 'safe.bareRepository unset' '
|
||||
expect_accepted -C outer-repo/bare-repo
|
||||
'
|
||||
|
||||
test_expect_success 'safe.bareRepository=all' '
|
||||
test_config_global safe.bareRepository all &&
|
||||
expect_accepted -C outer-repo/bare-repo
|
||||
'
|
||||
|
||||
test_expect_success 'safe.bareRepository=explicit' '
|
||||
test_config_global safe.bareRepository explicit &&
|
||||
expect_rejected -C outer-repo/bare-repo
|
||||
'
|
||||
|
||||
test_expect_success 'safe.bareRepository in the repository' '
|
||||
# safe.bareRepository must not be "explicit", otherwise
|
||||
# git config fails with "fatal: not in a git directory" (like
|
||||
# safe.directory)
|
||||
test_config -C outer-repo/bare-repo safe.bareRepository \
|
||||
all &&
|
||||
test_config_global safe.bareRepository explicit &&
|
||||
expect_rejected -C outer-repo/bare-repo
|
||||
'
|
||||
|
||||
test_expect_success 'safe.bareRepository on the command line' '
|
||||
test_config_global safe.bareRepository explicit &&
|
||||
expect_accepted -C outer-repo/bare-repo \
|
||||
-c safe.bareRepository=all
|
||||
'
|
||||
|
||||
test_done
|
@ -56,7 +56,12 @@ test_expect_success 'hook does not run from repo config' '
|
||||
! grep "hook running" stderr &&
|
||||
test_path_is_missing .git/hook.args &&
|
||||
test_path_is_missing .git/hook.stdin &&
|
||||
test_path_is_missing .git/hook.stdout
|
||||
test_path_is_missing .git/hook.stdout &&
|
||||
|
||||
# check that global config is used instead
|
||||
test_config_global uploadpack.packObjectsHook ./hook &&
|
||||
git clone --no-local . dst2.git 2>stderr &&
|
||||
grep "hook running" stderr
|
||||
'
|
||||
|
||||
test_expect_success 'hook works with partial clone' '
|
||||
|
@ -1321,18 +1321,27 @@ static int upload_pack_config(const char *var, const char *value, void *cb_data)
|
||||
data->advertise_sid = git_config_bool(var, value);
|
||||
}
|
||||
|
||||
if (current_config_scope() != CONFIG_SCOPE_LOCAL &&
|
||||
current_config_scope() != CONFIG_SCOPE_WORKTREE) {
|
||||
if (!strcmp("uploadpack.packobjectshook", var))
|
||||
return git_config_string(&data->pack_objects_hook, var, value);
|
||||
}
|
||||
|
||||
if (parse_object_filter_config(var, value, data) < 0)
|
||||
return -1;
|
||||
|
||||
return parse_hide_refs_config(var, value, "uploadpack");
|
||||
}
|
||||
|
||||
static int upload_pack_protected_config(const char *var, const char *value, void *cb_data)
|
||||
{
|
||||
struct upload_pack_data *data = cb_data;
|
||||
|
||||
if (!strcmp("uploadpack.packobjectshook", var))
|
||||
return git_config_string(&data->pack_objects_hook, var, value);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void get_upload_pack_config(struct upload_pack_data *data)
|
||||
{
|
||||
git_config(upload_pack_config, data);
|
||||
git_protected_config(upload_pack_protected_config, data);
|
||||
}
|
||||
|
||||
void upload_pack(const int advertise_refs, const int stateless_rpc,
|
||||
const int timeout)
|
||||
{
|
||||
@ -1340,8 +1349,7 @@ void upload_pack(const int advertise_refs, const int stateless_rpc,
|
||||
struct upload_pack_data data;
|
||||
|
||||
upload_pack_data_init(&data);
|
||||
|
||||
git_config(upload_pack_config, &data);
|
||||
get_upload_pack_config(&data);
|
||||
|
||||
data.stateless_rpc = stateless_rpc;
|
||||
data.timeout = timeout;
|
||||
@ -1695,8 +1703,7 @@ int upload_pack_v2(struct repository *r, struct packet_reader *request)
|
||||
|
||||
upload_pack_data_init(&data);
|
||||
data.use_sideband = LARGE_PACKET_MAX;
|
||||
|
||||
git_config(upload_pack_config, &data);
|
||||
get_upload_pack_config(&data);
|
||||
|
||||
while (state != FETCH_DONE) {
|
||||
switch (state) {
|
||||
|
Reference in New Issue
Block a user