git/list[1] front-page[2] threads[3] people[4] search[5] about
 

Re: [PATCH v2 2/4] SANITIZE tests: fix memory leaks in t13*config*, add to whitelist

From
Ævar Arnfjörð Bjarmason <avarab@gmail.com>
Date
Jul 16, 2021, 07:46 UTC
Message-ID
<87wnpqy8zd.fsf@evledraar.gmail.com>
In-Reply-To
<YPCrvOce5qRWk6Rq@coredump.intra.peff.net>
On Thu, Jul 15 2021, Jeff King wrote:
Show 103 quoted lines
> On Wed, Jul 14, 2021 at 08:57:37PM +0200, Andrzej Hunt wrote:
>
>> > @@ -1331,8 +1336,10 @@ static int git_default_core_config(const char *var, const char *value, void *cb)
>> >   	if (!strcmp(var, "core.attributesfile"))
>> >   		return git_config_pathname(&git_attributes_file, var, value);
>> > -	if (!strcmp(var, "core.hookspath"))
>> > +	if (!strcmp(var, "core.hookspath")) {
>> > +		UNLEAK(git_hooks_path);
>> >   		return git_config_pathname(&git_hooks_path, var, value);
>> > +	}
>> 
>> Why is the UNLEAK necessary here? We generally want to limit use of UNLEAK
>> to cmd_* functions or direct helpers. git_default_core_config() seems
>> generic enough that it could be called from anywhere, and using UNLEAK here
>> means we're potentially masking a real leak?
>> 
>> IIUC the leak here happens because:
>> - git_hooks_path is a global variable - hence it's unlikely we'd ever
>>   bother cleaning it up.
>> - git_default_core_config() gets called a first time with
>>   core.hookspath, and we end up allocating new memory into
>>   git_hooks_path.
>> - git_default_core_config() gets called again with core.hookspath,
>>   and we overwrite git_hooks_path with a new string which leaks
>>   the string that git_hooks_path used to point to.
>> 
>> So I think the real fix is to free(git_hooks_path) instead of an UNLEAK?
>> (Looking at the surrounding code, it looks like the same pattern of leak
>> might be repeated for other similar globals - is it worth auditing those
>> while we're here?)
>
> This is a common leak pattern in Git. We do something like:
>
>   static const char *foo = "default";
>   ...
>   int config_cb(const char *var, const char *value, void *)
>   {
>           if (!strcmp(var, "core.foo"))
> 	          foo = xstrdup(value);
>   }
>
> So we leak if the variable appears twice. But we can't just call
> "free(foo)" here. In the first call, it's pointing to a string literal!
>
> In the case of git_hooks_path, it defaults to NULL, so this works out
> OK. But it's setting up a trap for somebody later on, who assigns it a
> default value (and the compiler won't help; it's a "const char *", so
> the assignment is fine, and the free() would already be casting away the
> constness).
>
> I see a few possible solutions:
>
>   - instead of strdup'ing long-lived config values, strintern() them.
>     This is really leaking them, but in a way that we hold on to the old
>     values. This is actually more or less what UNLEAK() is doing under
>     the hood (saving a reference to the old buffer, even the variable is
>     overwritten).
>
>   - find a way to tell when a string comes from the heap versus a
>     literal. I don't think you can do this portably without keeping your
>     own separate flag. We could abstract away some of the pain with a
>     struct like:
>
>        struct def_string {
>                /* might point to heap memory; const because you must
>                 * check flag before modifying */
>                const char *value;
>                int from_heap;
>        }
>
>        /* regular static initialization is OK if you don't want a default */
>        #define DEF_STRING_INIT(str) { .value = str }
>
>        static void def_string_set(struct def_string *ds, const char *value)
>        {
>                if (ds->from_heap)
>                        free(ds->value);
>                ds->value = xstrdup(value);
>                ds->from_heap = 1;
>        }
>
>     The annoying thing is all of the users need to refer to
>     git_hook_path.value instead of just git_hook_path. If you don't mind
>     a little macro hackery, we could get around that by declaring pairs
>     of variables. Like:
>
>       #define DEF_STRING_DECLARE(name, value) \
>       const char *name = value; \
>       int name##_from_heap
>
>       #define DEF_STRING_SET(name, value) do { \
>               if (name##_from_heap) \
>                       free(name); \
>               name = xstrdup(value); \
>               name##_from_heap = 1; \
>       } while(0)
>
> I can't say I _love_ any of that, but I think it would work (and
> probably we'd adapt our helpers like git_config_pathname() to take a
> def_string. Or I guess just have a def_string_free() which can be called
> before writing into them).
>
> But maybe there's a better solution I'm missing.

Instead of: "int from_heap" in your "def_string" I think we should just use "struct string_list_item". I.e. you want a void* here. Why?

<Digression>

I have an unsent series for handling some more common cases in the string-list API. I started writing it due to a very related problem, i.e. that we conflate "string init dup/nodup" with "do we want to free?".

We (ab)use the "strdup_strings" in a few places to free that sort of thing at the end if we have heap-allocated strings, but ones we did not strdup ourselves, e.g. this in merge-ort.c (not picking on Elijah (CC'd) here, it's common in lots of places, and this one was pretty much lifted from merge-recursive).

        opti->paths_to_free.strdup_strings = 1;
        string_list_clear(&opti->paths_to_free, 0);
        opti->paths_to_free.strdup_strings = 0;

So I improved the string-list and strmap free functions so you can instead do:

    string_list_clear_strings((&opti->paths_to_free, 0);

And that along with some other changes allows you to clear (or not) any combination of the string, util, or have a callback function of your own run (but be ensured to run all of those before we get to any of the other freeing).

</Digression>

You must be thinking what any of this has to do with heap strings in C, well one common case you've not discussed is that we sometimes do the equivalent of, with string-list.h or not (somewhat pseudocode);

	void add_to_list(struct string_list *list, char *on_heap_now_we_own_it)
	{
		char *ptr = on_heap_now_we_own_it;
		char *mydup = xstrdup("foo");
	        ptr++; /* skip first byte */
		string_list_append(list, ptr);
		string_list_append(list, mydup);
	}
And:
        struct string_list list = STRING_LIST_INIT_NODUP;
        /* other stuff here, we get strings from somewhere etc. */
        add_to_list(list, some_string);

So now you're left with needing to free both at the end, but we since we did ptr++ there we can't free() that (we'd need to free(ptr - 1), but how to keep track of that?).

Well, tying this back to my clear() improvements for string-list.h I thought a really neat solution to this was:

    string_list_append(list, ptr)->util = on_heap_now_we_own_it;
    string_list_append(list, mydup)->util = mydup;

I.e. by convention we store the pointer we need to free (if any) in the "util" field.

And then if you get a string not from the heap you just leave the "util" as NULL, and at the end you just free() all your "util" fields, and it just so happens that some of them are the same as the "string" field.

We're not in the habit of passing loose "string_list_item" around now, but I don't see why we wouldn't (possibly with a change to extract that bit out, so we could use it in other places).

The neat thing about doing this is also that you're not left with every API boundary needing to deal with your new "def_string", a lot of them use string_list already, and hardly need to change anything, to the extent that we do need to change anything having a "void *util" is a lot more generally usable. You end up getting memory management for free as you gain a feature to pass arbitrary data along with your items.

Previous: Jeff KingNext: Jeff King
Message 35 of 125 in “UNLEAK(), leak checking in the default tests etc.”
  1. Ævar Arnfjörð BjarmasonJun 9, 2021
  2. Andrzej HuntJun 9, 2021
  3. Felipe ContrerasJun 9, 2021
  4. Jeff KingJun 10, 2021
  5. Ævar Arnfjörð BjarmasonJun 10, 2021
  6. Jeff KingJun 10, 2021
  7. Andrzej HuntJun 10, 2021
  8. Jeff KingJun 10, 2021
  9. Andrzej HuntJun 11, 2021
  10. SZEDER GáborJun 10, 2021
  11. 0/4 add a test mode for SANITIZE=leak, run it in CIÆvar Arnfjörð Bjarmason, Jul 14, 2021
  12. 1/4 tests: add a test mode for SANITIZE=leak, run it in CIÆvar Arnfjörð Bjarmason, Jul 14, 2021
  13. Đoàn Trần Công DanhJul 14, 2021
  14. 2/4 SANITIZE tests: fix memory leaks in t13*config*, add to whitelistÆvar Arnfjörð Bjarmason, Jul 14, 2021
  15. 3/4 SANITIZE tests: fix memory leaks in t5701*, add to whitelistÆvar Arnfjörð Bjarmason, Jul 14, 2021
  16. 4/4 SANITIZE tests: fix leak in mailmap.cÆvar Arnfjörð Bjarmason, Jul 14, 2021
  17. Eric SunshineJul 14, 2021
  18. 0/4 add a test mode for SANITIZE=leak, run it in CIÆvar Arnfjörð Bjarmason, Jul 14, 2021
  19. 1/4 tests: add a test mode for SANITIZE=leak, run it in CIÆvar Arnfjörð Bjarmason, Jul 14, 2021
  20. Andrzej HuntJul 14, 2021
  21. Ævar Arnfjörð BjarmasonJul 14, 2021
  22. Jeff KingJul 15, 2021
  23. Jeff KingJul 15, 2021
  24. Ævar Arnfjörð BjarmasonJul 16, 2021
  25. Jeff KingJul 16, 2021
  26. Jeff KingJul 16, 2021
  27. Ævar Arnfjörð BjarmasonJul 16, 2021
  28. Jeff KingJul 16, 2021
  29. 2/4 SANITIZE tests: fix memory leaks in t13*config*, add to whitelistÆvar Arnfjörð Bjarmason, Jul 14, 2021
  30. Andrzej HuntJul 14, 2021
  31. Ævar Arnfjörð BjarmasonJul 14, 2021
  32. Jeff KingJul 15, 2021
  33. Andrzej HuntJul 16, 2021
  34. Jeff KingJul 16, 2021
  35. Ævar Arnfjörð BjarmasonJul 16, 2021
  36. Jeff KingJul 16, 2021
  37. Ævar Arnfjörð BjarmasonAug 31, 2021
  38. Jeff KingSep 1, 2021
  39. Ævar Arnfjörð BjarmasonSep 1, 2021
  40. 3/4 SANITIZE tests: fix memory leaks in t5701*, add to whitelistÆvar Arnfjörð Bjarmason, Jul 14, 2021
  41. Andrzej HuntJul 15, 2021
  42. Jeff KingJul 15, 2021
  43. protocol-caps.c: fix memory leak in send_info()Ævar Arnfjörð Bjarmason, Aug 31, 2021
  44. Bruno AlbuquerqueAug 31, 2021
  45. Junio C HamanoAug 31, 2021
  46. 4/4 SANITIZE tests: fix leak in mailmap.cÆvar Arnfjörð Bjarmason, Jul 14, 2021
  47. mailmap.c: fix a memory leak in free_mailap_{info,entry}()Ævar Arnfjörð Bjarmason, Aug 31, 2021
  48. Eric SunshineAug 31, 2021
  49. Jeff KingAug 31, 2021
  50. Junio C HamanoAug 31, 2021
  51. Andrzej HuntJul 15, 2021
  52. 0/8 add a test mode for SANITIZE=leak, run it in CIÆvar Arnfjörð Bjarmason, Aug 31, 2021
  53. Jeff KingSep 1, 2021
  54. Jeff KingSep 1, 2021
  55. Ævar Arnfjörð BjarmasonSep 2, 2021
  56. Jeff KingSep 3, 2021
  57. 0/3 add a test mode for SANITIZE=leak, run it in CIÆvar Arnfjörð Bjarmason, Sep 7, 2021
  58. 1/3 Makefile: add SANITIZE=leak flag to GIT-BUILD-OPTIONSÆvar Arnfjörð Bjarmason, Sep 7, 2021
  59. 2/3 CI: refactor "if" to "case" statementÆvar Arnfjörð Bjarmason, Sep 7, 2021
  60. 3/3 tests: add a test mode for SANITIZE=leak, run it in CIÆvar Arnfjörð Bjarmason, Sep 7, 2021
  61. Eric SunshineSep 7, 2021
  62. Jeff KingSep 7, 2021
  63. Jeff KingSep 7, 2021
  64. Junio C HamanoSep 7, 2021
  65. 0/3 add a test mode for SANITIZE=leak, run it in CIÆvar Arnfjörð Bjarmason, Sep 7, 2021
  66. 1/3 Makefile: add SANITIZE=leak flag to GIT-BUILD-OPTIONSÆvar Arnfjörð Bjarmason, Sep 7, 2021
  67. 2/3 CI: refactor "if" to "case" statementÆvar Arnfjörð Bjarmason, Sep 7, 2021
  68. 3/3 tests: add a test mode for SANITIZE=leak, run it in CIÆvar Arnfjörð Bjarmason, Sep 7, 2021
  69. Eric SunshineSep 8, 2021
  70. fixup! tests: add a test mode for SANITIZE=leak, run it in CICarlo Marcelo Arenas Belón, Sep 16, 2021
  71. Ævar Arnfjörð BjarmasonSep 16, 2021
  72. Junio C HamanoSep 8, 2021
  73. Ævar Arnfjörð BjarmasonSep 8, 2021
  74. Emily ShafferSep 9, 2021
  75. 0/2 add a test mode for SANITIZE=leak, run it in CIÆvar Arnfjörð Bjarmason, Sep 16, 2021
  76. 1/2 Makefile: add SANITIZE=leak flag to GIT-BUILD-OPTIONSÆvar Arnfjörð Bjarmason, Sep 16, 2021
  77. 2/2 tests: add a test mode for SANITIZE=leak, run it in CIÆvar Arnfjörð Bjarmason, Sep 16, 2021
  78. 0/2 add a test mode for SANITIZE=leak, run it in CIÆvar Arnfjörð Bjarmason, Sep 19, 2021
  79. 2/2 tests: add a test mode for SANITIZE=leak, run it in CIÆvar Arnfjörð Bjarmason, Sep 19, 2021
  80. fixup! tests: add a test mode for SANITIZE=leak, run it in CICarlo Marcelo Arenas Belón, Sep 22, 2021
  81. Ævar Arnfjörð BjarmasonSep 23, 2021
  82. 1/2 Makefile: add SANITIZE=leak flag to GIT-BUILD-OPTIONSÆvar Arnfjörð Bjarmason, Sep 19, 2021
  83. 0/2 add a test mode for SANITIZE=leak, run it in CIÆvar Arnfjörð Bjarmason, Sep 23, 2021
  84. 1/2 Makefile: add SANITIZE=leak flag to GIT-BUILD-OPTIONSÆvar Arnfjörð Bjarmason, Sep 23, 2021
  85. 2/2 tests: add a test mode for SANITIZE=leak, run it in CIÆvar Arnfjörð Bjarmason, Sep 23, 2021
  86. Re* [PATCH v8 2/2] tests: add a test mode for SANITIZE=leak, run it in CIJunio C Hamano, Nov 3, 2021
  87. Junio C HamanoNov 3, 2021
  88. Ævar Arnfjörð BjarmasonNov 4, 2021
  89. t0006: date_mode can leak .strftime_fmt memberÆvar Arnfjörð Bjarmason, Nov 16, 2021
  90. Junio C HamanoNov 16, 2021
  91. Jeff KingNov 16, 2021
  92. 0/5 date.[ch] API: split from cache.h, add API docs, stop leaking memoryÆvar Arnfjörð Bjarmason, Feb 2, 2022
  93. 1/5 cache.h: remove always unused show_date_human() declarationÆvar Arnfjörð Bjarmason, Feb 2, 2022
  94. 2/5 date API: create a date.h, split from cache.hÆvar Arnfjörð Bjarmason, Feb 2, 2022
  95. Ævar Arnfjörð BjarmasonFeb 2, 2022
  96. Junio C HamanoFeb 15, 2022
  97. 3/5 date API: provide and use a DATE_MODE_INITÆvar Arnfjörð Bjarmason, Feb 2, 2022
  98. 4/5 date API: add basic API docsÆvar Arnfjörð Bjarmason, Feb 2, 2022
  99. Junio C HamanoFeb 15, 2022
  100. 5/5 date API: add and use a date_mode_release()Ævar Arnfjörð Bjarmason, Feb 2, 2022
  101. Junio C HamanoFeb 15, 2022
  102. 0/5 date.[ch] API: split from cache.h, add API docs, stop leaking memoryÆvar Arnfjörð Bjarmason, Feb 4, 2022
  103. 1/5 cache.h: remove always unused show_date_human() declarationÆvar Arnfjörð Bjarmason, Feb 4, 2022
  104. 2/5 date API: create a date.h, split from cache.hÆvar Arnfjörð Bjarmason, Feb 4, 2022
  105. 3/5 date API: provide and use a DATE_MODE_INITÆvar Arnfjörð Bjarmason, Feb 4, 2022
  106. 4/5 date API: add basic API docsÆvar Arnfjörð Bjarmason, Feb 4, 2022
  107. 5/5 date API: add and use a date_mode_release()Ævar Arnfjörð Bjarmason, Feb 4, 2022
  108. Ævar Arnfjörð BjarmasonFeb 14, 2022
  109. Junio C HamanoFeb 14, 2022
  110. 0/5 date.[ch] API: split from cache.h, add API docs, stop leaking memoryÆvar Arnfjörð Bjarmason, Feb 16, 2022
  111. 1/5 cache.h: remove always unused show_date_human() declarationÆvar Arnfjörð Bjarmason, Feb 16, 2022
  112. 2/5 date API: create a date.h, split from cache.hÆvar Arnfjörð Bjarmason, Feb 16, 2022
  113. 3/5 date API: provide and use a DATE_MODE_INITÆvar Arnfjörð Bjarmason, Feb 16, 2022
  114. 4/5 date API: add basic API docsÆvar Arnfjörð Bjarmason, Feb 16, 2022
  115. 5/5 date API: add and use a date_mode_release()Ævar Arnfjörð Bjarmason, Feb 16, 2022
  116. Junio C HamanoFeb 16, 2022
  117. 1/8 Makefile: add SANITIZE=leak flag to GIT-BUILD-OPTIONSÆvar Arnfjörð Bjarmason, Aug 31, 2021
  118. 2/8 CI: refactor "if" to "case" statementÆvar Arnfjörð Bjarmason, Aug 31, 2021
  119. 3/8 tests: add a test mode for SANITIZE=leak, run it in CIÆvar Arnfjörð Bjarmason, Aug 31, 2021
  120. 4/8 tests: annotate t000*.sh with TEST_PASSES_SANITIZE_LEAK=trueÆvar Arnfjörð Bjarmason, Aug 31, 2021
  121. 5/8 tests: annotate t001*.sh with TEST_PASSES_SANITIZE_LEAK=trueÆvar Arnfjörð Bjarmason, Aug 31, 2021
  122. 6/8 tests: annotate t002*.sh with TEST_PASSES_SANITIZE_LEAK=trueÆvar Arnfjörð Bjarmason, Aug 31, 2021
  123. 7/8 tests: annotate select t0*.sh with TEST_PASSES_SANITIZE_LEAK=trueÆvar Arnfjörð Bjarmason, Aug 31, 2021
  124. 8/8 tests: annotate select t*.sh with TEST_PASSES_SANITIZE_LEAK=trueÆvar Arnfjörð Bjarmason, Aug 31, 2021
  125. Ævar Arnfjörð BjarmasonAug 31, 2021

Read the whole thread, see it on lore, or plain text.

$ cat FOOTERMessages come from the public archive at lore.kernel.org/git, fetched every hour. The front page is chosen and written each morning by an AI editor and can be wrong; the threads themselves are the record. About and API. For agents: an MCP server at https://gitlist.dev/mcp, and any thread, story or person page as Markdown by adding .md to its URL (or sending Accept: text/markdown). Details in /llms.txt.