diff mbox series

[v2] promisor-remote: skip move_to_tail when no-op

Message ID 20190926213156.88185-1-emilyshaffer@google.com (mailing list archive)
State New, archived
Headers show
Series [v2] promisor-remote: skip move_to_tail when no-op | expand

Commit Message

Emily Shaffer Sept. 26, 2019, 9:31 p.m. UTC
Previously, when promisor_remote_move_to_tail() is called for a
promisor_remote which is currently the final element in promisors, a
cycle is created in the promisors linked list. This cycle leads to a
double free later on in promisor_remote_clear() when the final element
of the promisors list is removed: promisors is set to promisors->next (a
no-op, as promisors->next == promisors); the previous value of promisors
is free()'d; then the new value of promisors (which is equal to the
previous value of promisors) is also free()'d. This double-free error
was unrecoverable for the user without removing the filter or re-cloning
the repo and hoping to miss this edge case.

Now, when promisor_remote_move_to_tail() would be a no-op, just do a
no-op. In cases of promisor_remote_move_to_tail() where r is not already
at the tail of the list, it works as before.

Signed-off-by: Emily Shaffer <emilyshaffer@google.com>
Helped-by: Jeff King <peff@peff.net>
---
Thanks Peff for pointing out this bug occurs any time the tail is being
moved to tail in the promisors list. Modified patch to check just for
"already at tail" condition.

Added a test case based on Peff's reproduction steps (thanks!) and
confirmed it failed before the patch, exiting gracelessly with a
SIGABRT, and now fails gracefully (and passes test_must_fail condition).

 - Emily


 promisor-remote.c        |  3 +++
 t/t0410-partial-clone.sh | 12 ++++++++++++
 2 files changed, 15 insertions(+)

Comments

Jeff King Sept. 27, 2019, 12:32 a.m. UTC | #1
On Thu, Sep 26, 2019 at 02:31:56PM -0700, Emily Shaffer wrote:

> ---
> Thanks Peff for pointing out this bug occurs any time the tail is being
> moved to tail in the promisors list. Modified patch to check just for
> "already at tail" condition.
> 
> Added a test case based on Peff's reproduction steps (thanks!) and
> confirmed it failed before the patch, exiting gracelessly with a
> SIGABRT, and now fails gracefully (and passes test_must_fail condition).

Thanks, this is looking good, though I have a few nits.

> Signed-off-by: Emily Shaffer <emilyshaffer@google.com>
> Helped-by: Jeff King <peff@peff.net>

The trailers are supposed to be chronological. It probably doesn't
matter a lot here, but it's more important for something like a signoff
chain. I suppose you could also argue that you signed off and then I
helped but I think you signed off again after. :)

> diff --git a/promisor-remote.c b/promisor-remote.c
> index 9bc296cdde..9bd5b79d59 100644
> --- a/promisor-remote.c
> +++ b/promisor-remote.c
> @@ -89,6 +89,9 @@ static struct promisor_remote *promisor_remote_lookup(const char *remote_name,
>  static void promisor_remote_move_to_tail(struct promisor_remote *r,
>  					 struct promisor_remote *previous)
>  {
> +	if (r->next == NULL)
> +		return;
> +
>  	if (previous)
>  		previous->next = r->next;
>  	else

Yeah, I think of all the discussed options, this one is pretty easy to
understand.

> --- a/t/t0410-partial-clone.sh
> +++ b/t/t0410-partial-clone.sh
> @@ -429,6 +429,18 @@ test_expect_success 'rev-list dies for missing objects on cmd line' '
>  	done
>  '
>  
> +test_expect_success 'single promisor remote can be re-initialized gracefully' '
> +	# ensure one promisor is in the promisors list
> +	rm -rf repo &&
> +	test_create_repo repo &&
> +	git -C repo remote add foo /wherever &&
> +	git -C repo config remote.foo.promisor true &&
> +	git -C repo config extensions.partialclone foo &&
> +
> +	# reinitialize the promisors list; this must fail gracefully
> +	test_must_fail git -C repo fetch --filter=blob:none foo 2>fetch_err
> +'

Could we make this a little more robust by using a real repo instead of
"/wherever", and confirming that the command actually succeeds?

As a side note, if we're not going to check the content of fetch_err, I
think we're better off not redirecting it (it goes to /dev/null by
default, or the user or log if running with "-v" or "--verbose-log").

-Peff
diff mbox series

Patch

diff --git a/promisor-remote.c b/promisor-remote.c
index 9bc296cdde..9bd5b79d59 100644
--- a/promisor-remote.c
+++ b/promisor-remote.c
@@ -89,6 +89,9 @@  static struct promisor_remote *promisor_remote_lookup(const char *remote_name,
 static void promisor_remote_move_to_tail(struct promisor_remote *r,
 					 struct promisor_remote *previous)
 {
+	if (r->next == NULL)
+		return;
+
 	if (previous)
 		previous->next = r->next;
 	else
diff --git a/t/t0410-partial-clone.sh b/t/t0410-partial-clone.sh
index d4b7e535ea..4db4d488c8 100755
--- a/t/t0410-partial-clone.sh
+++ b/t/t0410-partial-clone.sh
@@ -429,6 +429,18 @@  test_expect_success 'rev-list dies for missing objects on cmd line' '
 	done
 '
 
+test_expect_success 'single promisor remote can be re-initialized gracefully' '
+	# ensure one promisor is in the promisors list
+	rm -rf repo &&
+	test_create_repo repo &&
+	git -C repo remote add foo /wherever &&
+	git -C repo config remote.foo.promisor true &&
+	git -C repo config extensions.partialclone foo &&
+
+	# reinitialize the promisors list; this must fail gracefully
+	test_must_fail git -C repo fetch --filter=blob:none foo 2>fetch_err
+'
+
 test_expect_success 'gc repacks promisor objects separately from non-promisor objects' '
 	rm -rf repo &&
 	test_create_repo repo &&