Re: [PATCH] diff: fix a segfault in >2 tree -I<regex> and --output=<file>
- From
Junio C Hamano <gitster@pobox.com>
- Date
- May 23, 2022, 20:08 UTC
- Message-ID
- <xmqqleusqaff.fsf@gitster.g>
- In-Reply-To
- <patch-1.1-f7fd645468c-20220523T182954Z-avarab@gmail.com>
Ævar Arnfjörð Bjarmason <avarab@gmail.com> writes:
Show 8 quoted lines
> Fix a regression in c45dc9cf30 (diff: plug memory leak from regcomp()
> on {log,diff} -I, 2021-02-11), as noted in [1] there was a logic error
> where we'd free the regex too soon.
>
> Now we'll ensure that diff_free() can be called repeatedly
> instead. We'd ultimately like to do away with the "no_free" confusion
> surrounding it, and to attempt to free() things only once, as outlined
> in [2]. But in the meantime this will fix the segfault.Hmph, repeated calls to diff_free_file() now closes the file upon the first call. I would have expected that such a resource would be released when all the references go away, i.e. upon the last call. The same thing for the ignore-regex array.
Clearing the "options->close_file" bit, and using FREE_AND_NULL(), would hide a breakage that could be caused by this change, doesn't it, because any use-after-release will say "ah, no need to close the file" and "oh, there is no regex". The former is not so worrisome, but the latter may be---we may no longer have regex because the first call to diff_free_ignore_regex() has cleared it and the code that wants to use the regex, if exists, would happily say "oh, there is no regex", instead of exposing the use-after-release breakage to a segfault.
> Thus we're here testing that -I<regex> is ignored in this case, and > likewise for --output=<file>, but since this is what we were doing > before c45dc9cf30 let's accept it for now.
It is true that the result of applying this patch is equivalent to c45dc9cf (diff: plug memory leak from regcomp() on {log,diff} -I, 2021-02-11), but doesn't that merely point at the commit as the source of behaviour breakage? With ignore-regex leaking before that commit, wouldn't we have been using ignore-regex with combined diff machinery?
Sorry, but I am failing to convince myself that this is not sweep the issue under the rug.
Show 61 quoted lines
> 1. https://lore.kernel.org/git/a6a14213-bc82-d6fb-43dd-5a423c40a4f8@web.de/
> 2. https://lore.kernel.org/git/220520.86pmk81a9z.gmgdl@evledraar.gmail.com/
>
> Reported-by: René Scharfe <l.s.r@web.de>
> Signed-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>
> ---
>
> On Sat, May 14 2022, René Scharfe wrote:
>
>> Hi all,
>>
>> git diff segfaults when it's asked to produce a combined diff and ignore
>> certain lines with --ignore-matching-lines/-I, e.g.:
>>
>> $ git diff -I DEF_VER v2.33.3 v2.33.3^@
>> zsh: segmentation fault ./git-diff -I DEF_VER v2.33.3 v2.33.3^@
>
> diff.c | 9 ++++++---
> t/t4013-diff-various.sh | 15 +++++++++++++++
> 2 files changed, 21 insertions(+), 3 deletions(-)
>
> diff --git a/diff.c b/diff.c
> index e71cf758861..183c9f21305 100644
> --- a/diff.c
> +++ b/diff.c
> @@ -6432,8 +6432,10 @@ static void diff_flush_patch_all_file_pairs(struct diff_options *o)
>
> static void diff_free_file(struct diff_options *options)
> {
> - if (options->close_file)
> - fclose(options->file);
> + if (!options->close_file)
> + return;
> + options->close_file = 0;
> + fclose(options->file);
> }
>
> static void diff_free_ignore_regex(struct diff_options *options)
> @@ -6444,7 +6446,8 @@ static void diff_free_ignore_regex(struct diff_options *options)
> regfree(options->ignore_regex[i]);
> free(options->ignore_regex[i]);
> }
> - free(options->ignore_regex);
> + options->ignore_regex_nr = 0;
> + FREE_AND_NULL(options->ignore_regex);
> }
>
> void diff_free(struct diff_options *options)
> diff --git a/t/t4013-diff-various.sh b/t/t4013-diff-various.sh
> index 056e922164d..b556d185f53 100755
> --- a/t/t4013-diff-various.sh
> +++ b/t/t4013-diff-various.sh
> @@ -614,4 +614,19 @@ test_expect_success 'diff -I<regex>: detect malformed regex' '
> test_i18ngrep "invalid regex given to -I: " error
> '
>
> +test_expect_success 'diff -I<regex>: combined diff does not segfault' '
> + revs="HEAD~2 HEAD~ HEAD" &&
> + git diff $revs >expect &&
> + git diff -I . $revs >actual &&
> + test_cmp expect actualAnd indeed this casts such a broken behaviour in stone.
Show 8 quoted lines
> +' > + > +test_expect_success 'diff --output=<file>: combined diff does not segfault' ' > + revs="HEAD~2 HEAD~ HEAD" && > + git diff --output=expect.file $revs >expect.out && > + git diff $revs >actual && > + test_cmp expect.out actual && > + test_must_be_empty expect.file
So is this one.
> +' > + > test_done