diff mbox series

[09/22] builtin/rebase: fix leaking `commit.gpgsign` value

Message ID 05290fc1f14cae8229c42f2d0aafe6619c069e3a.1722933642.git.ps@pks.im (mailing list archive)
State Superseded
Headers show
Series Memory leak fixes (pt.4) | expand

Commit Message

Patrick Steinhardt Aug. 6, 2024, 9 a.m. UTC
In `get_replay_opts()`, we unconditionally override the `gpg_sign` field
that already got populated by `sequencer_init_config()` in case the user
has "commit.gpgsign" set in their config. It is kind of dubious whether
this is the correct thing to do or a bug. What is clear though is that
this creates a memory leak.

Let's mark this assignment with a TODO comment to figure out whether
this needs to be fixed or not. Meanwhile though, let's plug the memory
leak.

Signed-off-by: Patrick Steinhardt <ps@pks.im>
---
 builtin/rebase.c              | 8 ++++++++
 sequencer.c                   | 1 +
 t/t3404-rebase-interactive.sh | 1 +
 t/t3435-rebase-gpg-sign.sh    | 1 +
 t/t7030-verify-tag.sh         | 1 +
 5 files changed, 12 insertions(+)

Comments

James Liu Aug. 7, 2024, 7:32 a.m. UTC | #1
On Tue Aug 6, 2024 at 7:00 PM AEST, Patrick Steinhardt wrote:
> In `get_replay_opts()`, we unconditionally override the `gpg_sign` field
> that already got populated by `sequencer_init_config()` in case the user
> has "commit.gpgsign" set in their config. It is kind of dubious whether
> this is the correct thing to do or a bug. What is clear though is that
> this creates a memory leak.
>
> Let's mark this assignment with a TODO comment to figure out whether
> this needs to be fixed or not. Meanwhile though, let's plug the memory
> leak.
>
> Signed-off-by: Patrick Steinhardt <ps@pks.im>
> ---
>  builtin/rebase.c              | 8 ++++++++
>  sequencer.c                   | 1 +
>  t/t3404-rebase-interactive.sh | 1 +
>  t/t3435-rebase-gpg-sign.sh    | 1 +
>  t/t7030-verify-tag.sh         | 1 +
>  5 files changed, 12 insertions(+)
>
> diff --git a/builtin/rebase.c b/builtin/rebase.c
> index e3a8e74cfc..f65316a023 100644
> --- a/builtin/rebase.c
> +++ b/builtin/rebase.c
> @@ -186,7 +186,15 @@ static struct replay_opts get_replay_opts(const struct rebase_options *opts)
>  	replay.committer_date_is_author_date =
>  					opts->committer_date_is_author_date;
>  	replay.ignore_date = opts->ignore_date;
> +
> +	/*
> +	 * TODO: Is it really intentional that we unconditionally override
> +	 * `replay.gpg_sign` even if it has already been initialized via the
> +	 * configuration?
> +	 */
> +	free(replay.gpg_sign);
>  	replay.gpg_sign = xstrdup_or_null(opts->gpg_sign_opt);
> +
>  	replay.reflog_action = xstrdup(opts->reflog_action);
>  	if (opts->strategy)
>  		replay.strategy = xstrdup_or_null(opts->strategy);
> diff --git a/sequencer.c b/sequencer.c
> index 0291920f0b..cade9b0ca8 100644
> --- a/sequencer.c
> +++ b/sequencer.c
> @@ -303,6 +303,7 @@ static int git_sequencer_config(const char *k, const char *v,
>  	}
>  
>  	if (!strcmp(k, "commit.gpgsign")) {
> +		free(opts->gpg_sign);
>  		opts->gpg_sign = git_config_bool(k, v) ? xstrdup("") : NULL;
>  		return 0;
>  	}

It looks like this free'ing would be managed by the caller by invoking
`replay_opts_release()`, but it's not being done consistently.

For example, `do_interactive_rebase()` invokes `replay_opts_release()`,
but `run_sequencer_rebase()` does not. Would it be better to address the
leak here?
Patrick Steinhardt Aug. 8, 2024, 5:05 a.m. UTC | #2
On Wed, Aug 07, 2024 at 05:32:25PM +1000, James Liu wrote:
> On Tue Aug 6, 2024 at 7:00 PM AEST, Patrick Steinhardt wrote:
> > In `get_replay_opts()`, we unconditionally override the `gpg_sign` field
> > that already got populated by `sequencer_init_config()` in case the user
> > has "commit.gpgsign" set in their config. It is kind of dubious whether
> > this is the correct thing to do or a bug. What is clear though is that
> > this creates a memory leak.
> >
> > Let's mark this assignment with a TODO comment to figure out whether
> > this needs to be fixed or not. Meanwhile though, let's plug the memory
> > leak.
> >
> > Signed-off-by: Patrick Steinhardt <ps@pks.im>
> > ---
> >  builtin/rebase.c              | 8 ++++++++
> >  sequencer.c                   | 1 +
> >  t/t3404-rebase-interactive.sh | 1 +
> >  t/t3435-rebase-gpg-sign.sh    | 1 +
> >  t/t7030-verify-tag.sh         | 1 +
> >  5 files changed, 12 insertions(+)
> >
> > diff --git a/builtin/rebase.c b/builtin/rebase.c
> > index e3a8e74cfc..f65316a023 100644
> > --- a/builtin/rebase.c
> > +++ b/builtin/rebase.c
> > @@ -186,7 +186,15 @@ static struct replay_opts get_replay_opts(const struct rebase_options *opts)
> >  	replay.committer_date_is_author_date =
> >  					opts->committer_date_is_author_date;
> >  	replay.ignore_date = opts->ignore_date;
> > +
> > +	/*
> > +	 * TODO: Is it really intentional that we unconditionally override
> > +	 * `replay.gpg_sign` even if it has already been initialized via the
> > +	 * configuration?
> > +	 */
> > +	free(replay.gpg_sign);
> >  	replay.gpg_sign = xstrdup_or_null(opts->gpg_sign_opt);
> > +
> >  	replay.reflog_action = xstrdup(opts->reflog_action);
> >  	if (opts->strategy)
> >  		replay.strategy = xstrdup_or_null(opts->strategy);
> > diff --git a/sequencer.c b/sequencer.c
> > index 0291920f0b..cade9b0ca8 100644
> > --- a/sequencer.c
> > +++ b/sequencer.c
> > @@ -303,6 +303,7 @@ static int git_sequencer_config(const char *k, const char *v,
> >  	}
> >  
> >  	if (!strcmp(k, "commit.gpgsign")) {
> > +		free(opts->gpg_sign);
> >  		opts->gpg_sign = git_config_bool(k, v) ? xstrdup("") : NULL;
> >  		return 0;
> >  	}
> 
> It looks like this free'ing would be managed by the caller by invoking
> `replay_opts_release()`, but it's not being done consistently.
> 
> For example, `do_interactive_rebase()` invokes `replay_opts_release()`,
> but `run_sequencer_rebase()` does not. Would it be better to address the
> leak here?

The problem here isn't that `replay_opts_release()` doesn't free the
values, or that the function isn't called consistently. The problem
rather is that we assign `opts->gpg_sign` even though it may already be
assigned an allocated string. Consequently, `replay_opts_release()`
doesn't even have a chance to free the old value because it cannot see
it anymore.

I'll massage the commit message a bit to clarify this.

Patrick
Phillip Wood Aug. 8, 2024, 10:07 a.m. UTC | #3
Hi Patrick

On 06/08/2024 10:00, Patrick Steinhardt wrote:
> @@ -186,7 +186,15 @@ static struct replay_opts get_replay_opts(const struct rebase_options *opts)
>   	replay.committer_date_is_author_date =
>   					opts->committer_date_is_author_date;
>   	replay.ignore_date = opts->ignore_date;
> +
> +	/*
> +	 * TODO: Is it really intentional that we unconditionally override
> +	 * `replay.gpg_sign` even if it has already been initialized via the
> +	 * configuration?
> +	 */
> +	free(replay.gpg_sign);
>   	replay.gpg_sign = xstrdup_or_null(opts->gpg_sign_opt);
> +

The code that handles "-S" could certainly be clearer. The value 
returned from the config is either "" or NULL, not a key name. In 
cmd_main() options.gpg_sign_opt is initialized by rebase_config(), we 
set gpg_sign to "" if options.gpg_sign_opt is non-NULL, free 
options.gpg_sign_opt and then copy gpg_sign back into 
options.gpg_sign_opt after parsing the command line so we're not losing 
anything by unconditionally copying it here. The code changes look good, 
though I'm not sure we need to add the blank lines. It's always nice to 
see more tests marked as leak-free especially a big file like t3404.

Best Wishes

Phillip
Patrick Steinhardt Aug. 8, 2024, 12:58 p.m. UTC | #4
On Thu, Aug 08, 2024 at 11:07:53AM +0100, Phillip Wood wrote:
> Hi Patrick
> 
> On 06/08/2024 10:00, Patrick Steinhardt wrote:
> > @@ -186,7 +186,15 @@ static struct replay_opts get_replay_opts(const struct rebase_options *opts)
> >   	replay.committer_date_is_author_date =
> >   					opts->committer_date_is_author_date;
> >   	replay.ignore_date = opts->ignore_date;
> > +
> > +	/*
> > +	 * TODO: Is it really intentional that we unconditionally override
> > +	 * `replay.gpg_sign` even if it has already been initialized via the
> > +	 * configuration?
> > +	 */
> > +	free(replay.gpg_sign);
> >   	replay.gpg_sign = xstrdup_or_null(opts->gpg_sign_opt);
> > +
> 
> The code that handles "-S" could certainly be clearer. The value returned
> from the config is either "" or NULL, not a key name. In cmd_main()
> options.gpg_sign_opt is initialized by rebase_config(), we set gpg_sign to
> "" if options.gpg_sign_opt is non-NULL, free options.gpg_sign_opt and then
> copy gpg_sign back into options.gpg_sign_opt after parsing the command line
> so we're not losing anything by unconditionally copying it here. The code
> changes look good, though I'm not sure we need to add the blank lines. It's
> always nice to see more tests marked as leak-free especially a big file like
> t3404.

Okay. In that case I'll just drop the comment. Thanks!

Patrick
diff mbox series

Patch

diff --git a/builtin/rebase.c b/builtin/rebase.c
index e3a8e74cfc..f65316a023 100644
--- a/builtin/rebase.c
+++ b/builtin/rebase.c
@@ -186,7 +186,15 @@  static struct replay_opts get_replay_opts(const struct rebase_options *opts)
 	replay.committer_date_is_author_date =
 					opts->committer_date_is_author_date;
 	replay.ignore_date = opts->ignore_date;
+
+	/*
+	 * TODO: Is it really intentional that we unconditionally override
+	 * `replay.gpg_sign` even if it has already been initialized via the
+	 * configuration?
+	 */
+	free(replay.gpg_sign);
 	replay.gpg_sign = xstrdup_or_null(opts->gpg_sign_opt);
+
 	replay.reflog_action = xstrdup(opts->reflog_action);
 	if (opts->strategy)
 		replay.strategy = xstrdup_or_null(opts->strategy);
diff --git a/sequencer.c b/sequencer.c
index 0291920f0b..cade9b0ca8 100644
--- a/sequencer.c
+++ b/sequencer.c
@@ -303,6 +303,7 @@  static int git_sequencer_config(const char *k, const char *v,
 	}
 
 	if (!strcmp(k, "commit.gpgsign")) {
+		free(opts->gpg_sign);
 		opts->gpg_sign = git_config_bool(k, v) ? xstrdup("") : NULL;
 		return 0;
 	}
diff --git a/t/t3404-rebase-interactive.sh b/t/t3404-rebase-interactive.sh
index f92baad138..f171af3061 100755
--- a/t/t3404-rebase-interactive.sh
+++ b/t/t3404-rebase-interactive.sh
@@ -26,6 +26,7 @@  Initial setup:
  touch file "conflict".
 '
 
+TEST_PASSES_SANITIZE_LEAK=true
 . ./test-lib.sh
 
 . "$TEST_DIRECTORY"/lib-rebase.sh
diff --git a/t/t3435-rebase-gpg-sign.sh b/t/t3435-rebase-gpg-sign.sh
index 6aa2aeb628..6e329fea7c 100755
--- a/t/t3435-rebase-gpg-sign.sh
+++ b/t/t3435-rebase-gpg-sign.sh
@@ -8,6 +8,7 @@  test_description='test rebase --[no-]gpg-sign'
 GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main
 export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME
 
+TEST_PASSES_SANITIZE_LEAK=true
 . ./test-lib.sh
 . "$TEST_DIRECTORY/lib-rebase.sh"
 . "$TEST_DIRECTORY/lib-gpg.sh"
diff --git a/t/t7030-verify-tag.sh b/t/t7030-verify-tag.sh
index 6f526c37c2..effa826744 100755
--- a/t/t7030-verify-tag.sh
+++ b/t/t7030-verify-tag.sh
@@ -4,6 +4,7 @@  test_description='signed tag tests'
 GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main
 export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME
 
+TEST_PASSES_SANITIZE_LEAK=true
 . ./test-lib.sh
 . "$TEST_DIRECTORY/lib-gpg.sh"