Volume XXII, number 280Wednesday, October 7, 2026Latest message 54 minutes ago

The Git List

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

remote-curl: segfault parsing remote.<name>.fetch outside a repository

17 messages between Mar 21, 2026 and Mar 24, 2026, from Jo Liss, K Jayatheerth, Jeff King, brian m. carlson, Junio C Hamano.

Plain Markdown or JSON for tools and agents.

Jo LissMar 21, 2026, 19:11 UTC on lore
Hi mailing list,

I ran into a bug and thought I'd report it! The following command segfaults for me (where ~/src/git is my clone):

env -C / \
    GIT_CONFIG_NOSYSTEM=1 \
    GIT_CONFIG_GLOBAL=/dev/null \
    GIT_CONFIG_COUNT=1 \
    GIT_CONFIG_KEY_0=remote.repro.fetch \
    GIT_CONFIG_VALUE_0='+refs/tags/*:refs/tags/*' \
    ~/src/git/git-remote-http repro

In other words, this is happening when the shared remote-curl code (here, git-remote-http) is called outside of any repository, while `remote.<name>.fetch` is set.

I can reproduce this on Ubuntu and macOS, with git master (7ff1e8dc1e16) and git 2.51.0.

The way I actually ran into this was by running `git ls-remote -h <url>` outside of a git repository, and my `remote.origin.fetch` is globally set to `+refs/tags/*:refs/tags/*`.

Here's a backtrace:
~/src/git $ make clean && make DEVELOPER=1 CFLAGS='-g -O0 -Wall'
...
~/src/git $ env -C / \
  GIT_CONFIG_NOSYSTEM=1 \
  GIT_CONFIG_GLOBAL=/dev/null \
  GIT_CONFIG_COUNT=1 \
  GIT_CONFIG_KEY_0=remote.repro.fetch \
  GIT_CONFIG_VALUE_0='+refs/tags/*:refs/tags/*' \
  gdb -q -batch \
  -ex 'set debuginfod enabled off' \
  -ex 'set startup-with-shell off' \
  -ex run \
  -ex 'bt full' \
  --args ~/src/git/git-remote-http repro
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/lib/aarch64-linux-gnu/libthread_db.so.1".
Program received signal SIGSEGV, Segmentation fault.
parse_refspec (item=0xffffffffda88, refspec=0xaaaaaadf0650
"+refs/tags/*:refs/tags/*", fetch=1) at refspec.c:104
104 else if (llen == the_hash_algo->hexsz && !get_oid_hex(item->src, &unused))
#0  parse_refspec (item=0xffffffffda88, refspec=0xaaaaaadf0650
"+refs/tags/*:refs/tags/*", fetch=1) at refspec.c:104
        unused = {hash = "
\nߪ\252\252\000\000\031\000\000\000\000\000\000\000
\332\377\377\377\377\000\000\300Iʪ\252\252\000", algo = 2866743840}
        llen = 11
        is_glob = 1
        lhs = 0xaaaaaadf0651 "refs/tags/*:refs/tags/*"
        rhs = 0xaaaaaadf065d "refs/tags/*"
        flags = 3
#1  0x0000aaaaaaca49dc in refspec_item_init (item=0xffffffffda88,
refspec=0xaaaaaadf0650 "+refs/tags/*:refs/tags/*", fetch=1) at
refspec.c:161
No locals.
#2  0x0000aaaaaaca4a04 in refspec_item_init_fetch
(item=0xffffffffda88, refspec=0xaaaaaadf0650
"+refs/tags/*:refs/tags/*") at refspec.c:166
No locals.
#3  0x0000aaaaaaca4c08 in refspec_append (rs=0xaaaaaadf0a90,
refspec=0xaaaaaadf0650 "+refs/tags/*:refs/tags/*") at refspec.c:203
        item = {force = 1, pattern = 1, matching = 0, exact_sha1 = 0,
negative = 0, src = 0xaaaaaadd49d0 "refs/tags/*", dst = 0xaaaaaadd4b90
"refs/tags/*", raw = 0xaaaaaadf0b20 "+refs/tags/*:refs/tags/*"}
        ret = 43690
#4  0x0000aaaaaab64c00 in handle_config (key=0xaaaaaadd4810
"remote.repro.fetch", value=0xaaaaaadf06d0 "+refs/tags/*:refs/tags/*",
ctx=0xffffffffdb80, cb=0xaaaaaadeeb70) at remote.c:528
        v = 0xaaaaaadf0650 "+refs/tags/*:refs/tags/*"
        name = 0xaaaaaadd4817 "repro.fetch"
        namelen = 5
        subkey = 0xaaaaaadd481d "fetch"
        remote = 0xaaaaaadf0a20
        branch = 0xaaaaaab98654 <cmp_strmap_entry>
        remote_state = 0xaaaaaadeeb70
        kvi = 0xaaaaaadd48f0
#5  0x0000aaaaaaac12f0 in configset_iter (set=0xaaaaaadeeb20,
fn=0xaaaaaab645e4 <handle_config>, data=0xaaaaaadeeb70) at
config.c:1639
        i = 0
        value_index = 0
        values = 0xaaaaaadf0698
        entry = 0xaaaaaadf0680
        list = 0xaaaaaadeeb58
        ctx = {kvi = 0xaaaaaadd48f0}
#6  0x0000aaaaaaac3134 in repo_config (repo=0xaaaaaadc9e70 <the_repo>,
fn=0xaaaaaab645e4 <handle_config>, data=0xaaaaaadeeb70) at
config.c:2300
No locals.
#7  0x0000aaaaaab6537c in read_config (repo=0xaaaaaadc9e70 <the_repo>,
early=0) at remote.c:637
        flag = 0
#8  0x0000aaaaaab65b3c in remote_get (name=0xffffffffe465 "repro") at
remote.c:823
No locals.
#9  0x0000aaaaaaab2164 in cmd_main (argc=2, argv=0xffffffffde88) at
remote-curl.c:1568
        buf = {alloc = 0, len = 0, buf = 0xaaaaaadca368 <strbuf_slopbuf> ""}
        nongit = 1
        ret = 1
#10 0x0000aaaaaaabc688 in main (argc=2, argv=0xffffffffde88) at common-main.c:9
        result = 65535

It looks like the immediate crash is in `parse_refspec()`, where `the_hash_algo->hexsz` is dereferenced while `the_repository->hash_algo` is still NULL.

Best, Jo

K JayatheerthMar 21, 2026, 19:46 UTC in reply to Jo Liss on lore

[PATCH] remote-curl: set fallback hash algorithm outside repo

When a remote helper like git-remote-http is invoked outside of a repository (for example, by running `git ls-remote -h <url>` in a non-git directory), setup_git_directory_gently() leaves the_repository->hash_algo as NULL.

If the user has a global fetch refspec configured, remote-curl attempts to parse this refspec during initialization. Inside parse_refspec(), it checks whether the LHS of the refspec is an exact OID by evaluating `llen == the_hash_algo->hexsz`. Because the_hash_algo is NULL, this results in a segmentation fault.

Fix this by mirroring the behavior of Git's main built-ins. If remote-curl is operating outside a repository, initialize a fallback hash algorithm (SHA-1) so that refspec parsing can safely check hexadecimal lengths.

Also add a test in t5551 to ensure this regression does not happen again. The test uses GIT_CEILING_DIRECTORIES to ensure the command genuinely runs in a nongit environment without falling back to the test suite's trash directory repository.

Reported-by: Jo Liss <joliss@gmail.com>
Signed-off-by: K Jayatheerth <jayatheerthkulkarni2005@gmail.com>
---
While the fix was tricky to find
I believe it is a small one.
The debug Jo did here helped me find it faster than I would've.
I hope the test I added is in the right file, I had multiple options, but
looking at other test files this seemed a fair option.
 remote-curl.c               |  4 ++++
 t/t5551-http-fetch-smart.sh | 15 +++++++++++++++
 2 files changed, 19 insertions(+)
diff --git a/remote-curl.c b/remote-curl.c
index 92e40bb682..4c85e6b079 100644
--- a/remote-curl.c
+++ b/remote-curl.c
@@ -1547,6 +1547,10 @@ int cmd_main(int argc, const char **argv)
 	int ret = 1;
 
 	setup_git_directory_gently(&nongit);
+
+	if (nongit && !the_repository->hash_algo)
+		repo_set_hash_algo(the_repository, GIT_HASH_SHA1);
+
 	if (argc < 2) {
 		error(_("remote-curl: usage: git remote-curl <remote> [<url>]"));
 		goto cleanup;
diff --git a/t/t5551-http-fetch-smart.sh b/t/t5551-http-fetch-smart.sh
index 73cf531580..ed81e6b49b 100755
--- a/t/t5551-http-fetch-smart.sh
+++ b/t/t5551-http-fetch-smart.sh
@@ -782,4 +782,19 @@ test_expect_success 'tag following always works over v0 http' '
 	test_cmp expect actual
 '
 
+test_expect_success 'ls-remote outside repo does not segfault with fetch refspec' '
+	GIT_CEILING_DIRECTORIES=$(pwd) &&
+	export GIT_CEILING_DIRECTORIES &&
+	mkdir nongit &&
+	(
+		cd nongit &&
+		env GIT_CONFIG_NOSYSTEM=1 \
+			GIT_CONFIG_GLOBAL=/dev/null \
+			GIT_CONFIG_COUNT=1 \
+			GIT_CONFIG_KEY_0=remote.origin.fetch \
+			GIT_CONFIG_VALUE_0="+refs/tags/*:refs/tags/*" \
+			git ls-remote "$HTTPD_URL/smart/repo.git"
+	)
+'
+
 test_done
-- 
2.53.0
Jeff KingMar 21, 2026, 21:06 UTC in reply to Jo Liss on lore

Re: remote-curl: segfault parsing remote.<name>.fetch outside a repository

On Sat, Mar 21, 2026 at 07:11:18PM +0000, Jo Liss wrote:
Show 17 quoted lines
> I ran into a bug and thought I'd report it! The following command
> segfaults for me (where ~/src/git is my clone):
> 
> env -C / \
>     GIT_CONFIG_NOSYSTEM=1 \
>     GIT_CONFIG_GLOBAL=/dev/null \
>     GIT_CONFIG_COUNT=1 \
>     GIT_CONFIG_KEY_0=remote.repro.fetch \
>     GIT_CONFIG_VALUE_0='+refs/tags/*:refs/tags/*' \
>     ~/src/git/git-remote-http repro
> 
> In other words, this is happening when the shared remote-curl code
> (here, git-remote-http) is called outside of any repository, while
> `remote.<name>.fetch` is set.
> 
> I can reproduce this on Ubuntu and macOS, with git master
> (7ff1e8dc1e16) and git 2.51.0.

This is another fallout from c8aed5e8da (repository: stop setting SHA1 as the default object hash, 2024-05-07).

It's a curious case, though. The crashing code is parse_refspec() does this:

  if (llen == the_hash_algo->hexsz && !get_oid_hex(item->src, &unused))
        item->exact_sha1 = 1; /* ok */

But what is the correct hash algo to use here when we are outside a repository? Usually remote-curl tries to detect the hash algorithm in use by the other side (based on its info/refs response). But we don't contact the other side until we've run remote_get(), and the refspec parsing is happening via that remote_get().

In this particular case, the origin refspecs are not even going to be used, but you can construct a similar one where they are:

  git -C / \
      -c remote.foo.url=https://github.com/git/git \
      -c remote.foo.fetch=whatever \
      ls-remote foo
We could do this:
diff --git a/refspec.c b/refspec.c
index 0775358d96..e6c29b7dd0 100644
--- a/refspec.c
+++ b/refspec.c
@@ -101,7 +101,7 @@ static int parse_refspec(struct refspec_item *item, const char *refspec, int fet
 		/* LHS */
 		if (!*item->src)
 			; /* empty is ok; it means "HEAD" */
-		else if (llen == the_hash_algo->hexsz && !get_oid_hex(item->src, &unused))
+		else if (the_hash_algo && llen == the_hash_algo->hexsz && !get_oid_hex(item->src, &unused))
 			item->exact_sha1 = 1; /* ok */
 		else if (!check_refname_format(item->src, flags))
 			; /* valid looking ref is ok */

to make the segfault go away, but it is mostly papering over the
problem. I'm not sure if the exact_sha1 flag would matter when we are
not actually fetching (and we cannot fetch when we are not in a local
repo). Grepping around, it looks like it does influence the ref prefixes
we send to the other side (yet another chicken-and-egg!).

-Peff
brian m. carlsonMar 21, 2026, 23:09 UTC in reply to K Jayatheerth on lore

Re: [PATCH] remote-curl: set fallback hash algorithm outside repo

On 2026-03-21 at 19:46:53, K Jayatheerth wrote:
Show 11 quoted lines
> diff --git a/remote-curl.c b/remote-curl.c
> index 92e40bb682..4c85e6b079 100644
> --- a/remote-curl.c
> +++ b/remote-curl.c
> @@ -1547,6 +1547,10 @@ int cmd_main(int argc, const char **argv)
>  	int ret = 1;
>  
>  	setup_git_directory_gently(&nongit);
> +
> +	if (nongit && !the_repository->hash_algo)
> +		repo_set_hash_algo(the_repository, GIT_HASH_SHA1);
This should be GIT_HASH_DEFAULT, which is whatever the default hash is.

GIT_HASH_SHA1_LEGACY is for places where we assume SHA-1 because the data format doesn't specify (e.g., v1 bundles) and GIT_HASH_SHA1 says, “The user or data format specifically chose SHA-1.” The user didn't specify any particular algorithm here (which is the problem), so we should go with the default (which will change to SHA-256 in 3.0).

That being said, I also agree with Peff downthread that it might be better to fix this elsewhere,

-- 
brian m. carlson (they/them)
Toronto, Ontario, CA
Junio C HamanoMar 22, 2026, 01:20 UTC in reply to Jeff King on lore

Re: remote-curl: segfault parsing remote.<name>.fetch outside a repository

Jeff King <peff@peff.net> writes:
Show 8 quoted lines
> It's a curious case, though. The crashing code is parse_refspec() does
> this:
>
>   if (llen == the_hash_algo->hexsz && !get_oid_hex(item->src, &unused))
>         item->exact_sha1 = 1; /* ok */
>
> But what is the correct hash algo to use here when we are outside a
> repository?

Hmph, who is calling into the transport outside a repository in the first place? Even "git clone" should create the receiving repository before it calls into the transport, no? Is this "git ls-remote" or something?

Show 7 quoted lines
> In this particular case, the origin refspecs are not even going to be
> used, but you can construct a similar one where they are:
>
>   git -C / \
>       -c remote.foo.url=https://github.com/git/git \
>       -c remote.foo.fetch=whatever \
>       ls-remote foo
OK.
Show 21 quoted lines
> We could do this:
>
> diff --git a/refspec.c b/refspec.c
> index 0775358d96..e6c29b7dd0 100644
> --- a/refspec.c
> +++ b/refspec.c
> @@ -101,7 +101,7 @@ static int parse_refspec(struct refspec_item *item, const char *refspec, int fet
>  		/* LHS */
>  		if (!*item->src)
>  			; /* empty is ok; it means "HEAD" */
> -		else if (llen == the_hash_algo->hexsz && !get_oid_hex(item->src, &unused))
> +		else if (the_hash_algo && llen == the_hash_algo->hexsz && !get_oid_hex(item->src, &unused))
>  			item->exact_sha1 = 1; /* ok */
>  		else if (!check_refname_format(item->src, flags))
>  			; /* valid looking ref is ok */
>
> to make the segfault go away, but it is mostly papering over the
> problem. I'm not sure if the exact_sha1 flag would matter when we are
> not actually fetching (and we cannot fetch when we are not in a local
> repo). Grepping around, it looks like it does influence the ref prefixes
> we send to the other side (yet another chicken-and-egg!).

Yup, I agree with your assessment that exact_sha1 should mostly be garbage if we do not have a repository in the first place.

Jeff KingMar 22, 2026, 01:37 UTC in reply to Junio C Hamano on lore

Re: remote-curl: segfault parsing remote.<name>.fetch outside a repository

On Sat, Mar 21, 2026 at 06:20:16PM -0700, Junio C Hamano wrote:
Show 15 quoted lines
> Jeff King <peff@peff.net> writes:
> 
> > It's a curious case, though. The crashing code is parse_refspec() does
> > this:
> >
> >   if (llen == the_hash_algo->hexsz && !get_oid_hex(item->src, &unused))
> >         item->exact_sha1 = 1; /* ok */
> >
> > But what is the correct hash algo to use here when we are outside a
> > repository?
> 
> Hmph, who is calling into the transport outside a repository in the
> first place?  Even "git clone" should create the receiving
> repository before it calls into the transport, no?  Is this "git
> ls-remote" or something?
Yes, exactly; the real-world case that Jo mentioned is ls-remote.
Show 8 quoted lines
> > to make the segfault go away, but it is mostly papering over the
> > problem. I'm not sure if the exact_sha1 flag would matter when we are
> > not actually fetching (and we cannot fetch when we are not in a local
> > repo). Grepping around, it looks like it does influence the ref prefixes
> > we send to the other side (yet another chicken-and-egg!).
> 
> Yup, I agree with your assessment that exact_sha1 should mostly be
> garbage if we do not have a repository in the first place.
There's at least one more instance of the same problem:
          if (item->negative)
		...
                  else if (llen == the_hash_algo->hexsz && !get_oid_hex(item->src, &unused))
                          return 0; /* negative refpsecs cannot be exact sha1 */
It might be OK to quietly disable the check outside a repo there, too.
-Peff
K JayatheerthMar 22, 2026, 02:35 UTC in reply to K Jayatheerth on lore

[PATCH v2] refspec: safely parse refspecs outside a repository

When a remote helper like git-remote-http is invoked outside of a repository (for example, by running `git ls-remote -h <url>` in a non-git directory), `the_hash_algo` is left uninitialized (NULL).

If a user has a global fetch refspec configured, `parse_refspec()` attempts to check if the LHS of the refspec is an exact OID by evaluating `llen == the_hash_algo->hexsz`. Because `the_hash_algo` is NULL, this results in a segmentation fault. This crash occurs for both standard and negative refspecs.

Fix this by ensuring `the_hash_algo` is non-NULL before checking `the_hash_algo->hexsz` for both standard and negative refspecs. When operating outside a repository, fetching is impossible, so bypassing the exact OID check is the cleanest approach.

Additionally, while looking into the remote-curl execution path, take the opportunity to remove an unused `#include "git-curl-compat.h"` from `remote-curl.c`.

Reported-by: Jo Liss <joliss@gmail.com>
Helped-by: Jeff King <peff@peff.net>
Signed-off-by: K Jayatheerth <jayatheerthkulkarni2005@gmail.com>
---
Changes in v2:
Instead of adding a fix by giving a default hash like we did in cmd_apply()
I understood that it is impossible to fetch here.
Therefore I picked up whatever Peff suggested here.
Since I got no feedback on the test, I am assuming it is correct and leaving as is.
 refspec.c                   |  4 ++--
 remote-curl.c               |  1 -
 t/t5551-http-fetch-smart.sh | 15 +++++++++++++++
 3 files changed, 17 insertions(+), 3 deletions(-)
diff --git a/refspec.c b/refspec.c
index 0775358d96..a864a0bac2 100644
--- a/refspec.c
+++ b/refspec.c
@@ -84,7 +84,7 @@ static int parse_refspec(struct refspec_item *item, const char *refspec, int fet
 		 */
 		if (!*item->src)
 			return 0; /* negative refspecs must not be empty */
-		else if (llen == the_hash_algo->hexsz && !get_oid_hex(item->src, &unused))
+		else if (the_hash_algo && llen == the_hash_algo->hexsz && !get_oid_hex(item->src, &unused))
 			return 0; /* negative refpsecs cannot be exact sha1 */
 		else if (!check_refname_format(item->src, flags))
 			; /* valid looking ref is ok */
@@ -101,7 +101,7 @@ static int parse_refspec(struct refspec_item *item, const char *refspec, int fet
 		/* LHS */
 		if (!*item->src)
 			; /* empty is ok; it means "HEAD" */
-		else if (llen == the_hash_algo->hexsz && !get_oid_hex(item->src, &unused))
+		else if (the_hash_algo && llen == the_hash_algo->hexsz && !get_oid_hex(item->src, &unused))
 			item->exact_sha1 = 1; /* ok */
 		else if (!check_refname_format(item->src, flags))
 			; /* valid looking ref is ok */
diff --git a/remote-curl.c b/remote-curl.c
index 92e40bb682..280880e54e 100644
--- a/remote-curl.c
+++ b/remote-curl.c
@@ -2,7 +2,6 @@
 #define DISABLE_SIGN_COMPARE_WARNINGS
 
 #include "git-compat-util.h"
-#include "git-curl-compat.h"
 #include "config.h"
 #include "environment.h"
 #include "gettext.h"
diff --git a/t/t5551-http-fetch-smart.sh b/t/t5551-http-fetch-smart.sh
index 73cf531580..ed81e6b49b 100755
--- a/t/t5551-http-fetch-smart.sh
+++ b/t/t5551-http-fetch-smart.sh
@@ -782,4 +782,19 @@ test_expect_success 'tag following always works over v0 http' '
 	test_cmp expect actual
 '
 
+test_expect_success 'ls-remote outside repo does not segfault with fetch refspec' '
+	GIT_CEILING_DIRECTORIES=$(pwd) &&
+	export GIT_CEILING_DIRECTORIES &&
+	mkdir nongit &&
+	(
+		cd nongit &&
+		env GIT_CONFIG_NOSYSTEM=1 \
+			GIT_CONFIG_GLOBAL=/dev/null \
+			GIT_CONFIG_COUNT=1 \
+			GIT_CONFIG_KEY_0=remote.origin.fetch \
+			GIT_CONFIG_VALUE_0="+refs/tags/*:refs/tags/*" \
+			git ls-remote "$HTTPD_URL/smart/repo.git"
+	)
+'
+
 test_done
-- 
2.53.0
Junio C HamanoMar 22, 2026, 03:31 UTC in reply to K Jayatheerth on lore

Re: [PATCH v2] refspec: safely parse refspecs outside a repository

K Jayatheerth <jayatheerthkulkarni2005@gmail.com> writes:
> Additionally, while looking into the remote-curl execution path,
> take the opportunity to remove an unused `#include "git-curl-compat.h"`
> from `remote-curl.c`.

I wish you didn't do this in the same patch. It is completely unrelated, isn't it?

>  refspec.c                   |  4 ++--
>  remote-curl.c               |  1 -
>  t/t5551-http-fetch-smart.sh | 15 +++++++++++++++
>  3 files changed, 17 insertions(+), 3 deletions(-)
Show 12 quoted lines
> +test_expect_success 'ls-remote outside repo does not segfault with fetch refspec' '
> +	GIT_CEILING_DIRECTORIES=$(pwd) &&
> +	export GIT_CEILING_DIRECTORIES &&
> +	mkdir nongit &&
> +	(
> +		cd nongit &&
> +		env GIT_CONFIG_NOSYSTEM=1 \
> +			GIT_CONFIG_GLOBAL=/dev/null \
> +			GIT_CONFIG_COUNT=1 \
> +			GIT_CONFIG_KEY_0=remote.origin.fetch \
> +			GIT_CONFIG_VALUE_0="+refs/tags/*:refs/tags/*" \
> +			git ls-remote "$HTTPD_URL/smart/repo.git"

This complex "env" dance is probably uncalled for. Wouldn't something like

	mkdir nongit &&
	git -C nongit -c remote.origin.fetch=+refs/*:refs/* \
		ls-remote "$HTTPD_URL/smart/repo.git"
be sufficient?
Jeff KingMar 22, 2026, 03:53 UTC in reply to K Jayatheerth on lore

Re: [PATCH v2] refspec: safely parse refspecs outside a repository

On Sun, Mar 22, 2026 at 08:05:57AM +0530, K Jayatheerth wrote:
> Fix this by ensuring `the_hash_algo` is non-NULL before checking
> `the_hash_algo->hexsz` for both standard and negative refspecs.
> When operating outside a repository, fetching is impossible,
> so bypassing the exact OID check is the cleanest approach.

This argument is glossing over some details. Trying to break down all of the implications, I think we have:

  - Without knowing the hash algo, we cannot reject negative refspecs
    that look like oids. This is OK in practice for two reasons. One,
    the only commands which apply refspecs are fetch and push, and they
    require a repository. And two, while we miss an opportunity to
    complain about broken config, it is quite unlikely for somebody to
    have such config (a global-level configured negative refspec that
    looks like an oid). And they will be told about it when running an
    actual fetch anyway.
  - Without knowing the hash algo, we cannot mark refspecs with the
    exact_sha1 flag. Again, we are not actually applying any refspecs
    unless we have a repo. The exact_sha1 flag is used to influence the
    set of prefixes we send to a remote v2 upload-pack process, but
    only for fetch (which requires a repository). For ls-remote, which
    can run outside a repo, we don't even look at the refspecs.

And so for those reasons it's probably OK to quietly ignore things. Still, it rubs me the wrong way a little that we might create a subtle bug from some other caller.

If we think we don't care about refspecs, it kind of makes me wonder if we ought to be able to tell the remote API that we are interested in remotes for their URLs only, and _not_ for their refspecs. But maybe that leads to madness, as we end up with half-initialized "struct remote"s floating around our process.

The other thing I wondered is why we are talking about remote-curl here, and not ls-remote. And that's because ls-remote already hacked around this!

Check out 9e89dcb66a (builtin/ls-remote: fall back to SHA1 outside of a repo, 2024-08-02), which adds this:

          /*
           * TODO: This is buggy, but required for transport helpers. When a
           * transport helper advertises a "refspec", then we'd add that to a
           * list of refspecs via `refspec_append()`, which transitively depends
           * on `the_hash_algo`. Thus, when the hash algorithm isn't properly set
           * up, this would lead to a segfault.
           *
           * We really should fix this in the transport helper logic such that we
           * lazily parse refspec capabilities _after_ we have learned about the
           * remote's object format. Otherwise, we may end up misparsing refspecs
           * depending on what object hash the remote uses.
           */
          if (!the_repository->hash_algo)
                  repo_set_hash_algo(the_repository, GIT_HASH_DEFAULT);

Obviously that is kicking the can down the road, but it kind of makes sense that we would have the same hack in place for remote-curl (which in practice is only going to be called out-of-repo by ls-remote anyway). It is only the fact that it happens in a separate process that the existing fix from 9e89dcb66a is not helping us.

> Additionally, while looking into the remote-curl execution path,
> take the opportunity to remove an unused `#include "git-curl-compat.h"`
> from `remote-curl.c`.
I doubt this is correct.

remote-curl checks GIT_CURL_NEED_TRANSFER_ENCODING_HEADER, which is defined in git-curl-compat.h. It may work fine without that header if you have a recent version of curl, but older systems would be subtly broken.

Show 14 quoted lines
> +test_expect_success 'ls-remote outside repo does not segfault with fetch refspec' '
> +	GIT_CEILING_DIRECTORIES=$(pwd) &&
> +	export GIT_CEILING_DIRECTORIES &&
> +	mkdir nongit &&
> +	(
> +		cd nongit &&
> +		env GIT_CONFIG_NOSYSTEM=1 \
> +			GIT_CONFIG_GLOBAL=/dev/null \
> +			GIT_CONFIG_COUNT=1 \
> +			GIT_CONFIG_KEY_0=remote.origin.fetch \
> +			GIT_CONFIG_VALUE_0="+refs/tags/*:refs/tags/*" \
> +			git ls-remote "$HTTPD_URL/smart/repo.git"
> +	)
> +'

Some of this is irrelevant to reproducing the bug (like redirecting system and global config). And it is much easier to use "git -c" to set temporary config.

We also have a "nongit" helper function already. So I think just:
   nongit git \
          -c remote.origin.fetch=anything \
          ls-remote "$HTTPD_URL/smart/repo.git"

is enough to trigger it. Possibly it is slightly more realistic to actually use the remote whose refspecs we are configuring:

  nongit git \
         -c remote.origin.url="$HTTPD_URL/smart/repo.git" \
	 -c remote.origin.fetch=anything \
	 ls-remote origin

but as the bug exists now, either is sufficient to trigger it. You could also add a negative refspec if you want to test that half of the change.

-Peff
K JayatheerthMar 22, 2026, 05:36 UTC in reply to K Jayatheerth on lore

[PATCH v3 1/2] refspec: safely parse refspecs outside a repository

When git-remote-http is invoked outside of a repository (for example, by running `git ls-remote` in a non-git directory with a globally configured fetch refspec), `the_hash_algo` is left as NULL by setup_git_directory_gently().

parse_refspec() checks whether the LHS of a refspec is an exact OID by evaluating `llen == the_hash_algo->hexsz`. With `the_hash_algo` being NULL, this results in a segmentation fault. The same NULL dereference exists in the negative refspec path.

Note that builtin/ls-remote already works around a related issue by setting a fallback hash algorithm before calling into the transport layer (see 9e89dcb66a). However, since remote-curl runs as a separate process, that fix does not help here.

Guard both dereferences with a NULL check on `the_hash_algo`. When operating outside a repository, fetching and pushing are impossible anyway, so skipping the exact OID check is safe: the exact_sha1 flag only influences ref prefixes sent to a remote v2 upload-pack during fetch, and we will never reach that point without a local repository.

Reported-by: Jo Liss <joliss@gmail.com>
Helped-by: Jeff King <peff@peff.net>
Signed-off-by: K Jayatheerth <jayatheerthkulkarni2005@gmail.com>
---
 refspec.c                   | 4 ++--
 t/t5551-http-fetch-smart.sh | 7 +++++++
 2 files changed, 9 insertions(+), 2 deletions(-)
diff --git a/refspec.c b/refspec.c
index 0775358d96..a864a0bac2 100644
--- a/refspec.c
+++ b/refspec.c
@@ -84,7 +84,7 @@ static int parse_refspec(struct refspec_item *item, const char *refspec, int fet
 		 */
 		if (!*item->src)
 			return 0; /* negative refspecs must not be empty */
-		else if (llen == the_hash_algo->hexsz && !get_oid_hex(item->src, &unused))
+		else if (the_hash_algo && llen == the_hash_algo->hexsz && !get_oid_hex(item->src, &unused))
 			return 0; /* negative refpsecs cannot be exact sha1 */
 		else if (!check_refname_format(item->src, flags))
 			; /* valid looking ref is ok */
@@ -101,7 +101,7 @@ static int parse_refspec(struct refspec_item *item, const char *refspec, int fet
 		/* LHS */
 		if (!*item->src)
 			; /* empty is ok; it means "HEAD" */
-		else if (llen == the_hash_algo->hexsz && !get_oid_hex(item->src, &unused))
+		else if (the_hash_algo && llen == the_hash_algo->hexsz && !get_oid_hex(item->src, &unused))
 			item->exact_sha1 = 1; /* ok */
 		else if (!check_refname_format(item->src, flags))
 			; /* valid looking ref is ok */
diff --git a/t/t5551-http-fetch-smart.sh b/t/t5551-http-fetch-smart.sh
index 73cf531580..a26b6c2844 100755
--- a/t/t5551-http-fetch-smart.sh
+++ b/t/t5551-http-fetch-smart.sh
@@ -782,4 +782,11 @@ test_expect_success 'tag following always works over v0 http' '
 	test_cmp expect actual
 '
 
+test_expect_success 'ls-remote outside repo does not segfault with fetch refspec' '
+	nongit git \
+		-c remote.origin.url="$HTTPD_URL/smart/repo.git" \
+		-c remote.origin.fetch=anything \
+		ls-remote origin
+'
+
 test_done
-- 
2.53.0
K JayatheerthMar 22, 2026, 05:36 UTC in reply to K Jayatheerth on lore

[PATCH v3 2/2] refspec: fix typo in comment

Fix a long-standing typo in a comment: "refpsecs" -> "refspecs".
Signed-off-by: K Jayatheerth <jayatheerthkulkarni2005@gmail.com>
---
 refspec.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/refspec.c b/refspec.c
index a864a0bac2..a0c9edfbea 100644
--- a/refspec.c
+++ b/refspec.c
@@ -85,7 +85,7 @@ static int parse_refspec(struct refspec_item *item, const char *refspec, int fet
 		if (!*item->src)
 			return 0; /* negative refspecs must not be empty */
 		else if (the_hash_algo && llen == the_hash_algo->hexsz && !get_oid_hex(item->src, &unused))
-			return 0; /* negative refpsecs cannot be exact sha1 */
+			return 0; /* negative refspecs cannot be exact sha1 */
 		else if (!check_refname_format(item->src, flags))
 			; /* valid looking ref is ok */
 		else
-- 
2.53.0
Junio C HamanoMar 23, 2026, 22:27 UTC in reply to K Jayatheerth on lore

Re: [PATCH v3 1/2] refspec: safely parse refspecs outside a repository

K Jayatheerth <jayatheerthkulkarni2005@gmail.com> writes:
Show 28 quoted lines
> When git-remote-http is invoked outside of a repository (for example,
> by running `git ls-remote` in a non-git directory with a globally
> configured fetch refspec), `the_hash_algo` is left as NULL by
> setup_git_directory_gently().
>
> parse_refspec() checks whether the LHS of a refspec is an exact OID by
> evaluating `llen == the_hash_algo->hexsz`. With `the_hash_algo` being
> NULL, this results in a segmentation fault. The same NULL dereference
> exists in the negative refspec path.
>
> Note that builtin/ls-remote already works around a related issue by
> setting a fallback hash algorithm before calling into the transport
> layer (see 9e89dcb66a). However, since remote-curl runs as a separate
> process, that fix does not help here.
>
> Guard both dereferences with a NULL check on `the_hash_algo`. When
> operating outside a repository, fetching and pushing are impossible
> anyway, so skipping the exact OID check is safe: the exact_sha1 flag
> only influences ref prefixes sent to a remote v2 upload-pack during
> fetch, and we will never reach that point without a local repository.
>
> Reported-by: Jo Liss <joliss@gmail.com>
> Helped-by: Jeff King <peff@peff.net>
> Signed-off-by: K Jayatheerth <jayatheerthkulkarni2005@gmail.com>
> ---
>  refspec.c                   | 4 ++--
>  t/t5551-http-fetch-smart.sh | 7 +++++++
>  2 files changed, 9 insertions(+), 2 deletions(-)

Looking good. Shall we declare victory and mark the topic for 'next' by now?

Thanks.
Show 38 quoted lines
> diff --git a/refspec.c b/refspec.c
> index 0775358d96..a864a0bac2 100644
> --- a/refspec.c
> +++ b/refspec.c
> @@ -84,7 +84,7 @@ static int parse_refspec(struct refspec_item *item, const char *refspec, int fet
>  		 */
>  		if (!*item->src)
>  			return 0; /* negative refspecs must not be empty */
> -		else if (llen == the_hash_algo->hexsz && !get_oid_hex(item->src, &unused))
> +		else if (the_hash_algo && llen == the_hash_algo->hexsz && !get_oid_hex(item->src, &unused))
>  			return 0; /* negative refpsecs cannot be exact sha1 */
>  		else if (!check_refname_format(item->src, flags))
>  			; /* valid looking ref is ok */
> @@ -101,7 +101,7 @@ static int parse_refspec(struct refspec_item *item, const char *refspec, int fet
>  		/* LHS */
>  		if (!*item->src)
>  			; /* empty is ok; it means "HEAD" */
> -		else if (llen == the_hash_algo->hexsz && !get_oid_hex(item->src, &unused))
> +		else if (the_hash_algo && llen == the_hash_algo->hexsz && !get_oid_hex(item->src, &unused))
>  			item->exact_sha1 = 1; /* ok */
>  		else if (!check_refname_format(item->src, flags))
>  			; /* valid looking ref is ok */
> diff --git a/t/t5551-http-fetch-smart.sh b/t/t5551-http-fetch-smart.sh
> index 73cf531580..a26b6c2844 100755
> --- a/t/t5551-http-fetch-smart.sh
> +++ b/t/t5551-http-fetch-smart.sh
> @@ -782,4 +782,11 @@ test_expect_success 'tag following always works over v0 http' '
>  	test_cmp expect actual
>  '
>  
> +test_expect_success 'ls-remote outside repo does not segfault with fetch refspec' '
> +	nongit git \
> +		-c remote.origin.url="$HTTPD_URL/smart/repo.git" \
> +		-c remote.origin.fetch=anything \
> +		ls-remote origin
> +'
> +
>  test_done
Jeff KingMar 23, 2026, 23:10 UTC in reply to Junio C Hamano on lore

Re: [PATCH v3 1/2] refspec: safely parse refspecs outside a repository

On Mon, Mar 23, 2026 at 03:27:29PM -0700, Junio C Hamano wrote:
> Looking good.  Shall we declare victory and mark the topic for
> 'next' by now?
I'm not entirely convinced the better solution isn't just:
diff --git a/remote-curl.c b/remote-curl.c
index 92e40bb682..60774af929 100644
--- a/remote-curl.c
+++ b/remote-curl.c
@@ -1552,6 +1552,11 @@ int cmd_main(int argc, const char **argv)
 		goto cleanup;
 	}
 
+	/* yuck, see 9e89dcb66a (builtin/ls-remote: fall back to SHA1 outside
+	 * of a repo, 2024-08-02) */
+	if (nongit)
+		repo_set_hash_algo(the_repository, GIT_HASH_DEFAULT);
+
 	options.verbosity = 1;
 	options.progress = !!isatty(2);
 	options.thin = 1;

That would make the http transport consistent with non-http ones (or at
least any that execute in-process within ls-remote).

Or alternatively, if we think that this use of parse_refspec() is the
only remaining spot for which ls-remote needs a fallback, then we could
apply the patch here and then revert 9e89dcb66a.

-Peff
Junio C HamanoMar 23, 2026, 23:39 UTC in reply to Jeff King on lore

Re: [PATCH v3 1/2] refspec: safely parse refspecs outside a repository

Jeff King <peff@peff.net> writes:
Show 26 quoted lines
> On Mon, Mar 23, 2026 at 03:27:29PM -0700, Junio C Hamano wrote:
>
>> Looking good.  Shall we declare victory and mark the topic for
>> 'next' by now?
>
> I'm not entirely convinced the better solution isn't just:
>
> diff --git a/remote-curl.c b/remote-curl.c
> index 92e40bb682..60774af929 100644
> --- a/remote-curl.c
> +++ b/remote-curl.c
> @@ -1552,6 +1552,11 @@ int cmd_main(int argc, const char **argv)
>  		goto cleanup;
>  	}
>  
> +	/* yuck, see 9e89dcb66a (builtin/ls-remote: fall back to SHA1 outside
> +	 * of a repo, 2024-08-02) */
> +	if (nongit)
> +		repo_set_hash_algo(the_repository, GIT_HASH_DEFAULT);
> +
>  	options.verbosity = 1;
>  	options.progress = !!isatty(2);
>  	options.thin = 1;
>
> That would make the http transport consistent with non-http ones (or at
> least any that execute in-process within ls-remote).
Ah, yes, I like this much better.
Thanks.
K JayatheerthMar 24, 2026, 01:57 UTC in reply to K Jayatheerth on lore

[PATCH v4 1/2] remote-curl: fall back to default hash outside repo

When a remote helper like git-remote-http is invoked outside of a repository (for example, by running git ls-remote in a non-git directory), setup_git_directory_gently() leaves the_hash_algo uninitialized as NULL.

If the user has a globally configured fetch refspec, remote-curl attempts to parse it during initialization. Inside parse_refspec(), it checks whether the LHS of the refspec is an exact OID by evaluating llen == the_hash_algo->hexsz. Because the_hash_algo is NULL, this results in a segmentation fault.

In 9e89dcb66a (builtin/ls-remote: fall back to SHA1 outside of a repo, 2024-08-02), we added a workaround to ls-remote to fall back to the default hash algorithm to prevent exactly this type of crash when parsing refspec capabilities. However, because remote-curl runs as a separate process, it does not inherit that fallback and crashes anyway.

Instead of pushing a NULL-guard workaround down into parse_refspec(), fix this by mirroring the ls-remote workaround directly in remote-curl.c. If we are operating outside a repository, initialize the_hash_algo to GIT_HASH_DEFAULT. This keeps the HTTP transport consistent with non-HTTP transports that execute in-process, preventing crashes without altering the generic refspec parsing logic.

Reported-by: Jo Liss <joliss@gmail.com>
Helped-by: Jeff King <peff@peff.net>
Signed-off-by: K Jayatheerth <jayatheerthkulkarni2005@gmail.com>
---
Thanks Peff and Junio this has been informative.
I understood a lot of things here.
 remote-curl.c               | 5 +++++
 t/t5551-http-fetch-smart.sh | 7 +++++++
 2 files changed, 12 insertions(+)
diff --git a/remote-curl.c b/remote-curl.c
index 92e40bb682..60774af929 100644
--- a/remote-curl.c
+++ b/remote-curl.c
@@ -1552,6 +1552,11 @@ int cmd_main(int argc, const char **argv)
 		goto cleanup;
 	}
 
+	/* yuck, see 9e89dcb66a (builtin/ls-remote: fall back to SHA1 outside
+	 * of a repo, 2024-08-02) */
+	if (nongit)
+		repo_set_hash_algo(the_repository, GIT_HASH_DEFAULT);
+
 	options.verbosity = 1;
 	options.progress = !!isatty(2);
 	options.thin = 1;
diff --git a/t/t5551-http-fetch-smart.sh b/t/t5551-http-fetch-smart.sh
index 73cf531580..a26b6c2844 100755
--- a/t/t5551-http-fetch-smart.sh
+++ b/t/t5551-http-fetch-smart.sh
@@ -782,4 +782,11 @@ test_expect_success 'tag following always works over v0 http' '
 	test_cmp expect actual
 '
 
+test_expect_success 'ls-remote outside repo does not segfault with fetch refspec' '
+	nongit git \
+		-c remote.origin.url="$HTTPD_URL/smart/repo.git" \
+		-c remote.origin.fetch=anything \
+		ls-remote origin
+'
+
 test_done
-- 
2.53.0
K JayatheerthMar 24, 2026, 01:57 UTC in reply to K Jayatheerth on lore

[PATCH v4 2/2] refspec: fix typo in comment

Fix a long-standing typo in a comment: "refpsecs" -> "refspecs".
Signed-off-by: K Jayatheerth <jayatheerthkulkarni2005@gmail.com>
---
 refspec.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/refspec.c b/refspec.c
index 0775358d96..fb89bce1db 100644
--- a/refspec.c
+++ b/refspec.c
@@ -85,7 +85,7 @@ static int parse_refspec(struct refspec_item *item, const char *refspec, int fet
 		if (!*item->src)
 			return 0; /* negative refspecs must not be empty */
 		else if (llen == the_hash_algo->hexsz && !get_oid_hex(item->src, &unused))
-			return 0; /* negative refpsecs cannot be exact sha1 */
+			return 0; /* negative refspecs cannot be exact sha1 */
 		else if (!check_refname_format(item->src, flags))
 			; /* valid looking ref is ok */
 		else
-- 
2.53.0
Junio C HamanoMar 24, 2026, 04:25 UTC in reply to K Jayatheerth on lore

Re: [PATCH v4 1/2] remote-curl: fall back to default hash outside repo

K Jayatheerth <jayatheerthkulkarni2005@gmail.com> writes:
Show 6 quoted lines
> Instead of pushing a NULL-guard workaround down into parse_refspec(),
> fix this by mirroring the ls-remote workaround directly in
> remote-curl.c. If we are operating outside a repository, initialize
> the_hash_algo to GIT_HASH_DEFAULT. This keeps the HTTP transport
> consistent with non-HTTP transports that execute in-process, preventing
> crashes without altering the generic refspec parsing logic.
Thanks.
Show 44 quoted lines
>
> Reported-by: Jo Liss <joliss@gmail.com>
> Helped-by: Jeff King <peff@peff.net>
> Signed-off-by: K Jayatheerth <jayatheerthkulkarni2005@gmail.com>
> ---
> Thanks Peff and Junio this has been informative.
> I understood a lot of things here.
>
>  remote-curl.c               | 5 +++++
>  t/t5551-http-fetch-smart.sh | 7 +++++++
>  2 files changed, 12 insertions(+)
>
> diff --git a/remote-curl.c b/remote-curl.c
> index 92e40bb682..60774af929 100644
> --- a/remote-curl.c
> +++ b/remote-curl.c
> @@ -1552,6 +1552,11 @@ int cmd_main(int argc, const char **argv)
>  		goto cleanup;
>  	}
>  
> +	/* yuck, see 9e89dcb66a (builtin/ls-remote: fall back to SHA1 outside
> +	 * of a repo, 2024-08-02) */
> +	if (nongit)
> +		repo_set_hash_algo(the_repository, GIT_HASH_DEFAULT);
> +
>  	options.verbosity = 1;
>  	options.progress = !!isatty(2);
>  	options.thin = 1;
> diff --git a/t/t5551-http-fetch-smart.sh b/t/t5551-http-fetch-smart.sh
> index 73cf531580..a26b6c2844 100755
> --- a/t/t5551-http-fetch-smart.sh
> +++ b/t/t5551-http-fetch-smart.sh
> @@ -782,4 +782,11 @@ test_expect_success 'tag following always works over v0 http' '
>  	test_cmp expect actual
>  '
>  
> +test_expect_success 'ls-remote outside repo does not segfault with fetch refspec' '
> +	nongit git \
> +		-c remote.origin.url="$HTTPD_URL/smart/repo.git" \
> +		-c remote.origin.fetch=anything \
> +		ls-remote origin
> +'
> +
>  test_done

Back to recent threads