All of lore.kernel.org
 help / color / mirror / Atom feed
From: Patrick Steinhardt <ps@pks.im>
To: Jeff King <peff@peff.net>
Cc: Nicolas Le Cam <niko.lecam@gmail.com>, git@vger.kernel.org
Subject: Re: [PATCH] revision: hang on to "freed" argv elements
Date: Tue, 1 Sep 2026 10:51:26 +0200	[thread overview]
Message-ID: <apaSDqIEyc82Q_zE@pks.im> (raw)
In-Reply-To: <20260901062815.GC1075462@coredump.intra.peff.net>

On Tue, Sep 01, 2026 at 02:28:15AM -0400, Jeff King wrote:
> On Sun, Aug 30, 2026 at 11:55:55PM +0200, Nicolas Le Cam wrote:
> 
> > The prefixes are replaced by fragments of unrelated heap data, and the
> > value changes between runs of the same command:
> > 
> >     $ git stash show --src-prefix=a/ --dst-prefix=b/ | head -1
> >     diff --git Uf.txt Uf.txt
> >     $ git stash show --src-prefix=a/ --dst-prefix=b/ | head -1
> >     diff --git Vf.txt Vf.txt
> > 
> > On other versions the garbage is recognisable as pieces of other
> > strings live in the process -- "ributes" (from "attributes"),
> > "bjectmode" (from "objectmode"), "4c/" -- which is what suggests a
> > use-after-free rather than an off-by-one.
> 
> Thanks for a clear and thorough bug report! The cause is indeed the
> related to the commits you found. The explanation (and fix) are below.
> 
> -- >8 --
> Subject: revision: hang on to "freed" argv elements
> 
> In setup_revisions() we rewrite the incoming argv array, losing
> references to the strings it contains. For a synthetic argv array
> constructed from heap strings, that traditionally meant we leaked those
> allocated strings.
> 
> We fixed the leak in cd43948798 (revision: manage memory ownership of
> argv in setup_revisions(), 2025-09-19). Now callers can tell the
> revision code that argv entries are allocated and should be freed, which
> it will do before overwriting them.
> 
> But this introduced a new bug! The overwritten entries go away as soon
> as option parsing is finished, but a few options may actually create new
> references to those strings. And once we free the strings, those stale
> references become use-after-free bugs. For example, running:
> 
>   git stash show --src-prefix=foo/
> 
> demonstrates the problem:
> 
>   1. The stash command generates its own synthetic argv (because it has
>      to treat the stash specifiers specially) which it then passes to
>      setup_revisions().
> 
>   2. Parsing will create a reference to the partial string "foo/" in
>      revs.diffopt.a_prefix.
> 
>   3. When setup_revisions() finishes, we rewrite argv to throw away
>      parsed strings. This frees the entry holding "--src-prefix=foo",
>      at which point we have a dangling reference in revs.diffopt.
> 
>   4. We generate an actual diff, accessing garbage memory via
>      revs.diffopt.a_prefix. The output is usually garbled, but ASan also
>      detects this reliably.
> 
> One obvious fix here is to allocate new strings when we pull data out of
> the argv array. But doing so is error prone (every string option must
> remember to do it or risk a subtle bug), and creates more questions
> about memory ownership (e.g., some callers assign string literals
> directly to a_prefix, and we would not want to free those).
> 
> Instead we can fix this centrally by delaying the free() calls. We'll
> collect any "freed" strings in a new array, hold on to it for the life
> of the rev_info struct, and then release it at the end. We can easily
> use a strvec for this, since it handles growth and cleanup for us.
> 
> This fixes the prefix case above (which is now tested in t3903), and
> should fix any other stray cases. Though I could not find any; we use
> OPT_STRING only in the prefix diff options, and very few revision opts
> store strings. Those that do (like --format and --encoding) already make
> a copy of the string. They do not need for us to hold on to the memory
> longer, but it does not hurt them if we do.

So the fix could've been as trivial as you mention above, where we
simply perform a copy of the string for "--src-prefix", and everything
else works just fine?

In any case though, your approach is more defensive and makes it way
harder for such use-after-free bugs to be introduced going forward, so
I'm in line with the proposed patch.

> diff --git a/revision.c b/revision.c
> index 50dc8b1991..7aee96bd8e 100644
> --- a/revision.c
> +++ b/revision.c
> @@ -2307,9 +2307,27 @@ static timestamp_t parse_age(const char *arg)
>  	return num;
>  }
>  
> +/*
> + * When asked to free argv strings, we should not do so immediately. Some
> + * option parsing may have stored a reference to the string (either the whole
> + * thing, or a substring inside it). We should keep it valid until the rev_info
> + * struct itself is freed.
> + *
> + * Note that we take a const str for the convenience of callers (who have the
> + * usual const argv array, even when opt->free_removed_argv_elements is set).
> + * We cast away the const on their behalf.
> + */
> +static void mark_argv_for_free(struct rev_info *revs, const char *str)
> +{
> +	if (!str)
> +		return;
> +	strvec_push_nodup(&revs->argv_to_free, (char *)str);
> +}

Hm. Doesn't this mean that we take ownership of the string and then
eventually try to release it when releasing the vector? I wonder whether
this could introduce subtle lifetime issues where the caller passes a
non-heap-allocated string.

I don't think it's that bad when seeing where we use these. But I feel
like hiding this fact by marking the parameter as `const` is a bit of a
weird design choice. I'd much rather prefer we force this onto the
callers so that they are aware of this, but I haven't seen the end
result of that. So maybe it's just too ugly.

Thanks!

Patrick

  parent reply	other threads:[~2026-09-01  8:51 UTC|newest]

Thread overview: 7+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-08-30 21:55 [BUG] git stash show --src-prefix prints freed memory since 2.52.0 Nicolas Le Cam
2026-09-01  6:28 ` [PATCH] revision: hang on to "freed" argv elements Jeff King
2026-09-01  6:36   ` [PATCH 2/1] revision: simplify mark_argv_for_free() callers Jeff King
2026-09-01 18:02     ` Junio C Hamano
2026-09-01  8:51   ` Patrick Steinhardt [this message]
2026-09-01  9:21     ` [PATCH] revision: hang on to "freed" argv elements Jeff King
2026-09-01 11:08       ` Patrick Steinhardt

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=apaSDqIEyc82Q_zE@pks.im \
    --to=ps@pks.im \
    --cc=git@vger.kernel.org \
    --cc=niko.lecam@gmail.com \
    --cc=peff@peff.net \
    /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 an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.