]> git.ipfire.org Git - thirdparty/git.git/commitdiff
config API: add "string" version of *_value_multi(), fix segfaults
authorÆvar Arnfjörð Bjarmason <avarab@gmail.com>
Tue, 28 Mar 2023 14:04:27 +0000 (16:04 +0200)
committerJunio C Hamano <gitster@pobox.com>
Tue, 28 Mar 2023 14:37:53 +0000 (07:37 -0700)
Fix numerous and mostly long-standing segfaults in consumers of
the *_config_*value_multi() API. As discussed in the preceding commit
an empty key in the config syntax yields a "NULL" string, which these
users would give to strcmp() (or similar), resulting in segfaults.

As this change shows, most users users of the *_config_*value_multi()
API didn't really want such an an unsafe and low-level API, let's give
them something with the safety of git_config_get_string() instead.

This fix is similar to what the *_string() functions and others
acquired in[1] and [2]. Namely introducing and using a safer
"*_get_string_multi()" variant of the low-level "_*value_multi()"
function.

This fixes segfaults in code introduced in:

  - d811c8e17c6 (versionsort: support reorder prerelease suffixes, 2015-02-26)
  - c026557a373 (versioncmp: generalize version sort suffix reordering, 2016-12-08)
  - a086f921a72 (submodule: decouple url and submodule interest, 2017-03-17)
  - a6be5e6764a (log: add log.excludeDecoration config option, 2020-04-16)
  - 92156291ca8 (log: add default decoration filter, 2022-08-05)
  - 50a044f1e40 (gc: replace config subprocesses with API calls, 2022-09-27)

There are now two users ofthe low-level API:

- One in "builtin/for-each-repo.c", which we'll convert in a
  subsequent commit.

- The "t/helper/test-config.c" code added in [3].

As seen in the preceding commit we need to give the
"t/helper/test-config.c" caller these "NULL" entries.

We could also alter the underlying git_configset_get_value_multi()
function to be "string safe", but doing so would leave no room for
other variants of "*_get_value_multi()" that coerce to other types.

Such coercion can't be built on the string version, since as we've
established "NULL" is a true value in the boolean context, but if we
coerced it to "" for use in a list of strings it'll be subsequently
coerced to "false" as a boolean.

The callback pattern being used here will make it easy to introduce
e.g. a "multi" variant which coerces its values to "bool", "int",
"path" etc.

1. 40ea4ed9032 (Add config_error_nonbool() helper function,
   2008-02-11)
2. 6c47d0e8f39 (config.c: guard config parser from value=NULL,
   2008-02-11).
3. 4c715ebb96a (test-config: add tests for the config_set API,
   2014-07-28)

Signed-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
12 files changed:
builtin/gc.c
builtin/log.c
config.c
config.h
pack-bitmap.c
submodule.c
t/t4202-log.sh
t/t5310-pack-bitmaps.sh
t/t7004-tag.sh
t/t7413-submodule-is-active.sh
t/t7900-maintenance.sh
versioncmp.c

index 2b3da377d52b97fd05f139c38b7d1e67654ecbac..9497bdf23e4bfad786ed3eb1781a44e00df46a0f 100644 (file)
@@ -1510,7 +1510,7 @@ static int maintenance_register(int argc, const char **argv, const char *prefix)
        if (git_config_get("maintenance.strategy"))
                git_config_set("maintenance.strategy", "incremental");
 
-       if (!git_config_get_value_multi(key, &list)) {
+       if (!git_config_get_string_multi(key, &list)) {
                for_each_string_list_item(item, list) {
                        if (!strcmp(maintpath, item->string)) {
                                found = 1;
@@ -1578,8 +1578,8 @@ static int maintenance_unregister(int argc, const char **argv, const char *prefi
                git_configset_add_file(&cs, config_file);
        }
        if (!(config_file
-             ? git_configset_get_value_multi(&cs, key, &list)
-             : git_config_get_value_multi(key, &list))) {
+             ? git_configset_get_string_multi(&cs, key, &list)
+             : git_config_get_string_multi(key, &list))) {
                for_each_string_list_item(item, list) {
                        if (!strcmp(maintpath, item->string)) {
                                found = 1;
index cc9d92f95da895d987c5f4549227babe915d2aca..cd17f3117125227e0f2c695b018c97d4c95ecd76 100644 (file)
@@ -184,8 +184,8 @@ static void set_default_decoration_filter(struct decoration_filter *decoration_f
        struct string_list *include = decoration_filter->include_ref_pattern;
        const struct string_list *config_exclude;
 
-       if (!git_config_get_value_multi("log.excludeDecoration",
-                                       &config_exclude)) {
+       if (!git_config_get_string_multi("log.excludeDecoration",
+                                        &config_exclude)) {
                struct string_list_item *item;
                for_each_string_list_item(item, config_exclude)
                        string_list_append(decoration_filter->exclude_ref_config_pattern,
index 1f654daf6fcc936e9bc28314eb173d1d598c6b68..92d9e7e968ee94299cafa36753bbadc77e3593d1 100644 (file)
--- a/config.c
+++ b/config.c
@@ -2434,6 +2434,25 @@ int git_configset_get_value_multi(struct config_set *cs, const char *key,
        return 0;
 }
 
+static int check_multi_string(struct string_list_item *item, void *util)
+{
+       return item->string ? 0 : config_error_nonbool(util);
+}
+
+int git_configset_get_string_multi(struct config_set *cs, const char *key,
+                                  const struct string_list **dest)
+{
+       int ret;
+
+       if ((ret = git_configset_get_value_multi(cs, key, dest)))
+               return ret;
+       if ((ret = for_each_string_list((struct string_list *)*dest,
+                                       check_multi_string, (void *)key)))
+               return ret;
+
+       return 0;
+}
+
 int git_configset_get(struct config_set *cs, const char *key)
 {
        struct config_set_element *e;
@@ -2602,6 +2621,13 @@ int repo_config_get_value_multi(struct repository *repo, const char *key,
        return git_configset_get_value_multi(repo->config, key, dest);
 }
 
+int repo_config_get_string_multi(struct repository *repo, const char *key,
+                                const struct string_list **dest)
+{
+       git_config_check_init(repo);
+       return git_configset_get_string_multi(repo->config, key, dest);
+}
+
 int repo_config_get_string(struct repository *repo,
                           const char *key, char **dest)
 {
@@ -2717,6 +2743,12 @@ int git_config_get_value_multi(const char *key, const struct string_list **dest)
        return repo_config_get_value_multi(the_repository, key, dest);
 }
 
+int git_config_get_string_multi(const char *key,
+                               const struct string_list **dest)
+{
+       return repo_config_get_string_multi(the_repository, key, dest);
+}
+
 int git_config_get_string(const char *key, char **dest)
 {
        return repo_config_get_string(the_repository, key, dest);
index f228fa9d5d12345265c5bf0ac20eecbb4b3ed65a..65e569e739eca45e19f5b26d3c670eb69feffc4f 100644 (file)
--- a/config.h
+++ b/config.h
@@ -472,6 +472,19 @@ RESULT_MUST_BE_USED
 int git_configset_get_value_multi(struct config_set *cs, const char *key,
                                  const struct string_list **dest);
 
+/**
+ * A validation wrapper for git_configset_get_value_multi() which does
+ * for it what git_configset_get_string() does for
+ * git_configset_get_value().
+ *
+ * The configuration syntax allows for "[section] key", which will
+ * give us a NULL entry in the "struct string_list", as opposed to
+ * "[section] key =" which is the empty string. Most users of the API
+ * are not prepared to handle NULL in a "struct string_list".
+ */
+int git_configset_get_string_multi(struct config_set *cs, const char *key,
+                                  const struct string_list **dest);
+
 /**
  * Clears `config_set` structure, removes all saved variable-value pairs.
  */
@@ -522,6 +535,9 @@ int repo_config_get_value(struct repository *repo,
 RESULT_MUST_BE_USED
 int repo_config_get_value_multi(struct repository *repo, const char *key,
                                const struct string_list **dest);
+RESULT_MUST_BE_USED
+int repo_config_get_string_multi(struct repository *repo, const char *key,
+                                const struct string_list **dest);
 int repo_config_get_string(struct repository *repo,
                           const char *key, char **dest);
 int repo_config_get_string_tmp(struct repository *repo,
@@ -583,6 +599,9 @@ int git_config_get_value(const char *key, const char **value);
 RESULT_MUST_BE_USED
 int git_config_get_value_multi(const char *key,
                               const struct string_list **dest);
+RESULT_MUST_BE_USED
+int git_config_get_string_multi(const char *key,
+                               const struct string_list **dest);
 
 /**
  * Resets and invalidates the config cache.
index 81f0c0e016bd5a59a23fed1b0708dfeddb8b63db..dd05ab03ca003558f18b49b33d0b973bf157aea6 100644 (file)
@@ -2303,7 +2303,7 @@ const struct string_list *bitmap_preferred_tips(struct repository *r)
 {
        const struct string_list *dest;
 
-       if (!repo_config_get_value_multi(r, "pack.preferbitmaptips", &dest))
+       if (!repo_config_get_string_multi(r, "pack.preferbitmaptips", &dest))
                return dest;
        return NULL;
 }
index e43c4230ba39524503f221577aa9dfb06a235003..0f6cf864ed98f73f477e9436892c2cd0a15b9cdf 100644 (file)
@@ -274,7 +274,7 @@ int is_tree_submodule_active(struct repository *repo,
        free(key);
 
        /* submodule.active is set */
-       if (!repo_config_get_value_multi(repo, "submodule.active", &sl)) {
+       if (!repo_config_get_string_multi(repo, "submodule.active", &sl)) {
                struct pathspec ps;
                struct strvec args = STRVEC_INIT;
                const struct string_list_item *item;
index e4f02d8208b4d274935a37962f1bf6d80b858ada..ae73aef922f950f9a289085a5417c84654bbcd8f 100755 (executable)
@@ -835,7 +835,7 @@ test_expect_success 'log.decorate configuration' '
 
 '
 
-test_expect_failure 'parse log.excludeDecoration with no value' '
+test_expect_success 'parse log.excludeDecoration with no value' '
        cp .git/config .git/config.orig &&
        test_when_finished mv .git/config.orig .git/config &&
 
@@ -843,7 +843,11 @@ test_expect_failure 'parse log.excludeDecoration with no value' '
        [log]
                excludeDecoration
        EOF
-       git log --decorate=short
+       cat >expect <<-\EOF &&
+       error: missing value for '\''log.excludeDecoration'\''
+       EOF
+       git log --decorate=short 2>actual &&
+       test_cmp expect actual
 '
 
 test_expect_success 'decorate-refs with glob' '
index 2e65c8139c4f3507cc1d65bc5b357ca756b0d29c..894c750080c589c09370b41102529c505bb603a2 100755 (executable)
@@ -404,7 +404,7 @@ test_bitmap_cases () {
                )
        '
 
-       test_expect_failure 'pack.preferBitmapTips' '
+       test_expect_success 'pack.preferBitmapTips' '
                git init repo &&
                test_when_finished "rm -rf repo" &&
                (
@@ -416,7 +416,11 @@ test_bitmap_cases () {
                        [pack]
                                preferBitmapTips
                        EOF
-                       git repack -adb
+                       cat >expect <<-\EOF &&
+                       error: missing value for '\''pack.preferbitmaptips'\''
+                       EOF
+                       git repack -adb 2>actual &&
+                       test_cmp expect actual
                )
        '
 
index f343551a7d445c097ae4d49373081e83cc2d2e57..f4a31ada79a6ec3b29cff060edbab47296ab2baa 100755 (executable)
@@ -1843,7 +1843,7 @@ test_expect_success 'invalid sort parameter in configuratoin' '
        test_must_fail git tag -l "foo*"
 '
 
-test_expect_failure 'version sort handles empty value for versionsort.{prereleaseSuffix,suffix}' '
+test_expect_success 'version sort handles empty value for versionsort.{prereleaseSuffix,suffix}' '
        cp .git/config .git/config.orig &&
        test_when_finished mv .git/config.orig .git/config &&
 
@@ -1852,7 +1852,12 @@ test_expect_failure 'version sort handles empty value for versionsort.{prereleas
                prereleaseSuffix
                suffix
        EOF
-       git tag -l --sort=version:refname
+       cat >expect <<-\EOF &&
+       error: missing value for '\''versionsort.suffix'\''
+       error: missing value for '\''versionsort.prereleasesuffix'\''
+       EOF
+       git tag -l --sort=version:refname 2>actual &&
+       test_cmp expect actual
 '
 
 test_expect_success 'version sort with prerelease reordering' '
index bfe27e507322144914421584ef0372a988b79e9d..887d181b72ec05ab68f619de3948d667179dcb68 100755 (executable)
@@ -51,7 +51,7 @@ test_expect_success 'is-active works with submodule.<name>.active config' '
        test-tool -C super submodule is-active sub1
 '
 
-test_expect_failure 'is-active handles submodule.active config missing a value' '
+test_expect_success 'is-active handles submodule.active config missing a value' '
        cp super/.git/config super/.git/config.orig &&
        test_when_finished mv super/.git/config.orig super/.git/config &&
 
@@ -60,7 +60,11 @@ test_expect_failure 'is-active handles submodule.active config missing a value'
                active
        EOF
 
-       test-tool -C super submodule is-active sub1
+       cat >expect <<-\EOF &&
+       error: missing value for '\''submodule.active'\''
+       EOF
+       test-tool -C super submodule is-active sub1 2>actual &&
+       test_cmp expect actual
 '
 
 test_expect_success 'is-active works with basic submodule.active config' '
index d82eac6a471eacef7d8038ce1c7b014f87d95bb5..487e326b3fac126fb611e50851465d7d8b963888 100755 (executable)
@@ -524,7 +524,7 @@ test_expect_success 'register and unregister' '
        git maintenance unregister --config-file ./other --force
 '
 
-test_expect_failure 'register with no value for maintenance.repo' '
+test_expect_success 'register with no value for maintenance.repo' '
        cp .git/config .git/config.orig &&
        test_when_finished mv .git/config.orig .git/config &&
 
@@ -532,10 +532,15 @@ test_expect_failure 'register with no value for maintenance.repo' '
        [maintenance]
                repo
        EOF
-       git maintenance register
+       cat >expect <<-\EOF &&
+       error: missing value for '\''maintenance.repo'\''
+       EOF
+       git maintenance register 2>actual &&
+       test_cmp expect actual &&
+       git config maintenance.repo
 '
 
-test_expect_failure 'unregister with no value for maintenance.repo' '
+test_expect_success 'unregister with no value for maintenance.repo' '
        cp .git/config .git/config.orig &&
        test_when_finished mv .git/config.orig .git/config &&
 
@@ -543,8 +548,18 @@ test_expect_failure 'unregister with no value for maintenance.repo' '
        [maintenance]
                repo
        EOF
-       git maintenance unregister &&
-       git maintenance unregister --force
+       cat >expect <<-\EOF &&
+       error: missing value for '\''maintenance.repo'\''
+       EOF
+       test_expect_code 128 git maintenance unregister 2>actual.raw &&
+       grep ^error actual.raw >actual &&
+       test_cmp expect actual &&
+       git config maintenance.repo &&
+
+       git maintenance unregister --force 2>actual.raw &&
+       grep ^error actual.raw >actual &&
+       test_cmp expect actual &&
+       git config maintenance.repo
 '
 
 test_expect_success !MINGW 'register and unregister with regex metacharacters' '
index 60c3a51712291a3225b95ccd2fef593a7cb85c3c..7498da96e0e75fec5efbd92567891a5caeb136df 100644 (file)
@@ -164,8 +164,8 @@ int versioncmp(const char *s1, const char *s2)
                const char *const oldk = "versionsort.prereleasesuffix";
                const struct string_list *newl;
                const struct string_list *oldl;
-               int new = git_config_get_value_multi(newk, &newl);
-               int old = git_config_get_value_multi(oldk, &oldl);
+               int new = git_config_get_string_multi(newk, &newl);
+               int old = git_config_get_string_multi(oldk, &oldl);
 
                if (!new && !old)
                        warning("ignoring %s because %s is set", oldk, newk);