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

Re: [PATCH 11/12] builtin/rebase: fix options.strategy memory lifecycle

From
PWPhillip Wood <phillip.wood123@gmail.com>
Date
Jun 22, 2021, 09:02 UTC
Message-ID
<d1ef45c1-067e-abde-62a2-1df2c12ba3a3@gmail.com>
In-Reply-To
<CABPp-BEQkUQLt-ZbwdO+ecd2rumttBUKUmh3=7LaKRxwXCkB+g@mail.gmail.com>
Hi Elijah
On 21/06/2021 22:39, Elijah Newren wrote:
Show 64 quoted lines
> On Sun, Jun 20, 2021 at 11:29 AM Phillip Wood <phillip.wood123@gmail.com> wrote:
>>
>> Hi Andrzej
>>
>> Thanks for working on removing memory leaks from git.
>>
>> On 20/06/2021 16:12, andrzej@ahunt.org wrote:
>>> From: Andrzej Hunt <ajrhunt@google.com>
>>>
>>> This change:
>>> - xstrdup()'s all string being used for replace_opts.strategy, to
>>
>> I think you mean replay_opts rather than replace_opts.
>>
>>>     guarantee that replace_opts owns these strings. This is needed because
>>>     sequencer_remove_state() will free replace_opts.strategy, and it's
>>>     usually called as part of the usage of replace_opts.
>>> - Removes xstrdup()'s being used to populate options.strategy in
>>>     cmd_rebase(), which avoids leaking options.strategy, even in the
>>>     case where strategy is never moved/copied into replace_opts.
>>
>>
>>> These changes are needed because:
>>> - We would always create a new string for options.strategy if we either
>>>     get a strategy via options (OPT_STRING(...strategy...), or via
>>>     GIT_TEST_MERGE_ALGORITHM.
>>> - But only sometimes is this string copied into replace_opts - in which
>>>     case it did get free()'d in sequencer_remove_state().
>>> - The rest of the time, the newly allocated string would remain unused,
>>>     causing a leak. But we can't just add a free because that can result
>>>     in a double-free in those cases where replace_opts was populated.
>>>
>>> An alternative approach would be to set options.strategy to NULL when
>>> moving the pointer to replace_opts.strategy, combined with always
>>> free()'ing options.strategy, but that seems like a more
>>> complicated and wasteful approach.
>>
>> read_basic_state() contains
>>          if (file_exists(state_dir_path("strategy", opts))) {
>>                  strbuf_reset(&buf);
>>                  if (!read_oneliner(&buf, state_dir_path("strategy", opts),
>>                                     READ_ONELINER_WARN_MISSING))
>>                          return -1;
>>                  free(opts->strategy);
>>                  opts->strategy = xstrdup(buf.buf);
>>          }
>>
>> So we do try to free opts->strategy when reading the state from disc and
>> we allocate a new string. I suspect that opts->strategy is actually NULL
>> in when this function is called but I haven't checked. Given that we are
>> allocating a copy above I think maybe your alternative approach of
>> always freeing opts->strategy would be better.
> 
> Good catches.  sequencer_remove_state() in sequencer.c also has a
> free(opts->strategy) call.
> 
> To make things even more muddy, we have code like
>      replay.strategy = replay.default_strategy;
> or
>      opts->strategy = opts->default_strategy;
> which both will probably work really poorly with the calls to
>      free(opts->default_strategy);
>      free(opts->strategy);
> from sequencer_remove_state().  I suspect we've got a few bugs here...

It's not immediately obvious but I think those are actually safe. opts->default_strategy is allocated by sequencer_init_config() so it is correct to free it and when we assign it in rebase.c we do

	else if (!replay.strategy && replay.default_strategy) {
		replay.strategy = replay.default_strategy;
		replay.default_strategy = NULL;
	}

so there is no double free. There is similar code in builtin/revert.c which I think is where your other example came from. I think there is a leak in builtin/revert.c though

	if (!opts->strategy && opts->default_strategy) {
		opts->strategy = opts->default_strategy;
		opts->default_strategy = NULL;
	}
	/* do some other stuff */
	/* These option values will be free()d */
	opts->gpg_sign = xstrdup_or_null(opts->gpg_sign);
	opts->strategy = xstrdup_or_null(opts->strategy);

So we copy the default strategy, leaking the original copy from sequencer_init_options() if --strategy isn't given on the command line. I think it would be simple to fix this by making the copy earlier.

	if (!opts->strategy && opts->default_strategy) {
		opts->strategy = opts->default_strategy;
		opts->default_strategy = NULL;
	} else if (opts->strategy) {
	/* This option will be free()d in sequencer_remove_state() */
		opts->strategy = xstrdup(opts->strategy);
	}

I'm going offline for a week or so in a couple of days but I'll have look at making a proper patch when I get back.

Best Wishes
Phillip
Previous: Elijah NewrenNext: Andrzej Hunt
Message 24 of 51 in “Fix all leaks in tests t0002-t0099: Part 2”
  1. 00/12 Fix all leaks in tests t0002-t0099: Part 2andrzej@ahunt.org, Jun 20, 2021
  2. 01/12 fmt-merge-msg: free newly allocated temporary strings when doneandrzej@ahunt.org, Jun 20, 2021
  3. Elijah NewrenJun 21, 2021
  4. 02/12 environment: move strbuf into block to plug leakandrzej@ahunt.org, Jun 20, 2021
  5. Elijah NewrenJun 21, 2021
  6. René ScharfeJun 26, 2021
  7. 03/12 builtin/submodule--helper: release unused strbuf to avoid leakandrzej@ahunt.org, Jun 20, 2021
  8. 04/12 builtin/for-each-repo: remove unnecessary argv copy to plug leakandrzej@ahunt.org, Jun 20, 2021
  9. Elijah NewrenJun 21, 2021
  10. 05/12 diffcore-rename: move old_dir/new_dir definition to plug leakandrzej@ahunt.org, Jun 20, 2021
  11. Elijah NewrenJun 21, 2021
  12. 06/12 ref-filter: also free head for ATOM_HEAD to avoid leakandrzej@ahunt.org, Jun 20, 2021
  13. Elijah NewrenJun 21, 2021
  14. 07/12 read-cache: call diff_setup_done to avoid leakandrzej@ahunt.org, Jun 20, 2021
  15. Elijah NewrenJun 21, 2021
  16. 08/12 convert: release strbuf to avoid leakandrzej@ahunt.org, Jun 20, 2021
  17. Elijah NewrenJun 21, 2021
  18. 09/12 builtin/mv: free or UNLEAK multiple pointers at end of cmd_mvandrzej@ahunt.org, Jun 20, 2021
  19. 10/12 builtin/merge: free found_ref when doneandrzej@ahunt.org, Jun 20, 2021
  20. Elijah NewrenJun 21, 2021
  21. 11/12 builtin/rebase: fix options.strategy memory lifecycleandrzej@ahunt.org, Jun 20, 2021
  22. Phillip WoodJun 20, 2021
  23. Elijah NewrenJun 21, 2021
  24. Phillip WoodJun 22, 2021
  25. Andrzej HuntJul 25, 2021
  26. Phillip WoodJul 27, 2021
  27. 12/12 reset: clear_unpack_trees_porcelain to plug leakandrzej@ahunt.org, Jun 20, 2021
  28. Elijah NewrenJun 21, 2021
  29. Elijah NewrenJun 21, 2021
  30. Andrzej HuntJul 25, 2021
  31. Christian CouderJul 26, 2021
  32. 00/12 Fix all leaks in tests t0002-t0099: Part 2andrzej@ahunt.org, Jul 25, 2021
  33. 01/12 fmt-merge-msg: free newly allocated temporary strings when doneandrzej@ahunt.org, Jul 25, 2021
  34. Junio C HamanoJul 26, 2021
  35. 02/12 environment: move strbuf into block to plug leakandrzej@ahunt.org, Jul 25, 2021
  36. 03/12 builtin/submodule--helper: release unused strbuf to avoid leakandrzej@ahunt.org, Jul 25, 2021
  37. 04/12 builtin/for-each-repo: remove unnecessary argv copy to plug leakandrzej@ahunt.org, Jul 25, 2021
  38. Junio C HamanoJul 26, 2021
  39. 06/12 ref-filter: also free head for ATOM_HEAD to avoid leakandrzej@ahunt.org, Jul 25, 2021
  40. Junio C HamanoJul 26, 2021
  41. 07/12 read-cache: call diff_setup_done to avoid leakandrzej@ahunt.org, Jul 25, 2021
  42. Junio C HamanoJul 26, 2021
  43. 05/12 diffcore-rename: move old_dir/new_dir definition to plug leakandrzej@ahunt.org, Jul 25, 2021
  44. Junio C HamanoJul 26, 2021
  45. 09/12 builtin/mv: free or UNLEAK multiple pointers at end of cmd_mvandrzej@ahunt.org, Jul 25, 2021
  46. 08/12 convert: release strbuf to avoid leakandrzej@ahunt.org, Jul 25, 2021
  47. Junio C HamanoJul 26, 2021
  48. 10/12 builtin/merge: free found_ref when doneandrzej@ahunt.org, Jul 25, 2021
  49. 12/12 reset: clear_unpack_trees_porcelain to plug leakandrzej@ahunt.org, Jul 25, 2021
  50. 11/12 builtin/rebase: fix options.strategy memory lifecycleandrzej@ahunt.org, Jul 25, 2021
  51. Junio C HamanoJul 26, 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.