From: Junio C Hamano <gitster@pobox.com>
To: "Kristoffer Haugsbakk" <kristofferhaugsbakk@fastmail.com>
Cc: "Lauri Niskanen" <ape@ape3000.com>,
git@vger.kernel.org, "Patrick Steinhardt" <ps@pks.im>
Subject: Re: [BUG] git stash show -p with invalid option aborts with double-free in show_stash() (strvec_clear)
Date: Fri, 19 Sep 2025 09:58:25 -0700 [thread overview]
Message-ID: <xmqqldma5qha.fsf@gitster.g> (raw)
In-Reply-To: <xmqq4isy77qr.fsf@gitster.g> (Junio C. Hamano's message of "Fri, 19 Sep 2025 09:00:12 -0700")
Junio C Hamano <gitster@pobox.com> writes:
> So there are two ways to fix this.
>
> The easier, more performant, and closer to the original design
> around the revisions API is to do this:
>
> diff --git c/builtin/stash.c w/builtin/stash.c
> index f5ddee5c7f..b6312b1b70 100644
> --- c/builtin/stash.c
> +++ w/builtin/stash.c
> @@ -1016,6 +1016,8 @@ static int show_stash(int argc, const char **argv, const char *prefix,
> }
>
> argc = setup_revisions(revision_args.nr, revision_args.v, &rev, NULL);
> + for (i = argc; i < revision_args.nr; i++)
> + revision_args.v[i] = NULL;
> if (argc > 1)
> goto usage;
> if (!rev.diffopt.output_format) {
>
> A less performant but may in the longer term safer alternative is to
> change the caller-callee contract around setup_revisions() so that
> the later "unused" slots in the argv array is NULLed before
> returning to the caller, i.e. instead of leaving
>
> .v = { "show", "--no-such-option", "--no-such-option", NULL }
>
> in the revision_args.v[] array, teach setup_revisions() to leave
>
> .v = { "show", "--no-such-option", NULL, NULL }
>
> there (again, we cannot do anything about .nr that is only available
> to the caller).
For completeness, here is how the other approach may look like, but
I have made my share of off-by-one mistakes all over the place over
the ears, so somebody else needs to lend an eyeball and check it for
sanity.
----- >8 -----
Subject: revisions: do not leave duplicated cruft from setup_revisions()
The API contract between setup_revisions() and its callers is that
the function gets <argc, argv[]> parameters, parses what it can
recognise, and leaves the remainder in argv[] (shifting them to the
head, and return the number of arguments left. The caller then is
supposed to look at only the early part of argv[] it passed to the
function, up to the argc returned by the function.
Because by contract the caller is not supposed to touch the
remainder of the original argv[], the function left them intact. If
the incoming parameter were
argc = 3, argv[] = { "A", "-p", "--unknown", NULL }
and only "-p" is what the function understands, the function would
return
argc = 2, argv[] = { "A", "--unknown", <unspecified>, <unspecified> }
to the caller, and it is fine as long as the caller would not look
beyond what it is allowed to see. In practice, these <unspecified>
slots has the original pointer given by the caller, which meant that
the same instance of string "--unknown" appears at argv[1] as it
got copied from argv[2], and argv[2] is not cleared.
Recently however this bit us when the <argc, argv[]> pair is
prepared in a strvec instance. When trying to clear the strvec
that was passed to setup_revisions(), because some later array
elements are duplicates of earlier elements, strvec_clear() ended up
freeing the same string twice X-<.
We could fix individual callers (in this particular case, letting
strvec_clear() touch the original array .v[] beyond the updated argc
returned by setup_revisions() is a bug in the caller), but I see no
good reason to return argv[] array after modifying it without
cleaning up the later slots (other than the fact that the callee
saves a few cycles because it does not have to bother cleaning array
slots that no caller is supposed to touch, that is).
Teach the function to assign NULLs to the slot the caller is not
supposed to see to help avoiding this class of bugs in the future.
Signed-off-by: Junio C Hamano <gitster@pobox.com>
---
revision.c | 6 ++++++
t/t3903-stash.sh | 5 +++++
2 files changed, 11 insertions(+)
diff --git c/revision.c w/revision.c
index 6ba8f67054..93948e7dc6 100644
--- c/revision.c
+++ w/revision.c
@@ -3174,6 +3174,12 @@ int setup_revisions(int argc, const char **argv, struct rev_info *revs, struct s
revs->show_notes_given = 1;
}
+ /*
+ * NULL out the leftover args we did not understand, which has
+ * shallow copies in earlier slots in the array.
+ */
+ while (left < argc--)
+ argv[argc] = NULL;
return left;
}
diff --git c/t/t3903-stash.sh w/t/t3903-stash.sh
index 0bb4648e36..dd70deb3b3 100755
--- c/t/t3903-stash.sh
+++ w/t/t3903-stash.sh
@@ -69,6 +69,11 @@ test_expect_success 'stash some dirty working directory' '
setup_stash
'
+test_expect_success 'controlled error return on unrecognized option' '
+ test_expect_code 129 git stash show -p --no-such 2>usage &&
+ grep -e "^usage: git stash show" usage
+'
+
cat >expect <<EOF
diff --git a/file b/file
index 0cfbf08..00750ed 100644
next prev parent reply other threads:[~2025-09-19 16:58 UTC|newest]
Thread overview: 26+ messages / expand[flat|nested] mbox.gz Atom feed top
2025-09-19 10:18 [BUG] git stash show -p with invalid option aborts with double-free in show_stash() (strvec_clear) Lauri Niskanen
2025-09-19 13:11 ` Kristoffer Haugsbakk
2025-09-19 16:00 ` Junio C Hamano
2025-09-19 16:48 ` Jeff King
2025-09-19 17:13 ` Junio C Hamano
2025-09-19 16:58 ` Junio C Hamano [this message]
2025-09-19 17:20 ` Jeff King
2025-09-19 18:15 ` Junio C Hamano
2025-09-19 19:56 ` Jeff King
2025-09-19 22:33 ` [PATCH 0/6] fixing double-frees and leaks via setup_revisions() Jeff King
2025-09-19 22:40 ` [PATCH 1/6] stash: tell setup_revisions() to free our allocated strings Jeff King
2025-09-22 15:45 ` Junio C Hamano
2025-09-22 19:05 ` Jeff King
2025-09-22 19:36 ` Junio C Hamano
2025-09-22 20:25 ` Jeff King
2025-09-22 21:26 ` Junio C Hamano
2025-09-23 0:48 ` Jeff King
2025-09-19 22:45 ` [PATCH 2/6] revision: manage memory ownership of argv in setup_revisions() Jeff King
2025-09-19 22:48 ` [PATCH 3/6] revision: add wrapper to setup_revisions() from a strvec Jeff King
2025-09-20 5:10 ` Eric Sunshine
2025-09-20 5:48 ` Jeff King
2025-09-19 22:49 ` [PATCH 4/6] treewide: use setup_revisions_from_strvec() when we have " Jeff King
2025-09-19 22:50 ` [PATCH 5/6] treewide: pass strvecs around for setup_revisions_from_strvec() Jeff King
2025-09-19 23:11 ` Jeff King
2025-09-19 22:51 ` [PATCH 6/6] revision: retain argv NULL invariant in setup_revisions() Jeff King
2025-09-19 23:07 ` Jeff King
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=xmqqldma5qha.fsf@gitster.g \
--to=gitster@pobox.com \
--cc=ape@ape3000.com \
--cc=git@vger.kernel.org \
--cc=kristofferhaugsbakk@fastmail.com \
--cc=ps@pks.im \
/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;
as well as URLs for NNTP newsgroup(s).