All of lore.kernel.org
 help / color / mirror / Atom feed
* [PATCH] bundle-uri: refuse advertised URIs by protocol
@ 2026-08-12 15:55 Johannes Schindelin via GitGitGadget
  0 siblings, 0 replies; only message in thread
From: Johannes Schindelin via GitGitGadget @ 2026-08-12 15:55 UTC (permalink / raw)
  To: git; +Cc: Johannes Schindelin, Johannes Schindelin

From: Johannes Schindelin <johannes.schindelin@gmx.de>

Servers may advertise bundle URIs that are not HTTP(S);
copy_uri_to_file() then opens it as a local path. On Windows that can be
a UNC path like `//attacker/share/x`, i.e. a clone can be manipulated
into making an outbound SMB connection that leaks NTLM credentials
(CVE-2026-62960).

Subject advertised URIs to the usual protocol allow-list
(`protocol.*.allow`), which drops "file" (and bare/UNC paths) by default
but keeps http/https/git/ssh. Do it in fetch_bundle_list(), the
clone/fetch consume path, so ls-remote still lists everything; each
skipped URI is reported. A user-supplied `--bundle-uri` is unaffected,
and `protocol.file.allow=always` re-enables an advertised file URI.

Assisted-by: Opus 4.7
Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
---
    bundle-uri: refuse advertised URIs by protocol
    
    This is the security fix released with Git for Windows v2.55.0(4). Due
    to the transparent NTLM authentication ("SSPI"), the vulnerability
    affects only Windows. The patch has been sent to the git-security list
    on June 26th, 2026, but only received reviews in the PR in
    https://github.com/git-for-windows/git/security/advisories/GHSA-xrpg-8j9v-v282's
    private fork (which had to be deleted so that the advisory could be
    published).

Published-As: https://github.com/gitgitgadget/git/releases/tag/pr-2200%2Fdscho%2Frespect-allowed-protocols-in-bundle-uris-v1
Fetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-2200/dscho/respect-allowed-protocols-in-bundle-uris-v1
Pull-Request: https://github.com/gitgitgadget/git/pull/2200

 bundle-uri.c                 | 50 ++++++++++++++++++++++++++++++++
 t/lib-bundle-uri-protocol.sh | 56 ++++++++++++++++++++++++++++++++++++
 2 files changed, 106 insertions(+)

diff --git a/bundle-uri.c b/bundle-uri.c
index 2bb2eb99e4..92a36ca0ab 100644
--- a/bundle-uri.c
+++ b/bundle-uri.c
@@ -15,6 +15,8 @@
 #include "remote.h"
 #include "trace2.h"
 #include "odb.h"
+#include "transport.h"
+#include "url.h"
 
 static struct {
 	enum bundle_list_heuristic heuristic;
@@ -890,11 +892,59 @@ cleanup:
 	return result;
 }
 
+/* protocol of 'uri', or "file" if it has none (bare/UNC/relative path) */
+static void bundle_uri_protocol(const char *uri, struct strbuf *out)
+{
+	const char *p = uri;
+
+	while (is_urlschemechar(p == uri, *p))
+		p++;
+	strbuf_reset(out);
+	if (p > uri && starts_with(p, "://"))
+		strbuf_add(out, uri, p - uri);
+	else
+		strbuf_addstr(out, "file");
+}
+
+/* Drop advertised URIs whose protocol is not allowed (see protocol.*.allow). */
+static void sanitize_bundle_list(struct bundle_list *list)
+{
+	struct remote_bundle_info **skipped;
+	size_t nr = 0, i;
+	struct remote_bundle_info *info;
+	struct hashmap_iter iter;
+	struct strbuf proto = STRBUF_INIT;
+
+	ALLOC_ARRAY(skipped, hashmap_get_size(&list->bundles));
+	hashmap_for_each_entry(&list->bundles, &iter, info, ent) {
+		if (!info->uri)
+			continue;
+		bundle_uri_protocol(info->uri, &proto);
+		/* advertised URIs are not user-provided */
+		if (!is_transport_allowed(proto.buf, 0)) {
+			warning(_("skipping bundle URI '%s': protocol '%s' "
+				  "is not allowed"), info->uri, proto.buf);
+			skipped[nr++] = info;
+		}
+	}
+	strbuf_release(&proto);
+
+	for (i = 0; i < nr; i++) {
+		hashmap_remove(&list->bundles, &skipped[i]->ent, NULL);
+		clear_remote_bundle_info(skipped[i], NULL);
+		free(skipped[i]);
+	}
+
+	free(skipped);
+}
+
 int fetch_bundle_list(struct repository *r, struct bundle_list *list)
 {
 	int result;
 	struct bundle_list global_list;
 
+	sanitize_bundle_list(list);
+
 	/*
 	 * If the creationToken heuristic is used, then the URIs
 	 * advertised by 'list' are not nested lists and instead
diff --git a/t/lib-bundle-uri-protocol.sh b/t/lib-bundle-uri-protocol.sh
index 794478ae19..889e673a44 100644
--- a/t/lib-bundle-uri-protocol.sh
+++ b/t/lib-bundle-uri-protocol.sh
@@ -237,3 +237,59 @@ test_expect_success "test bundle-uri with $BUNDLE_URI_PROTOCOL:// using protocol
 		>actual &&
 	test_cmp_config_output expect actual
 '
+
+# Advertised bundle URIs are subject to protocol.*.allow; "file" (and bare or
+# UNC paths) is denied by default, so such a URI must be skipped, not fetched.
+advertise_uri () {
+	test_config -C "$BUNDLE_URI_PARENT" bundle.version 1 &&
+	test_config -C "$BUNDLE_URI_PARENT" bundle.mode all &&
+	test_config -C "$BUNDLE_URI_PARENT" bundle.payload.uri "$1"
+}
+
+ignores_advertised_uri () {
+	rm -rf victim &&
+	advertise_uri "$1" &&
+	git -c transfer.bundleURI=true -c protocol.version=2 \
+		clone "$BUNDLE_URI_REPO_URI" victim &&
+	git -C victim for-each-ref refs/bundles/ >refs &&
+	test_must_be_empty refs
+}
+
+test_expect_success "create bundle to advertise" '
+	git -C "$BUNDLE_URI_PARENT" bundle create "$PWD/payload.bundle" main
+'
+
+test_expect_success "ignore non-HTTP(S) bundle URI with $BUNDLE_URI_PROTOCOL://" '
+	ignores_advertised_uri "$PWD/payload.bundle" &&
+	ignores_advertised_uri "file://$PWD/payload.bundle"
+'
+
+test_expect_success "protocol.file.allow=always honors file bundle URI with $BUNDLE_URI_PROTOCOL://" '
+	rm -rf victim &&
+	advertise_uri "$PWD/payload.bundle" &&
+	git -c transfer.bundleURI=true -c protocol.version=2 \
+		-c protocol.file.allow=always \
+		clone "$BUNDLE_URI_REPO_URI" victim &&
+	git -C victim rev-parse --verify refs/bundles/heads/main
+'
+
+# same path via a UNC administrative share (cf. t5580-unc-paths.sh)
+if test_have_prereq CYGWIN
+then
+	UNCPATH="$(cygpath -aw .)"
+elif test_have_prereq MINGW
+then
+	UNCPATH="$(pwd)"
+fi
+case "$UNCPATH" in
+[A-Za-z]:*)
+	WITHOUTDRIVE="${UNCPATH#?:}"
+	UNCPATH="//localhost/${UNCPATH%%:*}\$$WITHOUTDRIVE"
+	test -d "$UNCPATH" && test_set_prereq ADMIN_UNC
+	;;
+esac
+
+test_expect_success ADMIN_UNC "ignore UNC bundle URI with $BUNDLE_URI_PROTOCOL://" '
+	ignores_advertised_uri "$UNCPATH/payload.bundle" &&
+	ignores_advertised_uri "file://$UNCPATH/payload.bundle"
+'

base-commit: 11c6700f10234578d10523faf35656ca491425c9
-- 
gitgitgadget

^ permalink raw reply related	[flat|nested] only message in thread

only message in thread, other threads:[~2026-08-12 15:55 UTC | newest]

Thread overview: (only message) (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-08-12 15:55 [PATCH] bundle-uri: refuse advertised URIs by protocol Johannes Schindelin via GitGitGadget

This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.