Git development
 help / color / mirror / Atom feed
From: Junio C Hamano <gitster@pobox.com>
To: git@vger.kernel.org
Subject: [PATCH v2] remote: plug memory leaks
Date: Sat, 25 Jul 2026 09:03:24 -0700	[thread overview]
Message-ID: <xmqqpl0b12gj.fsf@gitster.g> (raw)
In-Reply-To: <xmqqv7a33nm9.fsf@gitster.g> (Junio C. Hamano's message of "Fri, 24 Jul 2026 17:43:26 -0700")

The in-core data structure used to keep track of
'url.<real>.{insteadOf,pushInsteadOf} = <alias>' settings is not
properly cleaned up when the process is done with it.

'struct rewrites' is embedded in 'remote_state' and serves as the
top level of the rewrite data.  This holds an array of a variable
number of pointers to 'struct rewrite' allocated individually on the
heap.  Each 'struct rewrite' holds a '.base' string and an array of
'struct counted_string' called '.instead_of', which is allocated
contiguously on the heap.  Each 'struct counted_string' has a
pointer to a string allocated on the heap.

Amid these pointers, rewrites_release() fails to free everything
other than 'struct rewrite''s '.base' member and the 'struct rewrite'
instances themselves.

Fix rewrites_release() to also free the contiguous array storing
'.instead_of', the string pointers within each '.instead_of' element,
and each 'struct rewrite' instance individually allocated on the heap.

Signed-off-by: Junio C Hamano <gitster@pobox.com>
---

* The initial iteration relied on the assumption that strings
  borrowed from the configset subsystem will not go away, attempting
  to plug the leak of 'instead_of[n].s' pointers without making
  copies.  However, it turns out that all existing users other than
  a select few make copies and do not rely on that assumption.  In
  this version, I decided to simply follow suit, which might be
  slightly inefficient but is vastly safer.
---
 remote.c | 11 +++++++++--
 1 file changed, 9 insertions(+), 2 deletions(-)

diff --git a/remote.c b/remote.c
index a664cd166a..6c84adb36a 100644
--- a/remote.c
+++ b/remote.c
@@ -304,8 +304,15 @@ static struct rewrite *make_rewrite(struct rewrites *r,
 
 static void rewrites_release(struct rewrites *r)
 {
-	for (int i = 0; i < r->rewrite_nr; i++)
-		free((char *)r->rewrite[i]->base);
+	for (int i = 0; i < r->rewrite_nr; i++) {
+		struct rewrite *rewrite = r->rewrite[i];
+
+		free((char *)rewrite->base);
+		for (int j = 0; j < rewrite->instead_of_nr; j++)
+			free((char *)rewrite->instead_of[j].s);
+		free(rewrite->instead_of);
+		free(rewrite);
+	}
 	free(r->rewrite);
 	memset(r, 0, sizeof(*r));
 }

Range-diff:
1:  19a305bd22 ! 1:  3bd8668117 remote: plug memory leaks
    @@ Commit message
         'url.<real>.{insteadOf,pushInsteadOf} = <alias>' settings is not
         properly cleaned up when the process is done with it.
     
    -    Fix the rewrites_release() function to free not just the 'struct
    -    rewrites' instance itself, but also allocated structures that are
    -    pointed at by the 'struct rewrites' instance.  One of the embedded
    -    structures holds a 'const char *' to point at a borrowed constant
    -    string from a configuration callback.  Since the code does not
    -    modify this string, stop copying the value (alias URL) before
    -    registering it in 'struct rewrite', as nobody is freeing this
    -    member, to avoid leaking the extra copy.
    +    'struct rewrites' is embedded in 'remote_state' and serves as the
    +    top level of the rewrite data.  This holds an array of a variable
    +    number of pointers to 'struct rewrite' allocated individually on the
    +    heap.  Each 'struct rewrite' holds a '.base' string and an array of
    +    'struct counted_string' called '.instead_of', which is allocated
    +    contiguously on the heap.  Each 'struct counted_string' has a
    +    pointer to a string allocated on the heap.
    +
    +    Amid these pointers, rewrites_release() fails to free everything
    +    other than 'struct rewrite''s '.base' member and the 'struct rewrite'
    +    instances themselves.
    +
    +    Fix rewrites_release() to also free the contiguous array storing
    +    '.instead_of', the string pointers within each '.instead_of' element,
    +    and each 'struct rewrite' instance individually allocated on the heap.
     
         Signed-off-by: Junio C Hamano <gitster@pobox.com>
    +    ---
    +
    +    * The initial iteration relied on the assumption that strings
    +      borrowed from the configset subsystem will not go away, attempting
    +      to plug the leak of 'instead_of[n].s' pointers without making
    +      copies.  However, it turns out that all existing users other than
    +      a select few make copies and do not rely on that assumption.  In
    +      this version, I decided to simply follow suit, which might be
    +      slightly inefficient but is vastly safer.
     
      ## remote.c ##
     @@ remote.c: static struct rewrite *make_rewrite(struct rewrites *r,
    @@ remote.c: static struct rewrite *make_rewrite(struct rewrites *r,
      static void rewrites_release(struct rewrites *r)
      {
     -	for (int i = 0; i < r->rewrite_nr; i++)
    +-		free((char *)r->rewrite[i]->base);
     +	for (int i = 0; i < r->rewrite_nr; i++) {
    - 		free((char *)r->rewrite[i]->base);
    -+		free(r->rewrite[i]->instead_of);
    -+		free(r->rewrite[i]);
    ++		struct rewrite *rewrite = r->rewrite[i];
    ++
    ++		free((char *)rewrite->base);
    ++		for (int j = 0; j < rewrite->instead_of_nr; j++)
    ++			free((char *)rewrite->instead_of[j].s);
    ++		free(rewrite->instead_of);
    ++		free(rewrite);
     +	}
      	free(r->rewrite);
      	memset(r, 0, sizeof(*r));
      }
    -@@ remote.c: static int handle_config(const char *key, const char *value,
    - 				return config_error_nonbool(key);
    - 			rewrite = make_rewrite(&remote_state->rewrites, name,
    - 					       namelen);
    --			add_instead_of(rewrite, xstrdup(value));
    -+			add_instead_of(rewrite, value);
    - 		} else if (!strcmp(subkey, "pushinsteadof")) {
    - 			if (!value)
    - 				return config_error_nonbool(key);
    - 			rewrite = make_rewrite(&remote_state->rewrites_push,
    - 					       name, namelen);
    --			add_instead_of(rewrite, xstrdup(value));
    -+			add_instead_of(rewrite, value);
    - 		}
    - 	}
    - 
-- 
2.55.0-570-g266ec51bf1


  parent reply	other threads:[~2026-07-25 16:03 UTC|newest]

Thread overview: 5+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-07-25  0:43 [PATCH] remote: plug memory leaks Junio C Hamano
2026-07-25  1:57 ` Junio C Hamano
2026-07-25 16:03 ` Junio C Hamano [this message]
2026-07-25 16:18   ` [PATCH v2] " Jeff King
2026-07-25 17:06     ` Junio C Hamano

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=xmqqpl0b12gj.fsf@gitster.g \
    --to=gitster@pobox.com \
    --cc=git@vger.kernel.org \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox