config: remove unneeded struct field

As well as receiving the config key and value, config callbacks also receive a "struct key_value_info" containing information about the source of the key-value pair. Accessing the "path" field of this struct from a callback passed to repo_config() results in a use-after-free. This happens because repo_config() first populates a configset by calling config_with_options() and then iterates over the configset with the callback passed by the caller. When the configset is constructed it takes a shallow copy of the "struct key_value_info" for each config setting. This leads to the use-after-free as the "path" member is freed before config_with_options() returns. We could fix this by interning the "path" field as we do for the "filename" field but the "path" field is not actually needed. It is populated with a copy of the "path" field from "struct config_source". That field was added in d14d42440d8 (config: disallow relative include paths from blobs, 2014-02-19) to distinguish between relative include directives in files and those in blobs. However, since 1b8132d99d8 (i18n: config: unfold error messages marked for translation, 2016-07-28) we can differentiate these by looking at the "origin_type" field in "struct key_value_info". So let's remove the "path" members from "struct config_source" and "struct key_value_info" and instead use a combination of the "filename" and "origin_type" fields to determine the absolute path of relative includes. Signed-off-by: Phillip Wood <phillip.wood@dunelm.org.uk> Signed-off-by: Junio C Hamano <gitster@pobox.com>

Phillip Wood committed Jul 15, 2025 at 15:00 UTC 14d7583beb020a4b3c388f8b7ea580cf2a156ff8
2 files changed +13 -17
config.c
+13 -15
@@ -56,7 +56,6 @@ struct config_source {
56 } u;
57 enum config_origin_type origin_type;
58 const char *name;
59 - const char *path;
59 enum config_error_action default_error_action;
60 int linenr;
61 int eof;
@@ -173,14 +172,14 @@ static int handle_path_include(const struct key_value_info *kvi,
172 if (!is_absolute_path(path)) {
173 char *slash;
174
176 - if (!kvi || !kvi->path) {
175 + if (!kvi || kvi->origin_type != CONFIG_ORIGIN_FILE) {
176 ret = error(_("relative config includes must come from files"));
177 goto cleanup;
178 }
179
181 - slash = find_last_dir_sep(kvi->path);
180 + slash = find_last_dir_sep(kvi->filename);
181 if (slash)
183 - strbuf_add(&buf, kvi->path, slash - kvi->path + 1);
182 + strbuf_add(&buf, kvi->filename, slash - kvi->filename + 1);
183 strbuf_addstr(&buf, path);
184 path = buf.buf;
185 }
@@ -224,11 +223,11 @@ static int prepare_include_condition_pattern(const struct key_value_info *kvi,
223 if (pat->buf[0] == '.' && is_dir_sep(pat->buf[1])) {
224 const char *slash;
225
227 - if (!kvi || !kvi->path)
226 + if (!kvi || kvi->origin_type != CONFIG_ORIGIN_FILE)
227 return error(_("relative config include "
228 "conditionals must come from files"));
229
231 - strbuf_realpath(&path, kvi->path, 1);
230 + strbuf_realpath(&path, kvi->filename, 1);
231 slash = find_last_dir_sep(path.buf);
232 if (!slash)
233 BUG("how is this possible?");
@@ -633,7 +632,6 @@ void kvi_from_param(struct key_value_info *out)
632 out->linenr = -1;
633 out->origin_type = CONFIG_ORIGIN_CMDLINE;
634 out->scope = CONFIG_SCOPE_COMMAND;
636 - out->path = NULL;
635 }
636
637 int git_config_parse_parameter(const char *text,
@@ -1036,7 +1034,6 @@ static void kvi_from_source(struct config_source *cs,
1034 out->origin_type = cs->origin_type;
1035 out->linenr = cs->linenr;
1036 out->scope = scope;
1039 - out->path = cs->path;
1037 }
1038
1039 static int git_parse_source(struct config_source *cs, config_fn_t fn,
@@ -1855,17 +1852,19 @@ static int do_config_from(struct config_source *top, config_fn_t fn,
1852
1853 static int do_config_from_file(config_fn_t fn,
1854 const enum config_origin_type origin_type,
1858 - const char *name, const char *path, FILE *f,
1859 - void *data, enum config_scope scope,
1855 + const char *name, FILE *f, void *data,
1856 + enum config_scope scope,
1857 const struct config_options *opts)
1858 {
1859 struct config_source top = CONFIG_SOURCE_INIT;
1860 int ret;
1861
1862 + if (origin_type == CONFIG_ORIGIN_FILE && (!name || !*name))
1863 + BUG("missing filename for CONFIG_ORIGIN_FILE");
1864 +
1865 top.u.file = f;
1866 top.origin_type = origin_type;
1867 top.name = name;
1868 - top.path = path;
1868 top.default_error_action = CONFIG_ERROR_DIE;
1869 top.do_fgetc = config_file_fgetc;
1870 top.do_ungetc = config_file_ungetc;
@@ -1880,8 +1879,8 @@ static int do_config_from_file(config_fn_t fn,
1879 static int git_config_from_stdin(config_fn_t fn, void *data,
1880 enum config_scope scope)
1881 {
1883 - return do_config_from_file(fn, CONFIG_ORIGIN_STDIN, "", NULL, stdin,
1884 - data, scope, NULL);
1882 + return do_config_from_file(fn, CONFIG_ORIGIN_STDIN, "", stdin, data,
1883 + scope, NULL);
1884 }
1885
1886 int git_config_from_file_with_options(config_fn_t fn, const char *filename,
@@ -1896,7 +1895,7 @@ int git_config_from_file_with_options(config_fn_t fn, const char *filename,
1895 f = fopen_or_warn(filename, "r");
1896 if (f) {
1897 ret = do_config_from_file(fn, CONFIG_ORIGIN_FILE, filename,
1899 - filename, f, data, scope, opts);
1898 + f, data, scope, opts);
1899 fclose(f);
1900 }
1901 return ret;
@@ -1921,7 +1920,6 @@ int git_config_from_mem(config_fn_t fn,
1920 top.u.buf.pos = 0;
1921 top.origin_type = origin_type;
1922 top.name = name;
1924 - top.path = NULL;
1923 top.default_error_action = CONFIG_ERROR_ERROR;
1924 top.do_fgetc = config_buf_fgetc;
1925 top.do_ungetc = config_buf_ungetc;
config.h
-2
@@ -122,14 +122,12 @@ struct key_value_info {
122 int linenr;
123 enum config_origin_type origin_type;
124 enum config_scope scope;
125 - const char *path;
125 };
126 #define KVI_INIT { \
127 .filename = NULL, \
128 .linenr = -1, \
129 .origin_type = CONFIG_ORIGIN_UNKNOWN, \
130 .scope = CONFIG_SCOPE_UNKNOWN, \
132 - .path = NULL, \
131 }
132
133 /* Captures additional information that a config callback can use. */