Skip to content

Commit 15c6308

Browse files
committed
Merge tag 'v2.55.0.windows.4'
Git for Windows v2.55.0(4) Changes since Git for Windows v2.55.0(3) (July 14th 2026): Following the MSYS2 project, on which Git for Windows is based, Windows 8.1 support will be dropped after Git for Windows v2.55. This is a security fix release, addressing CVE-2026-62960. * CVE-2026-62960, Git for Windows: Attacker-controlled servers may advertise bundle URIs that point to network shares, causing Windows to transparently perform NTLM authentication and disclose the user's NTLMv2 hash. Since NTLM hashing is weak, the captured hash can potentially be brute-forced to recover the user's credentials. This is addressed by limiting the bundle URIs that git clone respects by the same protocol.<name>.allow rules as usual, which excludes file:// URIs by default. Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
2 parents cf5497b + a935247 commit 15c6308

2 files changed

Lines changed: 106 additions & 0 deletions

File tree

bundle-uri.c

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@
1515
#include "remote.h"
1616
#include "trace2.h"
1717
#include "odb.h"
18+
#include "transport.h"
19+
#include "url.h"
1820

1921
static struct {
2022
enum bundle_list_heuristic heuristic;
@@ -890,11 +892,59 @@ int fetch_bundle_uri(struct repository *r, const char *uri,
890892
return result;
891893
}
892894

895+
/* protocol of 'uri', or "file" if it has none (bare/UNC/relative path) */
896+
static void bundle_uri_protocol(const char *uri, struct strbuf *out)
897+
{
898+
const char *p = uri;
899+
900+
while (is_urlschemechar(p == uri, *p))
901+
p++;
902+
strbuf_reset(out);
903+
if (p > uri && starts_with(p, "://"))
904+
strbuf_add(out, uri, p - uri);
905+
else
906+
strbuf_addstr(out, "file");
907+
}
908+
909+
/* Drop advertised URIs whose protocol is not allowed (see protocol.*.allow). */
910+
static void sanitize_bundle_list(struct bundle_list *list)
911+
{
912+
struct remote_bundle_info **skipped;
913+
size_t nr = 0, i;
914+
struct remote_bundle_info *info;
915+
struct hashmap_iter iter;
916+
struct strbuf proto = STRBUF_INIT;
917+
918+
ALLOC_ARRAY(skipped, hashmap_get_size(&list->bundles));
919+
hashmap_for_each_entry(&list->bundles, &iter, info, ent) {
920+
if (!info->uri)
921+
continue;
922+
bundle_uri_protocol(info->uri, &proto);
923+
/* advertised URIs are not user-provided */
924+
if (!is_transport_allowed(proto.buf, 0)) {
925+
warning(_("skipping bundle URI '%s': protocol '%s' "
926+
"is not allowed"), info->uri, proto.buf);
927+
skipped[nr++] = info;
928+
}
929+
}
930+
strbuf_release(&proto);
931+
932+
for (i = 0; i < nr; i++) {
933+
hashmap_remove(&list->bundles, &skipped[i]->ent, NULL);
934+
clear_remote_bundle_info(skipped[i], NULL);
935+
free(skipped[i]);
936+
}
937+
938+
free(skipped);
939+
}
940+
893941
int fetch_bundle_list(struct repository *r, struct bundle_list *list)
894942
{
895943
int result;
896944
struct bundle_list global_list;
897945

946+
sanitize_bundle_list(list);
947+
898948
/*
899949
* If the creationToken heuristic is used, then the URIs
900950
* advertised by 'list' are not nested lists and instead

t/lib-bundle-uri-protocol.sh

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -214,3 +214,59 @@ test_expect_success "test bundle-uri with $BUNDLE_URI_PROTOCOL:// using protocol
214214
>actual &&
215215
test_cmp_config_output expect actual
216216
'
217+
218+
# Advertised bundle URIs are subject to protocol.*.allow; "file" (and bare or
219+
# UNC paths) is denied by default, so such a URI must be skipped, not fetched.
220+
advertise_uri () {
221+
test_config -C "$BUNDLE_URI_PARENT" bundle.version 1 &&
222+
test_config -C "$BUNDLE_URI_PARENT" bundle.mode all &&
223+
test_config -C "$BUNDLE_URI_PARENT" bundle.payload.uri "$1"
224+
}
225+
226+
ignores_advertised_uri () {
227+
rm -rf victim &&
228+
advertise_uri "$1" &&
229+
git -c transfer.bundleURI=true -c protocol.version=2 \
230+
clone "$BUNDLE_URI_REPO_URI" victim &&
231+
git -C victim for-each-ref refs/bundles/ >refs &&
232+
test_must_be_empty refs
233+
}
234+
235+
test_expect_success "create bundle to advertise" '
236+
git -C "$BUNDLE_URI_PARENT" bundle create "$PWD/payload.bundle" main
237+
'
238+
239+
test_expect_success "ignore non-HTTP(S) bundle URI with $BUNDLE_URI_PROTOCOL://" '
240+
ignores_advertised_uri "$PWD/payload.bundle" &&
241+
ignores_advertised_uri "file://$PWD/payload.bundle"
242+
'
243+
244+
test_expect_success "protocol.file.allow=always honors file bundle URI with $BUNDLE_URI_PROTOCOL://" '
245+
rm -rf victim &&
246+
advertise_uri "$PWD/payload.bundle" &&
247+
git -c transfer.bundleURI=true -c protocol.version=2 \
248+
-c protocol.file.allow=always \
249+
clone "$BUNDLE_URI_REPO_URI" victim &&
250+
git -C victim rev-parse --verify refs/bundles/heads/main
251+
'
252+
253+
# same path via a UNC administrative share (cf. t5580-unc-paths.sh)
254+
if test_have_prereq CYGWIN
255+
then
256+
UNCPATH="$(cygpath -aw .)"
257+
elif test_have_prereq MINGW
258+
then
259+
UNCPATH="$(pwd)"
260+
fi
261+
case "$UNCPATH" in
262+
[A-Za-z]:*)
263+
WITHOUTDRIVE="${UNCPATH#?:}"
264+
UNCPATH="//localhost/${UNCPATH%%:*}\$$WITHOUTDRIVE"
265+
test -d "$UNCPATH" && test_set_prereq ADMIN_UNC
266+
;;
267+
esac
268+
269+
test_expect_success ADMIN_UNC "ignore UNC bundle URI with $BUNDLE_URI_PROTOCOL://" '
270+
ignores_advertised_uri "$UNCPATH/payload.bundle" &&
271+
ignores_advertised_uri "file://$UNCPATH/payload.bundle"
272+
'

0 commit comments

Comments
 (0)