setup: extract path_allowlist_apply()
In a following commit we are going to check whether a repository is part of an allowlist specified in a config variable. To prepare for that let's extract existing code from safe_directory_cb() into a new path_allowlist_apply() helper that will help with such checks. While at it let's make the helper's code simpler and more generic, by passing it a `bool (*allow_path)(const char *path, void *cbdata)` function that decides if a path is acceptable by the caller. To further simplify how to reuse that new helper, and avoid duplicating the config-value handling in a future commit, let's also introduce a path_allowlist_config_apply() helper. For clarity, let's change the `int is_safe` to `bool safe` in `struct safe_directory_data`. Signed-off-by: Christian Couder <christian.couder@gmail.com> Signed-off-by: Junio C Hamano <gitster@pobox.com>seen
parent
6aa4adae0e
commit
e63bb82850
138
setup.c
138
setup.c
|
|
@ -1338,67 +1338,105 @@ static int canonicalize_ceiling_entry(struct string_list_item *item,
|
|||
}
|
||||
}
|
||||
|
||||
void path_allowlist_apply(const char *allowed, const char *target_path,
|
||||
bool *matches,
|
||||
bool (*allow_path)(const char *path, void *cbdata),
|
||||
void *allow_path_cbdata)
|
||||
{
|
||||
char *normalized = NULL;
|
||||
|
||||
if (!allowed || !*allowed) {
|
||||
*matches = false;
|
||||
return;
|
||||
}
|
||||
|
||||
if (!strcmp(allowed, "*")) {
|
||||
*matches = true;
|
||||
return;
|
||||
}
|
||||
|
||||
if (!allow_path(allowed, allow_path_cbdata))
|
||||
return;
|
||||
|
||||
/*
|
||||
* A .gitconfig in $HOME may be shared across different
|
||||
* machines and the config variable entries may or may not
|
||||
* exist as paths on all of these machines. In other words,
|
||||
* it is not a warning worthy event when there is no such path
|
||||
* on this machine---the entry may be useful elsewhere.
|
||||
*/
|
||||
normalized = real_pathdup(allowed, 0);
|
||||
if (!normalized)
|
||||
return;
|
||||
|
||||
if (ends_with(normalized, "/*")) {
|
||||
size_t len = strlen(normalized);
|
||||
if (!fspathncmp(normalized, target_path, len - 1))
|
||||
*matches = true;
|
||||
} else if (!fspathcmp(target_path, normalized)) {
|
||||
*matches = true;
|
||||
}
|
||||
|
||||
free(normalized);
|
||||
}
|
||||
|
||||
void path_allowlist_config_apply(const char *key, const char *value,
|
||||
const char *target_path, bool *matches,
|
||||
bool (*allow_path)(const char *path, void *cbdata),
|
||||
void *allow_path_cbdata)
|
||||
{
|
||||
char *allowed = NULL;
|
||||
|
||||
if (!value || !*value || !strcmp(value, "*")) {
|
||||
path_allowlist_apply(value, target_path, matches,
|
||||
allow_path, allow_path_cbdata);
|
||||
return;
|
||||
}
|
||||
|
||||
if (git_config_pathname(&allowed, key, value) || !allowed)
|
||||
return;
|
||||
|
||||
path_allowlist_apply(allowed, target_path, matches,
|
||||
allow_path, allow_path_cbdata);
|
||||
|
||||
free(allowed);
|
||||
}
|
||||
|
||||
/*
|
||||
* Setting the config variable to a non-absolute path makes
|
||||
* little sense---it won't be relative to the configuration
|
||||
* file the item is defined in. Except for ".", which means
|
||||
* "if we are at the top level of a repository, then it is
|
||||
* OK", which is slightly tighter than "*" that allows
|
||||
* discovery.
|
||||
*/
|
||||
static bool allow_safe_dir(const char *path, void *cbdata_)
|
||||
{
|
||||
struct path_allowlist_cb_data *cbdata = cbdata_;
|
||||
|
||||
if (is_absolute_path(path) || !strcmp(path, "."))
|
||||
return true;
|
||||
|
||||
warning(_("%s '%s' not absolute"), cbdata->key, path);
|
||||
return false;
|
||||
}
|
||||
|
||||
struct safe_directory_data {
|
||||
char *path;
|
||||
int is_safe;
|
||||
bool safe;
|
||||
};
|
||||
|
||||
static int safe_directory_cb(const char *key, const char *value,
|
||||
const struct config_context *ctx UNUSED, void *d)
|
||||
{
|
||||
struct safe_directory_data *data = d;
|
||||
struct path_allowlist_cb_data cbdata = { .key = key };
|
||||
|
||||
if (strcmp(key, "safe.directory"))
|
||||
return 0;
|
||||
|
||||
if (!value || !*value) {
|
||||
data->is_safe = 0;
|
||||
} else if (!strcmp(value, "*")) {
|
||||
data->is_safe = 1;
|
||||
} else {
|
||||
char *allowed = NULL;
|
||||
|
||||
if (!git_config_pathname(&allowed, key, value) && allowed) {
|
||||
char *normalized = NULL;
|
||||
|
||||
/*
|
||||
* Setting safe.directory to a non-absolute path
|
||||
* makes little sense---it won't be relative to
|
||||
* the configuration file the item is defined in.
|
||||
* Except for ".", which means "if we are at the top
|
||||
* level of a repository, then it is OK", which is
|
||||
* slightly tighter than "*" that allows discovery.
|
||||
*/
|
||||
if (!is_absolute_path(allowed) && strcmp(allowed, ".")) {
|
||||
warning(_("safe.directory '%s' not absolute"),
|
||||
allowed);
|
||||
goto next;
|
||||
}
|
||||
|
||||
/*
|
||||
* A .gitconfig in $HOME may be shared across
|
||||
* different machines and safe.directory entries
|
||||
* may or may not exist as paths on all of these
|
||||
* machines. In other words, it is not a warning
|
||||
* worthy event when there is no such path on this
|
||||
* machine---the entry may be useful elsewhere.
|
||||
*/
|
||||
normalized = real_pathdup(allowed, 0);
|
||||
if (!normalized)
|
||||
goto next;
|
||||
|
||||
if (ends_with(normalized, "/*")) {
|
||||
size_t len = strlen(normalized);
|
||||
if (!fspathncmp(normalized, data->path, len - 1))
|
||||
data->is_safe = 1;
|
||||
} else if (!fspathcmp(data->path, normalized)) {
|
||||
data->is_safe = 1;
|
||||
}
|
||||
next:
|
||||
free(normalized);
|
||||
free(allowed);
|
||||
}
|
||||
}
|
||||
path_allowlist_config_apply(key, value, data->path, &data->safe,
|
||||
allow_safe_dir, &cbdata);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
|
@ -1440,7 +1478,7 @@ static int ensure_valid_ownership(const char *gitfile,
|
|||
git_protected_config(safe_directory_cb, &data);
|
||||
|
||||
free(data.path);
|
||||
return data.is_safe;
|
||||
return data.safe;
|
||||
}
|
||||
|
||||
void die_upon_dubious_ownership(const char *gitfile, const char *worktree,
|
||||
|
|
|
|||
50
setup.h
50
setup.h
|
|
@ -304,4 +304,54 @@ struct startup_info {
|
|||
extern struct startup_info *startup_info;
|
||||
extern const char *tmp_original_cwd;
|
||||
|
||||
/* Path allowlist */
|
||||
|
||||
struct path_allowlist_cb_data {
|
||||
const char *key;
|
||||
};
|
||||
|
||||
/*
|
||||
* Check the allowlist entry in `allowed` against `target_path`,
|
||||
* updating `*matches` accordingly.
|
||||
*
|
||||
* `allowed` is a single entry of an allowlist of paths, typically one
|
||||
* value of a multi-valued config variable, already expanded by
|
||||
* git_config_pathname(). `target_path` is the (normalized) path being
|
||||
* tested. `*matches` is updated in place:
|
||||
*
|
||||
* - an empty `allowed` resets it to 'false' (so a later, more
|
||||
* specific config scope can clear entries from a broader one),
|
||||
* - "*" sets it to 'true' (allow everything),
|
||||
* - "<path>" sets it to 'true' if <path> equals `target_path`,
|
||||
* - "<path>" + "/" + "*" sets it to 'true' if <path> is a leading
|
||||
* directory of `target_path`,
|
||||
* - anything else leaves `*matches` unchanged.
|
||||
*
|
||||
* `allow_path` is called with `allowed` and `allow_path_cbdata`, and
|
||||
* should return 'true' if the entry is acceptable to the caller. It
|
||||
* lets each caller decide which paths it is willing to consider, and
|
||||
* whether to warn about the ones it rejects. Returning 'false' leaves
|
||||
* `*matches` unchanged.
|
||||
*
|
||||
* Callers are expected to invoke this once per allowlist entry,
|
||||
* typically from a protected-config callback, so that untrusted
|
||||
* repository config cannot influence the decision.
|
||||
*/
|
||||
void path_allowlist_apply(const char *allowed, const char *target_path,
|
||||
bool *matches,
|
||||
bool (*allow_path)(const char *path, void *cbdata),
|
||||
void *allow_path_cbdata);
|
||||
|
||||
/*
|
||||
* Apply one value of a multi-valued config variable holding an
|
||||
* allowlist of paths, expanding it with git_config_pathname() before
|
||||
* checking it against `target_path`. Empty and "*" values are passed
|
||||
* through without expansion, as interpolating them is not
|
||||
* meaningful. See path_allowlist_apply().
|
||||
*/
|
||||
void path_allowlist_config_apply(const char *key, const char *value,
|
||||
const char *target_path, bool *matches,
|
||||
bool (*allow_path)(const char *path, void *cbdata),
|
||||
void *allow_path_cbdata);
|
||||
|
||||
#endif /* SETUP_H */
|
||||
|
|
|
|||
Loading…
Reference in New Issue