Segfault in "git remote show <remote-name>"
I've been messing around a bit, trying to set up a gitosis repo to mirror one of my projects. Now, I added the remote to my project, but when running "git remote show <remote-name>", I'm getting a segfault in builtin-remote.c at line 303 ("while (ref->next)"), because ref itself is NULL. The offending function itself (get_push_ref_states) is called with NULL as the remote_refs parameters, leading to a NULL-pointer dereferencing.
Here's the backtrace from the crash:
(gdb) bt
#0 get_push_ref_states (remote_refs=0x0, states=0x27fcdc)
at builtin-remote.c:303
#1 0x00444301 in get_remote_ref_states (name=0x342bcb "origin_new",
states=0x27fcdc, query=7) at builtin-remote.c:803
#2 0x004449ae in show (argc=1, argv=0x342d0c) at builtin-remote.c:1004
#3 0x0044593b in cmd_remote (argc=2, argv=0x342d0c, prefix=0x0)
at builtin-remote.c:1323
#4 0x00401bc7 in run_builtin (p=0x4e8338, argc=3, argv=0x342d0c) at git.c:247
#5 0x00401dab in handle_internal_command (argc=3, argv=0x342d0c) at git.c:392
#6 0x00401ea7 in run_argv (argcp=0x27ff30, argv=0x27ff34) at git.c:438
#7 0x00402036 in mingw_main (argc=3, argv=0x342d0c) at git.c:509
#8 0x00401f12 in main (argc=4, argv=0x342d08) at git.c:456It appears that the reason for the crash is that get_remote_heads() (called from transport_get_remote_refs() through get_refs_via_connect()) returns NULL in list.
Here's my log from stepping through get_remote_heads:
Breakpoint 3, get_remote_heads (in=7, list=0x27fc28, nr_match=0, match=0x0,
flags=0, extra_have=0x27a127c) at connect.c:59
59 *list = NULL;
(gdb) n
67 len = packet_read_line(in, buffer, sizeof(buffer));
(gdb) n
68 if (!len)
(gdb) n
101 return list;
(gdb) n
102 }
(gdb) p list
$13 = (struct ref **) 0x27fc28
(gdb) p list
$14 = (struct ref **) 0x27fc28
(gdb) nget_refs_via_connect (transport=0x27a1218, for_push=0) at transport.c:640
640 return refs;
(gdb) p refs
$15 = (struct ref *) 0x0
It looks to me like we're failing to read anything from the network here and don't handle the error correctly, but I don't understand the code well enough to tell for sure.
My best guess would be that a fix could be something like this, but I'm not really 100% sure. Doing so makes "git remote show <remote-name" display something like this
$ /git/git remote show origin_new * remote remote-name URL: USER@HOST:REPONAME.git HEAD branch: (unknown)
Which I guess makes sense, since I haven't made any branches on that repo yet.
builtin-remote.c +1 -0
@@ -793,6 +793,7 @@ static int get_remote_ref_states(const char *name, states->remote->url[0] : NULL); remote_refs =transport_get_remote_refs (transport);transport_disconnect (transport); +if (!remote_refs)return 0 ; states->queried =1 ;if (query &GET_REF_STATES )
Signature · 3 lines
Erik "kusma" Faye-Lund [email protected] (+47) 986 59 656
Subject: [PATCH] fix segfault showing an empty remote
In case of an empty list, the search for its tail caused a NULL-pointer dereference.
---
Quoted 5 lines · Erik Faye-Lund
I've been messing around a bit, trying to set up a gitosis repo to mirror one of my projects. Now, I added the remote to my project, but when running "git remote show <remote-name>", I'm getting a segfault in
builtin-remote.cat line 303 ("while (ref->next)"), because ref itself is NULL.
This should fix it.
I simply copied this from other uses of match_refs. I wonder if this calls for a find_link_ref_tail() function, but I didn't know where to put it.
Clemens
builtin-remote.c | 8 ++++---- t/t5505-remote.sh | 10 ++++++++++ 2 files changed, 14 insertions(+), 4 deletions(-)
builtin-remote.c +4 -4
@@ -299,11 +299,11 @@ static int get_push_ref_states(const struct ref *remote_refs,return 0 ; local_refs =get_local_heads (); - ref = push_map =copy_ref_list (remote_refs); -while (ref->next) - ref = ref->next; - push_tail = &ref->next; + push_map =copy_ref_list (remote_refs); + push_tail = &push_map; +while (*push_tail) + push_tail = &((*push_tail)->next);match_refs (local_refs, push_map, &push_tail, remote->push_refspec_nr, remote->push_refspec,MATCH_REFS_NONE );
t/t5505-remote.sh +10 -0
@@ -494,5 +494,15 @@ test_expect_success 'remote prune to cause a dangling symref' ' grep "dangling symref" err ' +test_expect_success 'show empty remote' ' + + test_create_repo empty && + git clone empty empty-clone && + ( + cd empty-clone && + git remote show origin + ) +' + test_done
Signature · 1 line
1.6.3.1.147.g637c3
Erik Faye-Lund
Tested-byQuoted 7 lines · Clemens Buchacher
Subject: [PATCH] fix segfault showing an empty remote
In case of an empty list, the search for its tail caused a NULL-pointer dereference.
Reported-by Erik Faye-Lund <[email protected]>Signed-off-by Clemens Buchacher <[email protected]>
Quoted 10 lines
---
Quoted 5 lines · Erik Faye-Lund
I've been messing around a bit, trying to set up a gitosis repo to mirror one of my projects. Now, I added the remote to my project, but when running "git remote show <remote-name>", I'm getting a segfault in
builtin-remote.cat line 303 ("while (ref->next)"), because ref itself is NULL.This should fix it.
Thanks a lot for looking into and fixing this. I have verified that the patch indeed fixes the issue for me.
Signature · 3 lines
Erik "kusma" Faye-Lund [email protected] (+47) 986 59 656
Jay Soffian
Acked-byQuoted 7 lines · Clemens Buchacher
Subject: [PATCH] fix segfault showing an empty remote
In case of an empty list, the search for its tail caused a NULL-pointer dereference.
Reported-by Erik Faye-Lund <[email protected]>Signed-off-by Clemens Buchacher <[email protected]>
Quoted 10 lines
---
Quoted 5 lines · Erik Faye-Lund
I've been messing around a bit, trying to set up a gitosis repo to mirror one of my projects. Now, I added the remote to my project, but when running "git remote show <remote-name>", I'm getting a segfault in
builtin-remote.cat line 303 ("while (ref->next)"), because ref itself is NULL.This should fix it.
Thanks.
Quoted 2 lines
I simply copied this from other uses of
match_refs. I wonder if this calls for afind_link_ref_tail()function, but I didn't know where to put it.
Since the primary use case is in combination with match_refs(), remote.[ch] perhaps.
j.
PATCH 1/2 fix segfault showing an empty remote
In case of an empty list, the search for its tail caused a NULL-pointer dereference.
--- builtin-remote.c | 8 ++++---- t/t5505-remote.sh | 10 ++++++++++ 2 files changed, 14 insertions(+), 4 deletions(-)
builtin-remote.c +4 -4
@@ -299,11 +299,11 @@ static int get_push_ref_states(const struct ref *remote_refs,return 0 ; local_refs =get_local_heads (); - ref = push_map =copy_ref_list (remote_refs); -while (ref->next) - ref = ref->next; - push_tail = &ref->next; + push_map =copy_ref_list (remote_refs); + push_tail = &push_map; +while (*push_tail) + push_tail = &((*push_tail)->next);match_refs (local_refs, push_map, &push_tail, remote->push_refspec_nr, remote->push_refspec,MATCH_REFS_NONE );
t/t5505-remote.sh +10 -0
@@ -494,5 +494,15 @@ test_expect_success 'remote prune to cause a dangling symref' ' grep "dangling symref" err ' +test_expect_success 'show empty remote' ' + + test_create_repo empty && + git clone empty empty-clone && + ( + cd empty-clone && + git remote show origin + ) +' + test_done
Signature · 1 line
1.6.3.1.147.g637c3
PATCH 2/2 match_refs: search ref list tail internally
Avoid code duplication by moving list tail search to match_refs().
This does not change the semantics, not even for http-push. The NULL test for remote_tail was redundant.
---
Quoted 6 lines · Jay Soffian
Quoted 2 lines · Clemens Buchacher
I simply copied this from other uses of
match_refs. I wonder if this calls for afind_link_ref_tail()function, but I didn't know where to put it.-
Since the primary use case is in combination withmatch_refs(), remote.[ch] perhaps.
Fair enough. Here it goes.
builtin-remote.c | 7 ++----- builtin-send-pack.c | 9 ++------- http-push.c | 4 +--- remote.c | 17 +++++++++++++---- remote.h | 2 +- transport.c | 6 +----- 6 files changed, 20 insertions(+), 25 deletions(-)
builtin-remote.c +2 -5
@@ -294,17 +294,14 @@ static int get_push_ref_states(const struct ref *remote_refs, struct ref_states *states) {struct remote *remote = states->remote; -struct ref *ref, *local_refs, *push_map, **push_tail; +struct ref *ref, *local_refs, *push_map;if (remote->mirror)return 0 ; local_refs =get_local_heads (); push_map =copy_ref_list (remote_refs); - push_tail = &push_map; -while (*push_tail) - push_tail = &((*push_tail)->next); -match_refs (local_refs, push_map, &push_tail, remote->push_refspec_nr, +match_refs (local_refs, &push_map, remote->push_refspec_nr, remote->push_refspec,MATCH_REFS_NONE ); states->push.strdup_strings =1 ;
builtin-send-pack.c +2 -7
@@ -473,7 +473,7 @@ int cmd_send_pack(int argc, const char **argv, const char *prefix)int fd[2 ];struct child_process *conn;struct extra_have_objects extra_have; -struct ref *remote_refs, **remote_tail, *local_refs; +struct ref *remote_refs, *local_refs;int ret;int send_all =0 ;const char *receivepack ="git-receive-pack" ;
@@ -567,13 +567,8 @@ int cmd_send_pack(int argc, const char **argv, const char *prefix) flags |=MATCH_REFS_MIRROR ;/* match them up */ - remote_tail = &remote_refs; -while (*remote_tail) - remote_tail = &((*remote_tail)->next); -if (match_refs (local_refs, remote_refs, &remote_tail, - nr_refspecs, refspecs, flags)) { +if (match_refs (local_refs, &remote_refs, nr_refspecs, refspecs, flags))return -1 ; - } ret =send_pack (&args, fd, conn, remote_refs, &extra_have);
http-push.c +1 -3
@@ -2310,9 +2310,7 @@ int main(int argc, char **argv) }/* match them up */ -if (!remote_tail) - remote_tail = &remote_refs; -if (match_refs (local_refs, remote_refs, &remote_tail, +if (match_refs (local_refs, &remote_refs, nr_refspec, (const char **) refspec, push_all)) { rc =-1 ; goto cleanup;
remote.c +13 -4
@@ -1085,12 +1085,20 @@ static const struct refspec *check_pattern_match(const struct refspec *rs,return NULL ; } +static struct ref **tail_ref (struct ref ** head) +{ +struct ref **tail = head; +while (*tail) + tail = &((*tail)->next); +return tail; +} +/* * Note. This is used only by "push"; refspec matching rules for * push and fetch are subtly different, so do not try to reuse it * without thinking. */ -int match_refs (struct ref *src,struct ref *dst,struct ref ***dst_tail, +int match_refs (struct ref *src,struct ref **dst,int nr_refspec,const char **refspec,int flags) {struct refspec *rs;
@@ -1098,13 +1106,14 @@ int match_refs(struct ref *src, struct ref *dst, struct ref ***dst_tail,int send_mirror = flags &MATCH_REFS_MIRROR ;int errs;static const char *default_refspec[] = {":" ,0 }; +struct ref **dst_tail =tail_ref (dst);if (!nr_refspec) { nr_refspec =1 ; refspec = default_refspec; } rs =parse_push_refspec (nr_refspec, (const char **) refspec); - errs =match_explicit_refs (src, dst, dst_tail, rs, nr_refspec); + errs =match_explicit_refs (src, *dst, &dst_tail, rs, nr_refspec);/* pick the remainder */ for ( ; src; src = src->next) {
@@ -1134,7 +1143,7 @@ int match_refs(struct ref *src, struct ref *dst, struct ref ***dst_tail, dst_side, &dst_name))die ("Didn't think it matches any more" ); } - dst_peer =find_ref_by_name (dst, dst_name); + dst_peer =find_ref_by_name (*dst, dst_name);if (dst_peer) {if (dst_peer->peer_ref)/* We're already sending something to this ref. */
@@ -1150,7 +1159,7 @@ int match_refs(struct ref *src, struct ref *dst, struct ref ***dst_tail, goto free_name;/* Create a new one and link it */ - dst_peer =make_linked_ref (dst_name, dst_tail); + dst_peer =make_linked_ref (dst_name, &dst_tail);hashcpy (dst_peer->new_sha1, src->new_sha1); } dst_peer->peer_ref =copy_ref (src);
remote.h +1 -1
@@ -85,7 +85,7 @@ void ref_remove_duplicates(struct ref *ref_map);int valid_fetch_refspec (const char *refspec);struct refspec *parse_fetch_refspec (int nr_refspec,const char **refspec); -int match_refs (struct ref *src,struct ref *dst,struct ref ***dst_tail, +int match_refs (struct ref *src,struct ref **dst,int nr_refspec,const char **refspec,int all);/*
transport.c +1 -5
@@ -1003,7 +1003,6 @@ int transport_push(struct transport *transport,if (transport->push_refs) {struct ref *remote_refs = transport->get_refs_list (transport,1 ); -struct ref **remote_tail;struct ref *local_refs =get_local_heads ();int match_flags =MATCH_REFS_NONE ;int verbose = flags &TRANSPORT_PUSH_VERBOSE ;
@@ -1014,10 +1013,7 @@ int transport_push(struct transport *transport,if (flags &TRANSPORT_PUSH_MIRROR ) match_flags |=MATCH_REFS_MIRROR ; - remote_tail = &remote_refs; -while (*remote_tail) - remote_tail = &((*remote_tail)->next); -if (match_refs (local_refs, remote_refs, &remote_tail, +if (match_refs (local_refs, &remote_refs, refspec_nr, refspec, match_flags)) {return -1 ; }
Signature · 1 line
1.6.3.1.147.g637c3
Re: [PATCH 2/2] match_refs: search ref list tail internally
Quoted 4 lines · Clemens Buchacher
Avoid code duplication by moving list tail search to
match_refs().This does not change the semantics, not even for http-push. The NULL test for
remote_tailwas redundant.
The existing program (and the result after the patch) open-codes too much inside the huge main() function, and it is extremely painful to follow what is going on.
But it is not that "the NULL test was redundant" that I'd be worried about. In that unreadable main() function:
- get_dav_remote_heads() is called, which asks the other end what the remote refs are; the result is queued in the linked list whose head is remote_refs and tail is remote_tail;
- the resulting remote_tail is given to match_refs() to further grow the linked list. The function will queue new "struct ref" instances (e.g. by making a call to match_explicit_refs()) at the end of the list.
The caller used to pass "here is the end of the list" variable to it, but now you compute (perhaps redundantly) the end of the ref list by tangling from dst to its tail, yourself, and match_refs() links new elements at the tail of the list correctly.
But what happens to the calling program's remote_tail variable after match_refs() returns? The code used to guarantee that it always point at the real end of the list, but that guarantee is now gone.
It so happens that http-push.c never looked at remote_tail to do further processing on the list after match_refs() returned, and that is why your patch does not break http-push.c. Any third-party patch to http-push.c that relied on the old guarantee will textually merge cleanly but will subtly break with this change.
Other parts of this patch removes the local "remote_tail" variables, and it is very clear that they do not have this problem; any third-parth patch will break if they used remote_tail after match_refs() returned, so this change is a safe one for them.
I wonder what interaction this change will have with the http-push clean-up Ray Chuan has been working on...
Re: [PATCH 2/2] match_refs: search ref list tail internally
Quoted 4 lines · Junio C Hamano
It so happens that
http-push.cnever looked atremote_tailto do further processing on the list aftermatch_refs()returned, and that is why your patch does not breakhttp-push.c.
Quoted 8 lines
Any third-party patch to
http-push.c
that relied on the old guarantee will textually merge cleanly but will subtly break with this change.Other parts of this patch removes the local "
remote_tail" variables, and it is very clear that they do not have this problem; any third-parth patch will break if they usedremote_tailaftermatch_refs()returned, so this change is a safe one for them.
Yes, I tried to get rid of remote_tail entirely, but I could not come up with a solution immediately. We could insert to the front of the list instead. I'll try that.
Quoted 2 lines
I wonder what interaction this change will have with the http-push clean-up Ray Chuan has been working on...
For me, t5540 currently segfaults on pu. I'm looking into it.
PATCH v2 1/1 match_refs: search ref list tail internally
In reply to Junio C HamanoAvoid code duplication by moving list tail search to match_refs().
This does not change the semantics, except for http-push, which now inserts to the front of the ref list in order to get rid of the global remote_tail.
---
Quoted 5 lines · Junio C Hamano
Quoted 4 lines · Clemens Buchacher
Avoid code duplication by moving list tail search to
match_refs().This does not change the semantics, not even for http-push. The NULL test for
remote_tailwas redundant.
[...]
Quoted 4 lines
Other parts of this patch removes the local "
remote_tail" variables, and it is very clear that they do not have this problem; any third-parth patch will break if they usedremote_tailaftermatch_refs()returned, so this change is a safe one for them.
Alright, so here's the version which removes remote_tail.
Quoted 2 lines
I wonder what interaction this change will have with the http-push clean-up Ray Chuan has been working on...
I did test on rctay's http-fix-push-fetching branch ingit://github.com/rctay/git.git with the most recent fixes.
I also squashed in your style fixes from the version currently in pu. Thanks.
Clemens
builtin-remote.c | 7 ++----- builtin-send-pack.c | 9 ++------- http-push.c | 11 ++++------- remote.c | 17 +++++++++++++---- remote.h | 2 +- transport.c | 6 +----- 6 files changed, 23 insertions(+), 29 deletions(-)
builtin-remote.c +2 -5
@@ -294,17 +294,14 @@ static int get_push_ref_states(const struct ref *remote_refs, struct ref_states *states) {struct remote *remote = states->remote; -struct ref *ref, *local_refs, *push_map, **push_tail; +struct ref *ref, *local_refs, *push_map;if (remote->mirror)return 0 ; local_refs =get_local_heads (); push_map =copy_ref_list (remote_refs); - push_tail = &push_map; -while (*push_tail) - push_tail = &((*push_tail)->next); -match_refs (local_refs, push_map, &push_tail, remote->push_refspec_nr, +match_refs (local_refs, &push_map, remote->push_refspec_nr, remote->push_refspec,MATCH_REFS_NONE ); states->push.strdup_strings =1 ;
builtin-send-pack.c +2 -7
@@ -473,7 +473,7 @@ int cmd_send_pack(int argc, const char **argv, const char *prefix)int fd[2 ];struct child_process *conn;struct extra_have_objects extra_have; -struct ref *remote_refs, **remote_tail, *local_refs; +struct ref *remote_refs, *local_refs;int ret;int send_all =0 ;const char *receivepack ="git-receive-pack" ;
@@ -567,13 +567,8 @@ int cmd_send_pack(int argc, const char **argv, const char *prefix) flags |=MATCH_REFS_MIRROR ;/* match them up */ - remote_tail = &remote_refs; -while (*remote_tail) - remote_tail = &((*remote_tail)->next); -if (match_refs (local_refs, remote_refs, &remote_tail, - nr_refspecs, refspecs, flags)) { +if (match_refs (local_refs, &remote_refs, nr_refspecs, refspecs, flags))return -1 ; - } ret =send_pack (&args, fd, conn, remote_refs, &extra_have);
http-push.c +4 -7
@@ -1850,7 +1850,7 @@ static int update_remote(unsigned char *sha1, struct remote_lock *lock)return 1 ; } -static struct ref *remote_refs, **remote_tail; +static struct ref *remote_refs;static void one_remote_ref (char *refname) {
@@ -1880,13 +1880,12 @@ static void one_remote_ref(char *refname) } } - *remote_tail = ref; - remote_tail = &ref->next; + ref->next = remote_refs; + remote_refs = ref; }static void get_dav_remote_heads (void ) { - remote_tail = &remote_refs;remote_ls ("refs/" , (PROCESS_FILES |PROCESS_DIRS |RECURSIVE ), process_ls_ref,NULL ); }
@@ -2341,9 +2340,7 @@ int main(int argc, char **argv) }/* match them up */ -if (!remote_tail) - remote_tail = &remote_refs; -if (match_refs (local_refs, remote_refs, &remote_tail, +if (match_refs (local_refs, &remote_refs, nr_refspec, (const char **) refspec, push_all)) { rc =-1 ; goto cleanup;
remote.c +13 -4
@@ -1085,12 +1085,20 @@ static const struct refspec *check_pattern_match(const struct refspec *rs,return NULL ; } +static struct ref **tail_ref (struct ref **head) +{ +struct ref **tail = head; +while (*tail) + tail = &((*tail)->next); +return tail; +} +/* * Note. This is used only by "push"; refspec matching rules for * push and fetch are subtly different, so do not try to reuse it * without thinking. */ -int match_refs (struct ref *src,struct ref *dst,struct ref ***dst_tail, +int match_refs (struct ref *src,struct ref **dst,int nr_refspec,const char **refspec,int flags) {struct refspec *rs;
@@ -1098,13 +1106,14 @@ int match_refs(struct ref *src, struct ref *dst, struct ref ***dst_tail,int send_mirror = flags &MATCH_REFS_MIRROR ;int errs;static const char *default_refspec[] = {":" ,0 }; +struct ref **dst_tail =tail_ref (dst);if (!nr_refspec) { nr_refspec =1 ; refspec = default_refspec; } rs =parse_push_refspec (nr_refspec, (const char **) refspec); - errs =match_explicit_refs (src, dst, dst_tail, rs, nr_refspec); + errs =match_explicit_refs (src, *dst, &dst_tail, rs, nr_refspec);/* pick the remainder */ for ( ; src; src = src->next) {
@@ -1134,7 +1143,7 @@ int match_refs(struct ref *src, struct ref *dst, struct ref ***dst_tail, dst_side, &dst_name))die ("Didn't think it matches any more" ); } - dst_peer =find_ref_by_name (dst, dst_name); + dst_peer =find_ref_by_name (*dst, dst_name);if (dst_peer) {if (dst_peer->peer_ref)/* We're already sending something to this ref. */
@@ -1150,7 +1159,7 @@ int match_refs(struct ref *src, struct ref *dst, struct ref ***dst_tail, goto free_name;/* Create a new one and link it */ - dst_peer =make_linked_ref (dst_name, dst_tail); + dst_peer =make_linked_ref (dst_name, &dst_tail);hashcpy (dst_peer->new_sha1, src->new_sha1); } dst_peer->peer_ref =copy_ref (src);
remote.h +1 -1
@@ -85,7 +85,7 @@ void ref_remove_duplicates(struct ref *ref_map);int valid_fetch_refspec (const char *refspec);struct refspec *parse_fetch_refspec (int nr_refspec,const char **refspec); -int match_refs (struct ref *src,struct ref *dst,struct ref ***dst_tail, +int match_refs (struct ref *src,struct ref **dst,int nr_refspec,const char **refspec,int all);/*
transport.c +1 -5
@@ -1003,7 +1003,6 @@ int transport_push(struct transport *transport,if (transport->push_refs) {struct ref *remote_refs = transport->get_refs_list (transport,1 ); -struct ref **remote_tail;struct ref *local_refs =get_local_heads ();int match_flags =MATCH_REFS_NONE ;int verbose = flags &TRANSPORT_PUSH_VERBOSE ;
@@ -1014,10 +1013,7 @@ int transport_push(struct transport *transport,if (flags &TRANSPORT_PUSH_MIRROR ) match_flags |=MATCH_REFS_MIRROR ; - remote_tail = &remote_refs; -while (*remote_tail) - remote_tail = &((*remote_tail)->next); -if (match_refs (local_refs, remote_refs, &remote_tail, +if (match_refs (local_refs, &remote_refs, refspec_nr, refspec, match_flags)) {return -1 ; }
Signature · 1 line
1.6.3.1.147.g637c3