config: add a notion of "scope"

A config callback passed to git_config() doesn't know very much about the context in which it sees a variable. It can ask whether the variable comes from a file, and get the file name. But without analyzing the filename (which is hard to do accurately), it cannot tell whether it is in system-level config, user-level config, or repo-specific config. Generally this doesn't matter; the point of not passing this to the callback is that it should treat the config the same no matter where it comes from. But some programs, like upload-pack, are a special case: we should be able to run them in an untrusted repository, which means we cannot use any "dangerous" config from the repository config file (but it is OK to use it from system or user config). This patch teaches the config code to record the "scope" of each variable, and make it available inside config callbacks, similar to how we give access to the filename. The scope is the starting source for a particular parsing operation, and remains the same even if we include other files (so a .git/config which includes another file will remain CONFIG_SCOPE_REPO, as it would be similarly untrusted). Signed-off-by: Jeff King <peff@peff.net> Signed-off-by: Junio C Hamano <gitster@pobox.com>

Jeff King committed May 18, 2016 at 18:44 UTC 9acc5911119ec0209877fbaa0a1e68aa714c191e
4 files changed +53
cache.h
+11
@@ -1604,6 +1604,16 @@ extern const char *get_log_output_encoding(void);
1604 extern const char *get_commit_output_encoding(void);
1605
1606 extern int git_config_parse_parameter(const char *, config_fn_t fn, void *data);
1607 +
1608 +enum config_scope {
1609 + CONFIG_SCOPE_UNKNOWN = 0,
1610 + CONFIG_SCOPE_SYSTEM,
1611 + CONFIG_SCOPE_GLOBAL,
1612 + CONFIG_SCOPE_REPO,
1613 + CONFIG_SCOPE_CMDLINE,
1614 +};
1615 +
1616 +extern enum config_scope current_config_scope(void);
1617 extern const char *current_config_origin_type(void);
1618 extern const char *current_config_name(void);
1619
@@ -1697,6 +1707,7 @@ struct key_value_info {
1707 const char *filename;
1708 int linenr;
1709 const char *origin_type;
1710 + enum config_scope scope;
1711 };
1712
1713 extern NORETURN void git_die_config(const char *key, const char *err, ...) __attribute__((format(printf, 2, 3)));
config.c
+23
@@ -57,6 +57,15 @@ struct config_source {
57 static struct config_source *cf;
58 static struct key_value_info *current_config_kvi;
59
60 +/*
61 + * Similar to the variables above, this gives access to the "scope" of the
62 + * current value (repo, global, etc). For cached values, it can be found via
63 + * the current_config_kvi as above. During parsing, the current value can be
64 + * found in this variable. It's not part of "cf" because it transcends a single
65 + * file (i.e., a file included from .git/config is still in "repo" scope).
66 + */
67 +static enum config_scope current_parsing_scope;
68 +
69 static int zlib_compression_seen;
70
71 /*
@@ -1229,22 +1238,27 @@ static int do_git_config_sequence(config_fn_t fn, void *data)
1238 char *user_config = expand_user_path("~/.gitconfig");
1239 char *repo_config = git_pathdup("config");
1240
1241 + current_parsing_scope = CONFIG_SCOPE_SYSTEM;
1242 if (git_config_system() && !access_or_die(git_etc_gitconfig(), R_OK, 0))
1243 ret += git_config_from_file(fn, git_etc_gitconfig(),
1244 data);
1245
1246 + current_parsing_scope = CONFIG_SCOPE_GLOBAL;
1247 if (xdg_config && !access_or_die(xdg_config, R_OK, ACCESS_EACCES_OK))
1248 ret += git_config_from_file(fn, xdg_config, data);
1249
1250 if (user_config && !access_or_die(user_config, R_OK, ACCESS_EACCES_OK))
1251 ret += git_config_from_file(fn, user_config, data);
1252
1253 + current_parsing_scope = CONFIG_SCOPE_REPO;
1254 if (repo_config && !access_or_die(repo_config, R_OK, 0))
1255 ret += git_config_from_file(fn, repo_config, data);
1256
1257 + current_parsing_scope = CONFIG_SCOPE_CMDLINE;
1258 if (git_config_from_parameters(fn, data) < 0)
1259 die(_("unable to parse command-line config"));
1260
1261 + current_parsing_scope = CONFIG_SCOPE_UNKNOWN;
1262 free(xdg_config);
1263 free(user_config);
1264 free(repo_config);
@@ -1383,6 +1397,7 @@ static int configset_add_value(struct config_set *cs, const char *key, const cha
1397 kv_info->linenr = -1;
1398 kv_info->origin_type = NULL;
1399 }
1400 + kv_info->scope = current_parsing_scope;
1401 si->util = kv_info;
1402
1403 return 0;
@@ -2482,3 +2497,11 @@ const char *current_config_name(void)
2497 die("BUG: current_config_name called outside config callback");
2498 return name ? name : "";
2499 }
2500 +
2501 +enum config_scope current_config_scope(void)
2502 +{
2503 + if (current_config_kvi)
2504 + return current_config_kvi->scope;
2505 + else
2506 + return current_parsing_scope;
2507 +}
t/helper/test-config.c
+16
@@ -35,6 +35,21 @@
35 *
36 */
37
38 +static const char *scope_name(enum config_scope scope)
39 +{
40 + switch (scope) {
41 + case CONFIG_SCOPE_SYSTEM:
42 + return "system";
43 + case CONFIG_SCOPE_GLOBAL:
44 + return "global";
45 + case CONFIG_SCOPE_REPO:
46 + return "repo";
47 + case CONFIG_SCOPE_CMDLINE:
48 + return "cmdline";
49 + default:
50 + return "unknown";
51 + }
52 +}
53 static int iterate_cb(const char *var, const char *value, void *data)
54 {
55 static int nr;
@@ -46,6 +61,7 @@ static int iterate_cb(const char *var, const char *value, void *data)
61 printf("value=%s\n", value ? value : "(null)");
62 printf("origin=%s\n", current_config_origin_type());
63 printf("name=%s\n", current_config_name());
64 + printf("scope=%s\n", scope_name(current_config_scope()));
65
66 return 0;
67 }
t/t1308-config-set.sh
+3
@@ -238,16 +238,19 @@ test_expect_success 'iteration shows correct origins' '
238 value=from-home
239 origin=file
240 name=$(pwd)/.gitconfig
241 + scope=global
242
243 key=foo.bar
244 value=from-repo
245 origin=file
246 name=.git/config
247 + scope=repo
248
249 key=foo.bar
250 value=from-cmdline
251 origin=command line
252 name=
253 + scope=cmdline
254 EOF
255 GIT_CONFIG_PARAMETERS=$cmdline_config test-config iterate >actual &&
256 test_cmp expect actual