Volume XXII, number 280Wednesday, October 7, 2026Latest message 1 hour ago

The Git List

News and archive of git@vger.kernel.org, since April 2005

patchbuiltin/clone: fix segfault when using --revision on some servers

4 messages between Mar 6, 2026 and Apr 9, 2026, from Adrian Friedli, Junio C Hamano, Friedli Adrian LCPF-CH.

Plain Markdown or JSON for tools and agents. Diffs are folded; open one to read it.

Adrian FriedliMar 6, 2026, 11:10 UTC on lore

Fix a segfault when a server advertises more refs than requested when using the --revision argument.

In the good case the server respects `transport_ls_refs_options.ref_prefixes` and in `cmd_clone()` the linked list `refs` returned by `transport_get_remote_refs()` only contains a single item, which is the ref requested with the --revision argument. Both `remote_head` returned by `find_ref_by_name()` and `remote_head_points_at` returned by `guess_remote_head()` are NULL. The guard in `update_remote_refs()` skips a the affected code because `remote_head_points_at` is NULL.

In the bad case the server ignores `transport_ls_refs_options.ref_prefixes` and in `cmd_clone()` the linked list `refs` returned by `transport_get_remote_refs()` contains many items, amongst others "HEAD". `remote_head` returned by `find_ref_by_name()` is not NULL and `remote_head_points_at` returned by `guess_remote_head()` is not NULL but its field `peer_ref` is NULL. Because `remote_head_points_at` is not NULL the guard in `update_remote_refs()` does not skip the affected code and `remote_head_points_at->peer_ref->name` is accessed, which causes a segfault later on.

Extend the guard in `update_remote_refs()` to also skip the block of code if `remote_head_points_at->peer_ref` is NULL.

Signed-off-by: Adrian Friedli <adrian.friedli@mt.com>
---
The segfault can be reproduced by e.g.

git clone --revision=refs/heads/main \ https://dev.azure.com/public-git/sample/_git/sample

 builtin/clone.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)
Show changes to builtin/clone.c +1 −1
diff --git a/builtin/clone.c b/builtin/clone.c
index fba3c9c508..09219791da 100644
--- a/builtin/clone.c
+++ b/builtin/clone.c
@@ -557,7 +557,7 @@ static void update_remote_refs(const struct ref *refs,
 			write_followtags(refs, msg);
 	}
 
-	if (remote_head_points_at && !option_bare) {
+	if (remote_head_points_at && remote_head_points_at->peer_ref && !option_bare) {
 		struct strbuf head_ref = STRBUF_INIT;
 		strbuf_addstr(&head_ref, branch_top);
 		strbuf_addstr(&head_ref, "HEAD");
-- 
2.53.0.394.g500c12b044
Junio C HamanoMar 6, 2026, 19:51 UTC in reply to Adrian Friedli on lore

Re: [PATCH] builtin/clone: fix segfault when using --revision on some servers

Adrian Friedli <adrian.friedli@mt.com> writes:
Show 10 quoted lines
> In the bad case the server ignores
> `transport_ls_refs_options.ref_prefixes` and in `cmd_clone()` the linked
> list `refs` returned by `transport_get_remote_refs()` contains many
> items, amongst others "HEAD". `remote_head` returned by
> `find_ref_by_name()` is not NULL and `remote_head_points_at` returned by
> `guess_remote_head()` is not NULL but its field `peer_ref` is NULL.
> Because `remote_head_points_at` is not NULL the guard in
> `update_remote_refs()` does not skip the affected code and
> `remote_head_points_at->peer_ref->name` is accessed, which causes a
> segfault later on.

The description makes it sound more like this code is perfectly fine, and the problem is in guess_remote_head() that reads the refs list and includes such a bogus thing with no peer_ref in the result of its guessing. There are 4 direct callers to guess_remote_head() including this one---wouldn't they also obtain a list with such a ref entry?

Or is guess_remote_head() correct in that some uses of its result do not mind such a ref with no peer_ref, but only this code path wants to see a ref with peer_ref? If that is the case, then shouldn't the code this patch touches be looping over remote_head_points_at to see if there is one with a peer_ref and use that? The original is assuming that remote_head_points_at that is not NULL has a valid and usable entry at the beginning of the list, but if that assumption does not hold and we are getting multiple hits, wouldn't it be possible that a good entry is hidden behind a bad one in the list of refs?

Thanks.
Show 26 quoted lines
> Extend the guard in `update_remote_refs()` to also skip the block of
> code if `remote_head_points_at->peer_ref` is NULL.
>
> Signed-off-by: Adrian Friedli <adrian.friedli@mt.com>
> ---
> The segfault can be reproduced by e.g.
>
> git clone --revision=refs/heads/main \
> https://dev.azure.com/public-git/sample/_git/sample
>
>  builtin/clone.c | 2 +-
>  1 file changed, 1 insertion(+), 1 deletion(-)
>
> diff --git a/builtin/clone.c b/builtin/clone.c
> index fba3c9c508..09219791da 100644
> --- a/builtin/clone.c
> +++ b/builtin/clone.c
> @@ -557,7 +557,7 @@ static void update_remote_refs(const struct ref *refs,
>  			write_followtags(refs, msg);
>  	}
>  
> -	if (remote_head_points_at && !option_bare) {
> +	if (remote_head_points_at && remote_head_points_at->peer_ref && !option_bare) {
>  		struct strbuf head_ref = STRBUF_INIT;
>  		strbuf_addstr(&head_ref, branch_top);
>  		strbuf_addstr(&head_ref, "HEAD");
Friedli Adrian LCPF-CHMar 12, 2026, 12:34 UTC in reply to Junio C Hamano on lore

Re: [PATCH] builtin/clone: fix segfault when using --revision on some servers

Hi
Thanks for your response.
Show 16 quoted lines
> > In the bad case the server ignores
> > `transport_ls_refs_options.ref_prefixes` and in `cmd_clone()` the
> > linked list `refs` returned by `transport_get_remote_refs()` contains
> > many items, amongst others "HEAD". `remote_head` returned by
> > `find_ref_by_name()` is not NULL and `remote_head_points_at` returned
> > by `guess_remote_head()` is not NULL but its field `peer_ref` is NULL.
> > Because `remote_head_points_at` is not NULL the guard in
> > `update_remote_refs()` does not skip the affected code and
> > `remote_head_points_at->peer_ref->name` is accessed, which causes a
> > segfault later on.
> 
> The description makes it sound more like this code is perfectly fine, and the
> problem is in guess_remote_head() that reads the refs list and includes such a
> bogus thing with no peer_ref in the result of its guessing.  There are 4 direct
> callers to guess_remote_head() including this one---wouldn't they also obtain
> a list with such a ref entry?

I traced the 3 other callers to guess_remote_head() and none of them has a problem if peer_ref is NULL. In get_expanded_map() there is even a condition `if (cpy->peer_ref)`, which indicates peer_ref is allowed to be NULL.

Show 8 quoted lines
> Or is guess_remote_head() correct in that some uses of its result do not mind
> such a ref with no peer_ref, but only this code path wants to see a ref with
> peer_ref?  If that is the case, then shouldn't the code this patch touches be
> looping over remote_head_points_at to see if there is one with a peer_ref and
> use that?  The original is assuming that remote_head_points_at that is not
> NULL has a valid and usable entry at the beginning of the list, but if that
> assumption does not hold and we are getting multiple hits, wouldn't it be
> possible that a good entry is hidden behind a bad one in the list of refs?

In this path, guess_remote_head() is called without the REMOTE_GUESS_HEAD_ALL flag, which makes it return only the most likely ref.

Kind regards, Adrian

Adrian FriedliApr 9, 2026, 13:09 UTC in reply to Friedli Adrian LCPF-CH on lore

Re: [PATCH] builtin/clone: fix segfault when using --revision on some servers

Show 20 quoted lines
> > > In the bad case the server ignores
> > > `transport_ls_refs_options.ref_prefixes` and in `cmd_clone()` the
> > > linked list `refs` returned by `transport_get_remote_refs()` contains
> > > many items, amongst others "HEAD". `remote_head` returned by
> > > `find_ref_by_name()` is not NULL and `remote_head_points_at` returned
> > > by `guess_remote_head()` is not NULL but its field `peer_ref` is NULL.
> > > Because `remote_head_points_at` is not NULL the guard in
> > > `update_remote_refs()` does not skip the affected code and
> > > `remote_head_points_at->peer_ref->name` is accessed, which causes a
> > > segfault later on.
> >
> > The description makes it sound more like this code is perfectly fine, and the
> > problem is in guess_remote_head() that reads the refs list and includes such a
> > bogus thing with no peer_ref in the result of its guessing.  There are 4 direct
> > callers to guess_remote_head() including this one---wouldn't they also obtain
> > a list with such a ref entry?
> 
> I traced the 3 other callers to guess_remote_head() and none of them has a
> problem if peer_ref is NULL. In get_expanded_map() there is even a condition
> `if (cpy->peer_ref)`, which indicates peer_ref is allowed to be NULL.
The details of the 4 callers to guess_remote_head()

builtin/fetch.c:set_head() builtin/remote.c:get_head_names()

  refs from guess_remote_head() are only used with ref->name
builtin/clone.c:wanted_peer_refs()
  Does not use REMOTE_GUESS_HEAD_ALL flag in guess_remote_head() call. So only one ref is returned.
  calls get_fetch_map() with refs from guess_remote_head().
    may call get_expanded_map()
      safe if peer_ref is NULL, has a guard on the copy of ref `if (cpy->peer_ref)`.
    may call get_remote_ref()
      calls get_remote_ref()
        calls find_ref_by_name_abbrev()
	  only looks at name and next
builtin/clone.c:cmd_clone()
  Does not use REMOTE_GUESS_HEAD_ALL flag in guess_remote_head() call. So only one ref is returned.
  may call write_refspec_config() with refs from guess_remote_head()
    only uses name from refs
  calls update_remote_refs() with refs from guess_remote_head() 
  -> thats the failing one, no other uses
  may call update_head() with refs from guess_remote_head()
    only uses name and old_oid from refs

Kind regards Adrian

Back to recent threads