{"thread":{"id":"58803","subject":"[PATCH] builtin/gc.c: fix use-after-free in maintenance_unregister()","startedAt":"2022-11-15T18:53:25Z","lastAt":"2022-11-16T20:14:17Z","messageCount":8,"participants":["Taylor Blau","Derrick Stolee","Ævar Arnfjörð Bjarmason"],"isPatch":true,"patchVersion":1,"patchTotal":null},"messages":[{"id":"467329","messageId":"2cbead254b77cb02d219bca8f628dc4362c045b0.1668538355.git.me@ttaylorr.com","threadId":"58803","inReplyTo":null,"subject":"[PATCH] builtin/gc.c: fix use-after-free in maintenance_unregister()","fromName":"Taylor Blau","fromEmail":"me@ttaylorr.com","sentAt":"2022-11-15T18:53:17Z","receivedAt":"2022-11-15T18:53:25Z","isPatch":true,"sender":{"key":"me@ttaylorr.com","avatar":"https://avatars.githubusercontent.com/u/301000140?v=4"},"body":"While trying to fix a move based on an uninitialized value (along with a\ndeclaration after the first statement), be0fd57228\n(maintenance --unregister: fix uninit'd data use &\n-Wdeclaration-after-statement, 2022-11-15) unintentionally introduced a\nuse-after-free.\n\nThe problem arises when `maintenance_unregister()` sees a non-NULL\n`config_file` string and thus tries to call\ngit_configset_get_value_multi() to lookup the corresponding values.\n\nWe store the result off, and then call git_configset_clear(), which\nfrees the pointer that we just stored. We then try to read that\nnow-freed pointer a few lines below, and there we have our\nuse-after-free:\n\n    $ ./t7900-maintenance.sh -vxi --run=23 --valgrind\n    [...]\n    + git maintenance unregister --config-file ./other\n    ==3048727== Invalid read of size 8\n    ==3048727==    at 0x1869CA: maintenance_unregister (gc.c:1590)\n    ==3048727==    by 0x188F42: cmd_maintenance (gc.c:2651)\n    ==3048727==    by 0x128C62: run_builtin (git.c:466)\n    ==3048727==    by 0x12907E: handle_builtin (git.c:721)\n    ==3048727==    by 0x1292EC: run_argv (git.c:788)\n    ==3048727==    by 0x12988E: cmd_main (git.c:926)\n    ==3048727==    by 0x21ED39: main (common-main.c:57)\n    ==3048727==  Address 0x4b38bc8 is 24 bytes inside a block of size 64 free'd\n    ==3048727==    at 0x484617B: free (vg_replace_malloc.c:872)\n    ==3048727==    by 0x2D207E: free_individual_entries (hashmap.c:188)\n    ==3048727==    by 0x2D2153: hashmap_clear_ (hashmap.c:207)\n    ==3048727==    by 0x270B5C: git_configset_clear (config.c:2375)\n    ==3048727==    by 0x1869AC: maintenance_unregister (gc.c:1585)\n    ==3048727==    by 0x188F42: cmd_maintenance (gc.c:2651)\n    ==3048727==    by 0x128C62: run_builtin (git.c:466)\n    ==3048727==    by 0x12907E: handle_builtin (git.c:721)\n    ==3048727==    by 0x1292EC: run_argv (git.c:788)\n    ==3048727==    by 0x12988E: cmd_main (git.c:926)\n    ==3048727==    by 0x21ED39: main (common-main.c:57)\n    [...]\n\nResolve this via a partial-revert of be0fd57228. The config_set struct\nnow gets a zero initialization, which makes free()-ing it a noop even\nwithout calling git_configset_init(). When we do initialize it to a\nnon-zero value, it is only free()'d after our last read of `list`.\n\nSigned-off-by: Taylor Blau <me@ttaylorr.com>\n---\nI am disappointed in myself for finding this only after I pushed out a\nhotfix to 'next' and rebuild the downstream branches.\n\nThis should be a minimal fix on top of Ævar's patch to get 'next'\nbuilding again.\n\n builtin/gc.c | 5 ++---\n 1 file changed, 2 insertions(+), 3 deletions(-)\n\ndiff --git a/builtin/gc.c b/builtin/gc.c\nindex d87cf84041..38882a1e35 100644\n--- a/builtin/gc.c\n+++ b/builtin/gc.c\n@@ -1543,6 +1543,7 @@ static int maintenance_unregister(int argc, const char **argv, const char *prefi\n \tint found = 0;\n \tstruct string_list_item *item;\n \tconst struct string_list *list;\n+\tstruct config_set cs = { { 0 } };\n\n \targc = parse_options(argc, argv, prefix, options,\n \t\t\t     builtin_maintenance_unregister_usage, 0);\n@@ -1551,12 +1552,9 @@ static int maintenance_unregister(int argc, const char **argv, const char *prefi\n \t\t\t\t   options);\n\n \tif (config_file) {\n-\t\tstruct config_set cs;\n-\n \t\tgit_configset_init(&cs);\n \t\tgit_configset_add_file(&cs, config_file);\n \t\tlist = git_configset_get_value_multi(&cs, key);\n-\t\tgit_configset_clear(&cs);\n \t} else {\n \t\tlist = git_config_get_value_multi(key);\n \t}\n@@ -1592,6 +1590,7 @@ static int maintenance_unregister(int argc, const char **argv, const char *prefi\n \t\tdie(_(\"repository '%s' is not registered\"), maintpath);\n \t}\n\n+\tgit_configset_clear(&cs);\n \tfree(maintpath);\n \treturn 0;\n }\n--\n2.38.0.16.g393fd4c6db\n"},{"id":"467331","messageId":"d8b732c9-ae41-bf1b-3bb1-6682493bee70@github.com","threadId":"58803","inReplyTo":"2cbead254b77cb02d219bca8f628dc4362c045b0.1668538355.git.me@ttaylorr.com","subject":"Re: [PATCH] builtin/gc.c: fix use-after-free in maintenance_unregister()","fromName":"Derrick Stolee","fromEmail":"derrickstolee@github.com","sentAt":"2022-11-15T19:00:48Z","receivedAt":"2022-11-15T19:00:59Z","isPatch":true,"sender":{"key":"stolee@gmail.com","avatar":"https://avatars.githubusercontent.com/u/570044?v=4"},"body":"On 11/15/2022 1:53 PM, Taylor Blau wrote:\n> While trying to fix a move based on an uninitialized value (along with a\n> declaration after the first statement), be0fd57228\n> (maintenance --unregister: fix uninit'd data use &\n> -Wdeclaration-after-statement, 2022-11-15) unintentionally introduced a\n> use-after-free.\n> \n> The problem arises when `maintenance_unregister()` sees a non-NULL\n> `config_file` string and thus tries to call\n> git_configset_get_value_multi() to lookup the corresponding values.\n> \n> We store the result off, and then call git_configset_clear(), which\n> frees the pointer that we just stored. We then try to read that\n> now-freed pointer a few lines below, and there we have our\n> use-after-free:\n\nMakes sense why this needs to be pulled out to a larger scope, but\nalso why it's so easy to make this mistake.\n\n> +\tstruct config_set cs = { { 0 } };\n> \n>  \targc = parse_options(argc, argv, prefix, options,\n>  \t\t\t     builtin_maintenance_unregister_usage, 0);\n> @@ -1551,12 +1552,9 @@ static int maintenance_unregister(int argc, const char **argv, const char *prefi\n>  \t\t\t\t   options);\n> \n>  \tif (config_file) {\n> -\t\tstruct config_set cs;\n> -\n>  \t\tgit_configset_init(&cs);\n>  \t\tgit_configset_add_file(&cs, config_file);\n>  \t\tlist = git_configset_get_value_multi(&cs, key);\n> -\t\tgit_configset_clear(&cs);\n\nThat the list depends on the configset and not exist as an\nindependent entity is non-obvious, but I'm sure is rooted\nin some kind of memory-saving optimization.\n\n>  \t} else {\n>  \t\tlist = git_config_get_value_multi(key);\n>  \t}\n> @@ -1592,6 +1590,7 @@ static int maintenance_unregister(int argc, const char **argv, const char *prefi\n>  \t\tdie(_(\"repository '%s' is not registered\"), maintpath);\n>  \t}\n> \n> +\tgit_configset_clear(&cs);\n>  \tfree(maintpath);\n>  \treturn 0;\n>  }\n\nThanks for drilling down on this. LGTM.\n\n-Stolee\n"},{"id":"467337","messageId":"221115.86r0y4j8tr.gmgdl@evledraar.gmail.com","threadId":"58803","inReplyTo":"2cbead254b77cb02d219bca8f628dc4362c045b0.1668538355.git.me@ttaylorr.com","subject":"Re: [PATCH] builtin/gc.c: fix use-after-free in maintenance_unregister()","fromName":"Ævar Arnfjörð Bjarmason","fromEmail":"avarab@gmail.com","sentAt":"2022-11-15T19:41:44Z","receivedAt":"2022-11-15T19:50:47Z","isPatch":true,"sender":{"key":"avarab@gmail.com","avatar":"https://avatars.githubusercontent.com/u/45301?v=4"},"body":"\nOn Tue, Nov 15 2022, Taylor Blau wrote:\n\n> While trying to fix a move based on an uninitialized value (along with a\n> declaration after the first statement), be0fd57228\n> (maintenance --unregister: fix uninit'd data use &\n> -Wdeclaration-after-statement, 2022-11-15) unintentionally introduced a\n> use-after-free.\n>\n> The problem arises when `maintenance_unregister()` sees a non-NULL\n> `config_file` string and thus tries to call\n> git_configset_get_value_multi() to lookup the corresponding values.\n>\n> We store the result off, and then call git_configset_clear(), which\n> frees the pointer that we just stored. We then try to read that\n> now-freed pointer a few lines below, and there we have our\n> use-after-free:\n>\n>     $ ./t7900-maintenance.sh -vxi --run=23 --valgrind\n>     [...]\n>     + git maintenance unregister --config-file ./other\n>     ==3048727== Invalid read of size 8\n>     ==3048727==    at 0x1869CA: maintenance_unregister (gc.c:1590)\n>     ==3048727==    by 0x188F42: cmd_maintenance (gc.c:2651)\n>     ==3048727==    by 0x128C62: run_builtin (git.c:466)\n>     ==3048727==    by 0x12907E: handle_builtin (git.c:721)\n>     ==3048727==    by 0x1292EC: run_argv (git.c:788)\n>     ==3048727==    by 0x12988E: cmd_main (git.c:926)\n>     ==3048727==    by 0x21ED39: main (common-main.c:57)\n>     ==3048727==  Address 0x4b38bc8 is 24 bytes inside a block of size 64 free'd\n>     ==3048727==    at 0x484617B: free (vg_replace_malloc.c:872)\n>     ==3048727==    by 0x2D207E: free_individual_entries (hashmap.c:188)\n>     ==3048727==    by 0x2D2153: hashmap_clear_ (hashmap.c:207)\n>     ==3048727==    by 0x270B5C: git_configset_clear (config.c:2375)\n>     ==3048727==    by 0x1869AC: maintenance_unregister (gc.c:1585)\n>     ==3048727==    by 0x188F42: cmd_maintenance (gc.c:2651)\n>     ==3048727==    by 0x128C62: run_builtin (git.c:466)\n>     ==3048727==    by 0x12907E: handle_builtin (git.c:721)\n>     ==3048727==    by 0x1292EC: run_argv (git.c:788)\n>     ==3048727==    by 0x12988E: cmd_main (git.c:926)\n>     ==3048727==    by 0x21ED39: main (common-main.c:57)\n>     [...]\n>\n> Resolve this via a partial-revert of be0fd57228. The config_set struct\n> now gets a zero initialization, which makes free()-ing it a noop even\n> without calling git_configset_init(). When we do initialize it to a\n> non-zero value, it is only free()'d after our last read of `list`.\n>\n> Signed-off-by: Taylor Blau <me@ttaylorr.com>\n> ---\n> I am disappointed in myself for finding this only after I pushed out a\n> hotfix to 'next' and rebuild the downstream branches.\n>\n> This should be a minimal fix on top of Ævar's patch to get 'next'\n> building again.\n\nI'm also disappointed in myself, sorry. I *did* test it locally with\nvalgrind, but obviously fat-fingered it somehow and tested the wrong\nversion. Sorry!\n\n>  builtin/gc.c | 5 ++---\n>  1 file changed, 2 insertions(+), 3 deletions(-)\n>\n> diff --git a/builtin/gc.c b/builtin/gc.c\n> index d87cf84041..38882a1e35 100644\n> --- a/builtin/gc.c\n> +++ b/builtin/gc.c\n> @@ -1543,6 +1543,7 @@ static int maintenance_unregister(int argc, const char **argv, const char *prefi\n>  \tint found = 0;\n>  \tstruct string_list_item *item;\n>  \tconst struct string_list *list;\n> +\tstruct config_set cs = { { 0 } };\n\nJust \"{ 0 }\" here instead? I see it may have been copied from some older\npre-image though, and they'll do the same in either case, so it's not\nimportant...\n"},{"id":"467338","messageId":"Y3Puhtj8Q8kf2wt0@nand.local","threadId":"58803","inReplyTo":"221115.86r0y4j8tr.gmgdl@evledraar.gmail.com","subject":"Re: [PATCH] builtin/gc.c: fix use-after-free in maintenance_unregister()","fromName":"Taylor Blau","fromEmail":"me@ttaylorr.com","sentAt":"2022-11-15T19:54:46Z","receivedAt":"2022-11-15T19:55:07Z","isPatch":true,"sender":{"key":"me@ttaylorr.com","avatar":"https://avatars.githubusercontent.com/u/301000140?v=4"},"body":"On Tue, Nov 15, 2022 at 08:41:44PM +0100, Ævar Arnfjörð Bjarmason wrote:\n> > I am disappointed in myself for finding this only after I pushed out a\n> > hotfix to 'next' and rebuild the downstream branches.\n> >\n> > This should be a minimal fix on top of Ævar's patch to get 'next'\n> > building again.\n>\n> I'm also disappointed in myself, sorry. I *did* test it locally with\n> valgrind, but obviously fat-fingered it somehow and tested the wrong\n> version. Sorry!\n\nIt's OK. Let's not beat ourselves up too much, and instead focusing on\nmaking sure the quality for the next pushout is higher (which is more on\nme than it is on you).\n\n> >  builtin/gc.c | 5 ++---\n> >  1 file changed, 2 insertions(+), 3 deletions(-)\n> >\n> > diff --git a/builtin/gc.c b/builtin/gc.c\n> > index d87cf84041..38882a1e35 100644\n> > --- a/builtin/gc.c\n> > +++ b/builtin/gc.c\n> > @@ -1543,6 +1543,7 @@ static int maintenance_unregister(int argc, const char **argv, const char *prefi\n> >  \tint found = 0;\n> >  \tstruct string_list_item *item;\n> >  \tconst struct string_list *list;\n> > +\tstruct config_set cs = { { 0 } };\n>\n> Just \"{ 0 }\" here instead? I see it may have been copied from some older\n> pre-image though, and they'll do the same in either case, so it's not\n> important...\n\nCopying from other zero-initializations of `struct config_set`:\n\n    $ git grep -oh 'struct config_set.*= {.*' | sort | uniq -c\n          3 struct config_set cs = { { 0 } };\n\nThanks,\nTaylor\n"},{"id":"467339","messageId":"221115.86mt8sj8fw.gmgdl@evledraar.gmail.com","threadId":"58803","inReplyTo":"d8b732c9-ae41-bf1b-3bb1-6682493bee70@github.com","subject":"Re: [PATCH] builtin/gc.c: fix use-after-free in maintenance_unregister()","fromName":"Ævar Arnfjörð Bjarmason","fromEmail":"avarab@gmail.com","sentAt":"2022-11-15T19:51:26Z","receivedAt":"2022-11-15T19:59:17Z","isPatch":true,"sender":{"key":"avarab@gmail.com","avatar":"https://avatars.githubusercontent.com/u/45301?v=4"},"body":"\nOn Tue, Nov 15 2022, Derrick Stolee wrote:\n\n> On 11/15/2022 1:53 PM, Taylor Blau wrote:\n>> While trying to fix a move based on an uninitialized value (along with a\n>> declaration after the first statement), be0fd57228\n>> (maintenance --unregister: fix uninit'd data use &\n>> -Wdeclaration-after-statement, 2022-11-15) unintentionally introduced a\n>> use-after-free.\n>> \n>> The problem arises when `maintenance_unregister()` sees a non-NULL\n>> `config_file` string and thus tries to call\n>> git_configset_get_value_multi() to lookup the corresponding values.\n>> \n>> We store the result off, and then call git_configset_clear(), which\n>> frees the pointer that we just stored. We then try to read that\n>> now-freed pointer a few lines below, and there we have our\n>> use-after-free:\n>\n> Makes sense why this needs to be pulled out to a larger scope, but\n> also why it's so easy to make this mistake.\n\nYeah, the config API's full of foot-guns, although here we return a\n\"const struct string_list *\", not a \"struct string_list *\", so in\nretrospect this should be rather obvious...\n\nBut still, we should probably as #leftoverbits make it behave\nconsistently wrt naming. I.e. in this case it's\ngit_configset_get_value_multi() really behaves like a\ngit_configset_get_string_tmp(), and there's no equivalent of a\ngit_configset_get_string() (i.e. xstrdup()'d) for *_multi().\n\n>> +\tstruct config_set cs = { { 0 } };\n>> \n>>  \targc = parse_options(argc, argv, prefix, options,\n>>  \t\t\t     builtin_maintenance_unregister_usage, 0);\n>> @@ -1551,12 +1552,9 @@ static int maintenance_unregister(int argc, const char **argv, const char *prefi\n>>  \t\t\t\t   options);\n>> \n>>  \tif (config_file) {\n>> -\t\tstruct config_set cs;\n>> -\n>>  \t\tgit_configset_init(&cs);\n>>  \t\tgit_configset_add_file(&cs, config_file);\n>>  \t\tlist = git_configset_get_value_multi(&cs, key);\n>> -\t\tgit_configset_clear(&cs);\n>\n> That the list depends on the configset and not exist as an\n> independent entity is non-obvious, but I'm sure is rooted\n> in some kind of memory-saving optimization.\n\nYes, and it's probably worth keeping that, but I haven't benchmarked\netc. This is only a problem in practice if you're constructing your own\nconfigset, e.g. here because we have a custom config file. So for most\nusers this API is safe in general, i.e. we free() it, but it's the\nconfig that's in \"the_repository\" normally, so it outlives any \"normal\"\ncode.\n\n>>  \t} else {\n>>  \t\tlist = git_config_get_value_multi(key);\n>>  \t}\n>> @@ -1592,6 +1590,7 @@ static int maintenance_unregister(int argc, const char **argv, const char *prefi\n>>  \t\tdie(_(\"repository '%s' is not registered\"), maintpath);\n>>  \t}\n>> \n>> +\tgit_configset_clear(&cs);\n>>  \tfree(maintpath);\n>>  \treturn 0;\n>>  }\n>\n> Thanks for drilling down on this. LGTM.\n\nOn the related subject of config API foot-guns, it would be great if you\ncould look over the in-flight series I have to make related parts of the\nconfig API safe by default [1].\n\n8/9 there fixes 6 segfaults, 3 of which are git blame'd to you :), and\n9/9 a foot-gun-y interaction with the strvec API, which you'll also\nprobably find interesting...\n\n1. https://lore.kernel.org/git/cover-v2-0.9-00000000000-20221101T225822Z-avarab@gmail.com/\n"},{"id":"467364","messageId":"2ce55cf7-5eef-8e6d-7b91-9a4bc16ed1c4@github.com","threadId":"58803","inReplyTo":"Y3Puhtj8Q8kf2wt0@nand.local","subject":"Re: [PATCH] builtin/gc.c: fix use-after-free in maintenance_unregister()","fromName":"Derrick Stolee","fromEmail":"derrickstolee@github.com","sentAt":"2022-11-16T13:44:00Z","receivedAt":"2022-11-16T13:44:25Z","isPatch":true,"sender":{"key":"stolee@gmail.com","avatar":"https://avatars.githubusercontent.com/u/570044?v=4"},"body":"On 11/15/22 2:54 PM, Taylor Blau wrote:\n> On Tue, Nov 15, 2022 at 08:41:44PM +0100, Ævar Arnfjörð Bjarmason wrote:\n>>> @@ -1543,6 +1543,7 @@ static int maintenance_unregister(int argc, const char **argv, const char *prefi\n>>>  \tint found = 0;\n>>>  \tstruct string_list_item *item;\n>>>  \tconst struct string_list *list;\n>>> +\tstruct config_set cs = { { 0 } };\n>>\n>> Just \"{ 0 }\" here instead? I see it may have been copied from some older\n>> pre-image though, and they'll do the same in either case, so it's not\n>> important...\n> \n> Copying from other zero-initializations of `struct config_set`:\n> \n>     $ git grep -oh 'struct config_set.*= {.*' | sort | uniq -c\n>           3 struct config_set cs = { { 0 } };\n\nYes, without the double braces the compiler will complain on\nmacOS, I believe.\n\nThanks,\n-Stolee\n"},{"id":"467372","messageId":"221116.865yffj5ha.gmgdl@evledraar.gmail.com","threadId":"58803","inReplyTo":"2ce55cf7-5eef-8e6d-7b91-9a4bc16ed1c4@github.com","subject":"Re: [PATCH] builtin/gc.c: fix use-after-free in maintenance_unregister()","fromName":"Ævar Arnfjörð Bjarmason","fromEmail":"avarab@gmail.com","sentAt":"2022-11-16T15:14:30Z","receivedAt":"2022-11-16T15:15:20Z","isPatch":true,"sender":{"key":"avarab@gmail.com","avatar":"https://avatars.githubusercontent.com/u/45301?v=4"},"body":"\nOn Wed, Nov 16 2022, Derrick Stolee wrote:\n\n> On 11/15/22 2:54 PM, Taylor Blau wrote:\n>> On Tue, Nov 15, 2022 at 08:41:44PM +0100, Ævar Arnfjörð Bjarmason wrote:\n>>>> @@ -1543,6 +1543,7 @@ static int maintenance_unregister(int argc, const char **argv, const char *prefi\n>>>>  \tint found = 0;\n>>>>  \tstruct string_list_item *item;\n>>>>  \tconst struct string_list *list;\n>>>> +\tstruct config_set cs = { { 0 } };\n>>>\n>>> Just \"{ 0 }\" here instead? I see it may have been copied from some older\n>>> pre-image though, and they'll do the same in either case, so it's not\n>>> important...\n>> \n>> Copying from other zero-initializations of `struct config_set`:\n>> \n>>     $ git grep -oh 'struct config_set.*= {.*' | sort | uniq -c\n>>           3 struct config_set cs = { { 0 } };\n>\n> Yes, without the double braces the compiler will complain on\n> macOS, I believe.\n\nAh, that was sorted in 54795d37d9e (config.mak.dev: disable suggest\nbraces error on old clang versions, 2022-10-10).\n\nIt's fine here, we can follow-up for the #leftoverbits of changing those\nsome other time.\n"},{"id":"467391","messageId":"Y3VEk5VbAow0bqpS@nand.local","threadId":"58803","inReplyTo":"221116.865yffj5ha.gmgdl@evledraar.gmail.com","subject":"Re: [PATCH] builtin/gc.c: fix use-after-free in maintenance_unregister()","fromName":"Taylor Blau","fromEmail":"me@ttaylorr.com","sentAt":"2022-11-16T20:14:11Z","receivedAt":"2022-11-16T20:14:17Z","isPatch":true,"sender":{"key":"me@ttaylorr.com","avatar":"https://avatars.githubusercontent.com/u/301000140?v=4"},"body":"On Wed, Nov 16, 2022 at 04:14:30PM +0100, Ævar Arnfjörð Bjarmason wrote:\n> >> Copying from other zero-initializations of `struct config_set`:\n> >>\n> >>     $ git grep -oh 'struct config_set.*= {.*' | sort | uniq -c\n> >>           3 struct config_set cs = { { 0 } };\n> >\n> > Yes, without the double braces the compiler will complain on\n> > macOS, I believe.\n>\n> Ah, that was sorted in 54795d37d9e (config.mak.dev: disable suggest\n> braces error on old clang versions, 2022-10-10).\n>\n> It's fine here, we can follow-up for the #leftoverbits of changing those\n> some other time.\n\nThanks for the reference to 54795d37d9e, Ævar.\n\nThere are a small handful of these sitting around in our codebase:\n\n    $ git grep '{ { '\n    builtin/gc.c:   struct config_set cs = { { 0 } };\n    builtin/worktree.c:             struct config_set cs = { { 0 } };\n    oidmap.h:#define OIDMAP_INIT { { NULL } }\n    oidset.h:#define OIDSET_INIT { { 0 } }\n    reftable/stack_test.c:  struct reftable_ref_record refs[2] = { { NULL } };\n    reftable/stack_test.c:  struct reftable_log_record logs[2] = { { NULL } };\n    reftable/stack_test.c:  struct reftable_ref_record refs[2] = { { NULL } };\n    reftable/stack_test.c:  struct reftable_log_record logs[2] = { { NULL } };\n    reftable/stack_test.c:  struct reftable_log_record logs[20] = { { NULL } };\n    reset.c:        struct tree_desc desc[2] = { { NULL }, { NULL } };\n    t/helper/test-ref-store.c:static struct flag_definition empty_flags[] = { { NULL, 0 } };\n    worktree.c:     struct config_set cs = { { 0 } };\n\nBut I tend to agree that I'm not all that eager to go and change these\nfor the sake of changing them. I think we can slowly let them age out of\nthe codebase when we're touching nearby areas in the future.\n\nThanks,\nTaylor\n"}]}