summaryrefslogtreecommitdiffstats
path: root/debian/patches
diff options
context:
space:
mode:
Diffstat (limited to 'debian/patches')
-rw-r--r--debian/patches/CVE-2022-23521.patch280
-rw-r--r--debian/patches/CVE-2022-24765.patch499
-rw-r--r--debian/patches/CVE-2022-29187.patch340
-rw-r--r--debian/patches/CVE-2022-39253.patch969
-rw-r--r--debian/patches/CVE-2022-39260.patch148
-rw-r--r--debian/patches/CVE-2022-41903.patch445
-rw-r--r--debian/patches/CVE-2023-22490-1.patch174
-rw-r--r--debian/patches/CVE-2023-22490-2.patch117
-rw-r--r--debian/patches/CVE-2023-22490-3.patch150
-rw-r--r--debian/patches/CVE-2023-23946.patch179
-rw-r--r--debian/patches/series10
11 files changed, 3311 insertions, 0 deletions
diff --git a/debian/patches/CVE-2022-23521.patch b/debian/patches/CVE-2022-23521.patch
new file mode 100644
index 0000000..1a021fc
--- /dev/null
+++ b/debian/patches/CVE-2022-23521.patch
@@ -0,0 +1,280 @@
+Origin: https://github.com/git/git/commit/eb22e7dfa23da6bd9aed9bd1dad69e1e8e167d24
+Origin: https://github.com/git/git/commit/8d0d48cf2157cfb914db1f53b3fe40785b86f3aa
+Origin: https://github.com/git/git/commit/24557209500e6ed618f04a8795a111a0c491a29c
+Origin: https://github.com/git/git/commit/34ace8bad02bb14ecc5b631f7e3daaa7a9bba7d9
+Origin: https://github.com/git/git/commit/447ac906e189535e77dcb1f4bbe3f1bc917d4c12
+Origin: https://github.com/git/git/commit/e1e12e97ac73ded85f7d000da1063a774b3cc14f
+Origin: https://github.com/git/git/commit/a60a66e409c265b2944f18bf43581c146812586d
+Origin: https://github.com/git/git/commit/d74b1fd54fdbc45966d12ea907dece11e072fb2b
+Origin: https://github.com/git/git/commit/dfa6b32b5e599d97448337ed4fc18dd50c90758f
+Origin: https://github.com/git/git/commit/3c50032ff5289cc45659f21949c8d09e52164579
+Reviewed-by: Aron Xu <aron@debian.org>
+Last-Updated: 2023-01-26
+
+diff --git a/attr.c b/attr.c
+index 4ef85d6..38ecd2f 100644
+--- a/attr.c
++++ b/attr.c
+@@ -28,7 +28,7 @@ static const char git_attr__unknown[] = "(builtin)unknown";
+ #endif
+
+ struct git_attr {
+- int attr_nr; /* unique attribute number */
++ unsigned int attr_nr; /* unique attribute number */
+ char name[FLEX_ARRAY]; /* attribute name */
+ };
+
+@@ -210,7 +210,7 @@ static void report_invalid_attr(const char *name, size_t len,
+ * dictionary. If no entry is found, create a new attribute and store it in
+ * the dictionary.
+ */
+-static const struct git_attr *git_attr_internal(const char *name, int namelen)
++static const struct git_attr *git_attr_internal(const char *name, size_t namelen)
+ {
+ struct git_attr *a;
+
+@@ -226,8 +226,8 @@ static const struct git_attr *git_attr_internal(const char *name, int namelen)
+ a->attr_nr = hashmap_get_size(&g_attr_hashmap.map);
+
+ attr_hashmap_add(&g_attr_hashmap, a->name, namelen, a);
+- assert(a->attr_nr ==
+- (hashmap_get_size(&g_attr_hashmap.map) - 1));
++ if (a->attr_nr != hashmap_get_size(&g_attr_hashmap.map) - 1)
++ die(_("unable to add additional attribute"));
+ }
+
+ hashmap_unlock(&g_attr_hashmap);
+@@ -272,7 +272,7 @@ struct match_attr {
+ const struct git_attr *attr;
+ } u;
+ char is_macro;
+- unsigned num_attr;
++ size_t num_attr;
+ struct attr_state state[FLEX_ARRAY];
+ };
+
+@@ -289,7 +289,7 @@ static const char *parse_attr(const char *src, int lineno, const char *cp,
+ struct attr_state *e)
+ {
+ const char *ep, *equals;
+- int len;
++ size_t len;
+
+ ep = cp + strcspn(cp, blank);
+ equals = strchr(cp, '=');
+@@ -333,8 +333,7 @@ static const char *parse_attr(const char *src, int lineno, const char *cp,
+ static struct match_attr *parse_attr_line(const char *line, const char *src,
+ int lineno, int macro_ok)
+ {
+- int namelen;
+- int num_attr, i;
++ size_t namelen, num_attr, i;
+ const char *cp, *name, *states;
+ struct match_attr *res = NULL;
+ int is_macro;
+@@ -345,6 +344,11 @@ static struct match_attr *parse_attr_line(const char *line, const char *src,
+ return NULL;
+ name = cp;
+
++ if (strlen(line) >= ATTR_MAX_LINE_LENGTH) {
++ warning(_("ignoring overly long attributes line %d"), lineno);
++ return NULL;
++ }
++
+ if (*cp == '"' && !unquote_c_style(&pattern, name, &states)) {
+ name = pattern.buf;
+ namelen = pattern.len;
+@@ -381,10 +385,9 @@ static struct match_attr *parse_attr_line(const char *line, const char *src,
+ goto fail_return;
+ }
+
+- res = xcalloc(1,
+- sizeof(*res) +
+- sizeof(struct attr_state) * num_attr +
+- (is_macro ? 0 : namelen + 1));
++ res = xcalloc(1, st_add3(sizeof(*res),
++ st_mult(sizeof(struct attr_state), num_attr),
++ is_macro ? 0 : namelen + 1));
+ if (is_macro) {
+ res->u.attr = git_attr_internal(name, namelen);
+ } else {
+@@ -447,11 +450,12 @@ struct attr_stack {
+
+ static void attr_stack_free(struct attr_stack *e)
+ {
+- int i;
++ unsigned i;
+ free(e->origin);
+ for (i = 0; i < e->num_matches; i++) {
+ struct match_attr *a = e->attrs[i];
+- int j;
++ size_t j;
++
+ for (j = 0; j < a->num_attr; j++) {
+ const char *setto = a->state[j].setto;
+ if (setto == ATTR__TRUE ||
+@@ -660,8 +664,8 @@ static void handle_attr_line(struct attr_stack *res,
+ a = parse_attr_line(line, src, lineno, macro_ok);
+ if (!a)
+ return;
+- ALLOC_GROW(res->attrs, res->num_matches + 1, res->alloc);
+- res->attrs[res->num_matches++] = a;
++ ALLOC_GROW_BY(res->attrs, res->num_matches, 1, res->alloc);
++ res->attrs[res->num_matches - 1] = a;
+ }
+
+ static struct attr_stack *read_attr_from_array(const char **list)
+@@ -700,21 +704,22 @@ void git_attr_set_direction(enum git_attr_direction new_direction)
+
+ static struct attr_stack *read_attr_from_file(const char *path, int macro_ok)
+ {
++ struct strbuf buf = STRBUF_INIT;
+ FILE *fp = fopen_or_warn(path, "r");
+ struct attr_stack *res;
+- char buf[2048];
+ int lineno = 0;
+
+ if (!fp)
+ return NULL;
+ res = xcalloc(1, sizeof(*res));
+- while (fgets(buf, sizeof(buf), fp)) {
+- char *bufp = buf;
+- if (!lineno)
+- skip_utf8_bom(&bufp, strlen(bufp));
+- handle_attr_line(res, bufp, path, ++lineno, macro_ok);
++ while (strbuf_getline(&buf, fp) != EOF) {
++ if (!lineno && starts_with(buf.buf, utf8_bom))
++ strbuf_remove(&buf, 0, strlen(utf8_bom));
++ handle_attr_line(res, buf.buf, path, ++lineno, macro_ok);
+ }
++
+ fclose(fp);
++ strbuf_release(&buf);
+ return res;
+ }
+
+@@ -1001,12 +1006,12 @@ static int macroexpand_one(struct all_attrs_item *all_attrs, int nr, int rem);
+ static int fill_one(const char *what, struct all_attrs_item *all_attrs,
+ const struct match_attr *a, int rem)
+ {
+- int i;
++ size_t i;
+
+- for (i = a->num_attr - 1; rem > 0 && i >= 0; i--) {
+- const struct git_attr *attr = a->state[i].attr;
++ for (i = a->num_attr; rem > 0 && i > 0; i--) {
++ const struct git_attr *attr = a->state[i - 1].attr;
+ const char **n = &(all_attrs[attr->attr_nr].value);
+- const char *v = a->state[i].setto;
++ const char *v = a->state[i - 1].setto;
+
+ if (*n == ATTR__UNKNOWN) {
+ debug_set(what,
+@@ -1025,11 +1030,11 @@ static int fill(const char *path, int pathlen, int basename_offset,
+ struct all_attrs_item *all_attrs, int rem)
+ {
+ for (; rem > 0 && stack; stack = stack->prev) {
+- int i;
++ unsigned i;
+ const char *base = stack->origin ? stack->origin : "";
+
+- for (i = stack->num_matches - 1; 0 < rem && 0 <= i; i--) {
+- const struct match_attr *a = stack->attrs[i];
++ for (i = stack->num_matches; 0 < rem && 0 < i; i--) {
++ const struct match_attr *a = stack->attrs[i - 1];
+ if (a->is_macro)
+ continue;
+ if (path_matches(path, pathlen, basename_offset,
+@@ -1060,11 +1065,11 @@ static void determine_macros(struct all_attrs_item *all_attrs,
+ const struct attr_stack *stack)
+ {
+ for (; stack; stack = stack->prev) {
+- int i;
+- for (i = stack->num_matches - 1; i >= 0; i--) {
+- const struct match_attr *ma = stack->attrs[i];
++ unsigned i;
++ for (i = stack->num_matches; i > 0; i--) {
++ const struct match_attr *ma = stack->attrs[i - 1];
+ if (ma->is_macro) {
+- int n = ma->u.attr->attr_nr;
++ unsigned int n = ma->u.attr->attr_nr;
+ if (!all_attrs[n].macro) {
+ all_attrs[n].macro = ma;
+ }
+@@ -1116,7 +1121,7 @@ void git_check_attr(const struct index_state *istate,
+ collect_some_attrs(istate, path, check);
+
+ for (i = 0; i < check->nr; i++) {
+- size_t n = check->items[i].attr->attr_nr;
++ unsigned int n = check->items[i].attr->attr_nr;
+ const char *value = check->all_attrs[n].value;
+ if (value == ATTR__UNKNOWN)
+ value = ATTR__UNSET;
+diff --git a/attr.h b/attr.h
+index 404548f..df9a75d 100644
+--- a/attr.h
++++ b/attr.h
+@@ -107,6 +107,12 @@
+ * - Free the `attr_check` struct by calling `attr_check_free()`.
+ */
+
++/**
++ * The maximum line length for a gitattributes file. If the line exceeds this
++ * length we will ignore it.
++ */
++#define ATTR_MAX_LINE_LENGTH 2048
++
+ struct index_state;
+
+ /**
+diff --git a/t/t0003-attributes.sh b/t/t0003-attributes.sh
+index b660593..7d68e6a 100755
+--- a/t/t0003-attributes.sh
++++ b/t/t0003-attributes.sh
+@@ -339,4 +339,46 @@ test_expect_success 'query binary macro directly' '
+ test_cmp expect actual
+ '
+
++test_expect_success 'large attributes line ignored in tree' '
++ test_when_finished "rm .gitattributes" &&
++ printf "path %02043d" 1 >.gitattributes &&
++ git check-attr --all path >actual 2>err &&
++ echo "warning: ignoring overly long attributes line 1" >expect &&
++ test_cmp expect err &&
++ test_must_be_empty actual
++'
++
++test_expect_success 'large attributes line ignores trailing content in tree' '
++ test_when_finished "rm .gitattributes" &&
++ # older versions of Git broke lines at 2048 bytes; the 2045 bytes
++ # of 0-padding here is accounting for the three bytes of "a 1", which
++ # would knock "trailing" to the "next" line, where it would be
++ # erroneously parsed.
++ printf "a %02045dtrailing attribute\n" 1 >.gitattributes &&
++ git check-attr --all trailing >actual 2>err &&
++ echo "warning: ignoring overly long attributes line 1" >expect &&
++ test_cmp expect err &&
++ test_must_be_empty actual
++'
++
++test_expect_success 'large attributes line ignored in index' '
++ test_when_finished "git update-index --remove .gitattributes" &&
++ blob=$(printf "path %02043d" 1 | git hash-object -w --stdin) &&
++ git update-index --add --cacheinfo 100644,$blob,.gitattributes &&
++ git check-attr --cached --all path >actual 2>err &&
++ echo "warning: ignoring overly long attributes line 1" >expect &&
++ test_cmp expect err &&
++ test_must_be_empty actual
++'
++
++test_expect_success 'large attributes line ignores trailing content in index' '
++ test_when_finished "git update-index --remove .gitattributes" &&
++ blob=$(printf "a %02045dtrailing attribute\n" 1 | git hash-object -w --stdin) &&
++ git update-index --add --cacheinfo 100644,$blob,.gitattributes &&
++ git check-attr --cached --all trailing >actual 2>err &&
++ echo "warning: ignoring overly long attributes line 1" >expect &&
++ test_cmp expect err &&
++ test_must_be_empty actual
++'
++
+ test_done
diff --git a/debian/patches/CVE-2022-24765.patch b/debian/patches/CVE-2022-24765.patch
new file mode 100644
index 0000000..f62a327
--- /dev/null
+++ b/debian/patches/CVE-2022-24765.patch
@@ -0,0 +1,499 @@
+Origin: https://github.com/git/git/commit/6e7ad1e4c22e7038975ba37c7413374fe566b064
+Origin: https://github.com/git/git/commit/bdc77d1d685be9c10b88abb281a42bc620548595
+Origin: https://github.com/git/git/commit/8959555cee7ec045958f9b6dd62e541affb7e7d9
+Origin: https://github.com/git/git/commit/cb95038137e9e66fc6a6b4a0e8db62bcc521b709
+Origin: https://github.com/git/git/commit/e47363e5a8bdf5144059d664c45c0975243ef05b
+Origin: https://github.com/git/git/commit/bb50ec3cc300eeff3aba7a2bea145aabdb477d31
+Origin: https://github.com/git/git/commit/0f85c4a30b072a26d74af8bbf63cc8f6a5dfc1b8
+Reviewed-by: Aron Xu <aron@debian.org>
+Last-Updated: 2023-01-26
+Comment: With the above patches, we are effectively updating the package to v2.30.3
+ plus some more changes. The symlink change to RelNotes has been dropped.
+
+diff --git a/Documentation/RelNotes/2.30.3.txt b/Documentation/RelNotes/2.30.3.txt
+new file mode 100644
+index 0000000..31b2a4d
+--- /dev/null
++++ b/Documentation/RelNotes/2.30.3.txt
+@@ -0,0 +1,24 @@
++Git v2.30.2 Release Notes
++=========================
++
++This release addresses the security issue CVE-2022-24765.
++
++Fixes since v2.30.2
++-------------------
++
++ * Build fix on Windows.
++
++ * Fix `GIT_CEILING_DIRECTORIES` with Windows-style root directories.
++
++ * CVE-2022-24765:
++ On multi-user machines, Git users might find themselves
++ unexpectedly in a Git worktree, e.g. when another user created a
++ repository in `C:\.git`, in a mounted network drive or in a
++ scratch space. Merely having a Git-aware prompt that runs `git
++ status` (or `git diff`) and navigating to a directory which is
++ supposedly not a Git worktree, or opening such a directory in an
++ editor or IDE such as VS Code or Atom, will potentially run
++ commands defined by that other user.
++
++Credit for finding this vulnerability goes to 俞晨东; The fix was
++authored by Johannes Schindelin.
+diff --git a/Documentation/config.txt b/Documentation/config.txt
+index 6ba50b1..34e6d47 100644
+--- a/Documentation/config.txt
++++ b/Documentation/config.txt
+@@ -438,6 +438,8 @@ include::config/rerere.txt[]
+
+ include::config/reset.txt[]
+
++include::config/safe.txt[]
++
+ include::config/sendemail.txt[]
+
+ include::config/sequencer.txt[]
+diff --git a/Documentation/config/safe.txt b/Documentation/config/safe.txt
+new file mode 100644
+index 0000000..6d764fe
+--- /dev/null
++++ b/Documentation/config/safe.txt
+@@ -0,0 +1,28 @@
++safe.directory::
++ These config entries specify Git-tracked directories that are
++ considered safe even if they are owned by someone other than the
++ current user. By default, Git will refuse to even parse a Git
++ config of a repository owned by someone else, let alone run its
++ hooks, and this config setting allows users to specify exceptions,
++ e.g. for intentionally shared repositories (see the `--shared`
++ option in linkgit:git-init[1]).
+++
++This is a multi-valued setting, i.e. you can add more than one directory
++via `git config --add`. To reset the list of safe directories (e.g. to
++override any such directories specified in the system config), add a
++`safe.directory` entry with an empty value.
+++
++This config setting is only respected when specified in a system or global
++config, not when it is specified in a repository config or via the command
++line option `-c safe.directory=<path>`.
+++
++The value of this setting is interpolated, i.e. `~/<path>` expands to a
++path relative to the home directory and `%(prefix)/<path>` expands to a
++path relative to Git's (runtime) prefix.
+++
++To completely opt-out of this security check, set `safe.directory` to the
++string `*`. This will allow all repositories to be treated as if their
++directory was listed in the `safe.directory` list. If `safe.directory=*`
++is set in system config and you want to re-enable this protection, then
++initialize your list with an empty value before listing the repositories
++that you deem safe.
+diff --git a/GIT-VERSION-GEN b/GIT-VERSION-GEN
+index 9d789e0..7cf68be 100755
+--- a/GIT-VERSION-GEN
++++ b/GIT-VERSION-GEN
+@@ -1,7 +1,7 @@
+ #!/bin/sh
+
+ GVF=GIT-VERSION-FILE
+-DEF_VER=v2.30.2
++DEF_VER=v2.30.3
+
+ LF='
+ '
+diff --git a/compat/mingw.c b/compat/mingw.c
+index a435998..38ac359 100644
+--- a/compat/mingw.c
++++ b/compat/mingw.c
+@@ -1,5 +1,6 @@
+ #include "../git-compat-util.h"
+ #include "win32.h"
++#include <aclapi.h>
+ #include <conio.h>
+ #include <wchar.h>
+ #include "../strbuf.h"
+@@ -1060,6 +1061,7 @@ int pipe(int filedes[2])
+ return 0;
+ }
+
++#ifndef __MINGW64__
+ struct tm *gmtime_r(const time_t *timep, struct tm *result)
+ {
+ if (gmtime_s(result, timep) == 0)
+@@ -1073,6 +1075,7 @@ struct tm *localtime_r(const time_t *timep, struct tm *result)
+ return result;
+ return NULL;
+ }
++#endif
+
+ char *mingw_getcwd(char *pointer, int len)
+ {
+@@ -2599,6 +2602,92 @@ static void setup_windows_environment(void)
+ }
+ }
+
++static PSID get_current_user_sid(void)
++{
++ HANDLE token;
++ DWORD len = 0;
++ PSID result = NULL;
++
++ if (!OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY, &token))
++ return NULL;
++
++ if (!GetTokenInformation(token, TokenUser, NULL, 0, &len)) {
++ TOKEN_USER *info = xmalloc((size_t)len);
++ if (GetTokenInformation(token, TokenUser, info, len, &len)) {
++ len = GetLengthSid(info->User.Sid);
++ result = xmalloc(len);
++ if (!CopySid(len, result, info->User.Sid)) {
++ error(_("failed to copy SID (%ld)"),
++ GetLastError());
++ FREE_AND_NULL(result);
++ }
++ }
++ FREE_AND_NULL(info);
++ }
++ CloseHandle(token);
++
++ return result;
++}
++
++int is_path_owned_by_current_sid(const char *path)
++{
++ WCHAR wpath[MAX_PATH];
++ PSID sid = NULL;
++ PSECURITY_DESCRIPTOR descriptor = NULL;
++ DWORD err;
++
++ static wchar_t home[MAX_PATH];
++
++ int result = 0;
++
++ if (xutftowcs_path(wpath, path) < 0)
++ return 0;
++
++ /*
++ * On Windows, the home directory is owned by the administrator, but for
++ * all practical purposes, it belongs to the user. Do pretend that it is
++ * owned by the user.
++ */
++ if (!*home) {
++ DWORD size = ARRAY_SIZE(home);
++ DWORD len = GetEnvironmentVariableW(L"HOME", home, size);
++ if (!len || len > size)
++ wcscpy(home, L"::N/A::");
++ }
++ if (!wcsicmp(wpath, home))
++ return 1;
++
++ /* Get the owner SID */
++ err = GetNamedSecurityInfoW(wpath, SE_FILE_OBJECT,
++ OWNER_SECURITY_INFORMATION |
++ DACL_SECURITY_INFORMATION,
++ &sid, NULL, NULL, NULL, &descriptor);
++
++ if (err != ERROR_SUCCESS)
++ error(_("failed to get owner for '%s' (%ld)"), path, err);
++ else if (sid && IsValidSid(sid)) {
++ /* Now, verify that the SID matches the current user's */
++ static PSID current_user_sid;
++
++ if (!current_user_sid)
++ current_user_sid = get_current_user_sid();
++
++ if (current_user_sid &&
++ IsValidSid(current_user_sid) &&
++ EqualSid(sid, current_user_sid))
++ result = 1;
++ }
++
++ /*
++ * We can release the security descriptor struct only now because `sid`
++ * actually points into this struct.
++ */
++ if (descriptor)
++ LocalFree(descriptor);
++
++ return result;
++}
++
+ int is_valid_win32_path(const char *path, int allow_literal_nul)
+ {
+ const char *p = path;
+diff --git a/compat/mingw.h b/compat/mingw.h
+index af8eddd..f6bab54 100644
+--- a/compat/mingw.h
++++ b/compat/mingw.h
+@@ -452,6 +452,13 @@ char *mingw_query_user_email(void);
+ #include <inttypes.h>
+ #endif
+
++/**
++ * Verifies that the specified path is owned by the user running the
++ * current process.
++ */
++int is_path_owned_by_current_sid(const char *path);
++#define is_path_owned_by_current_user is_path_owned_by_current_sid
++
+ /**
+ * Verifies that the given path is a valid one on Windows.
+ *
+diff --git a/git-compat-util.h b/git-compat-util.h
+index 7d3db43..63ba89d 100644
+--- a/git-compat-util.h
++++ b/git-compat-util.h
+@@ -127,7 +127,9 @@
+ /* Approximation of the length of the decimal representation of this type. */
+ #define decimal_length(x) ((int)(sizeof(x) * 2.56 + 0.5) + 1)
+
+-#if defined(__sun__)
++#ifdef __MINGW64__
++#define _POSIX_C_SOURCE 1
++#elif defined(__sun__)
+ /*
+ * On Solaris, when _XOPEN_EXTENDED is set, its header file
+ * forces the programs to be XPG4v2, defeating any _XOPEN_SOURCE
+@@ -390,6 +392,18 @@ static inline int git_offset_1st_component(const char *path)
+ #define is_valid_path(path) 1
+ #endif
+
++#ifndef is_path_owned_by_current_user
++static inline int is_path_owned_by_current_uid(const char *path)
++{
++ struct stat st;
++ if (lstat(path, &st))
++ return 0;
++ return st.st_uid == geteuid();
++}
++
++#define is_path_owned_by_current_user is_path_owned_by_current_uid
++#endif
++
+ #ifndef find_last_dir_sep
+ static inline char *git_find_last_dir_sep(const char *path)
+ {
+diff --git a/path.c b/path.c
+index 7b385e5..853e716 100644
+--- a/path.c
++++ b/path.c
+@@ -1218,11 +1218,15 @@ int longest_ancestor_length(const char *path, struct string_list *prefixes)
+ const char *ceil = prefixes->items[i].string;
+ int len = strlen(ceil);
+
+- if (len == 1 && ceil[0] == '/')
+- len = 0; /* root matches anything, with length 0 */
+- else if (!strncmp(path, ceil, len) && path[len] == '/')
+- ; /* match of length len */
+- else
++ /*
++ * For root directories (`/`, `C:/`, `//server/share/`)
++ * adjust the length to exclude the trailing slash.
++ */
++ if (len > 0 && ceil[len - 1] == '/')
++ len--;
++
++ if (strncmp(path, ceil, len) ||
++ path[len] != '/' || !path[len + 1])
+ continue; /* no match */
+
+ if (len > max_len)
+diff --git a/setup.c b/setup.c
+index c04cd25..aad9ace 100644
+--- a/setup.c
++++ b/setup.c
+@@ -5,6 +5,7 @@
+ #include "string-list.h"
+ #include "chdir-notify.h"
+ #include "promisor-remote.h"
++#include "quote.h"
+
+ static int inside_git_dir = -1;
+ static int inside_work_tree = -1;
+@@ -1024,6 +1025,48 @@ static int canonicalize_ceiling_entry(struct string_list_item *item,
+ }
+ }
+
++struct safe_directory_data {
++ const char *path;
++ int is_safe;
++};
++
++static int safe_directory_cb(const char *key, const char *value, void *d)
++{
++ struct safe_directory_data *data = d;
++
++ if (strcmp(key, "safe.directory"))
++ return 0;
++
++ if (!value || !*value) {
++ data->is_safe = 0;
++ } else if (!strcmp(value, "*")) {
++ data->is_safe = 1;
++ } else {
++ const char *interpolated = NULL;
++
++ if (!git_config_pathname(&interpolated, key, value) &&
++ !fspathcmp(data->path, interpolated ? interpolated : value))
++ data->is_safe = 1;
++
++ free((char *)interpolated);
++ }
++
++ return 0;
++}
++
++static int ensure_valid_ownership(const char *path)
++{
++ struct safe_directory_data data = { .path = path };
++
++ if (!git_env_bool("GIT_TEST_ASSUME_DIFFERENT_OWNER", 0) &&
++ is_path_owned_by_current_user(path))
++ return 1;
++
++ read_very_early_config(safe_directory_cb, &data);
++
++ return data.is_safe;
++}
++
+ enum discovery_result {
+ GIT_DIR_NONE = 0,
+ GIT_DIR_EXPLICIT,
+@@ -1032,7 +1075,8 @@ enum discovery_result {
+ /* these are errors */
+ GIT_DIR_HIT_CEILING = -1,
+ GIT_DIR_HIT_MOUNT_POINT = -2,
+- GIT_DIR_INVALID_GITFILE = -3
++ GIT_DIR_INVALID_GITFILE = -3,
++ GIT_DIR_INVALID_OWNERSHIP = -4
+ };
+
+ /*
+@@ -1122,11 +1166,15 @@ static enum discovery_result setup_git_directory_gently_1(struct strbuf *dir,
+ }
+ strbuf_setlen(dir, offset);
+ if (gitdirenv) {
++ if (!ensure_valid_ownership(dir->buf))
++ return GIT_DIR_INVALID_OWNERSHIP;
+ strbuf_addstr(gitdir, gitdirenv);
+ return GIT_DIR_DISCOVERED;
+ }
+
+ if (is_git_directory(dir->buf)) {
++ if (!ensure_valid_ownership(dir->buf))
++ return GIT_DIR_INVALID_OWNERSHIP;
+ strbuf_addstr(gitdir, ".");
+ return GIT_DIR_BARE;
+ }
+@@ -1253,6 +1301,19 @@ const char *setup_git_directory_gently(int *nongit_ok)
+ dir.buf);
+ *nongit_ok = 1;
+ break;
++ case GIT_DIR_INVALID_OWNERSHIP:
++ if (!nongit_ok) {
++ struct strbuf quoted = STRBUF_INIT;
++
++ sq_quote_buf_pretty(&quoted, dir.buf);
++ die(_("unsafe repository ('%s' is owned by someone else)\n"
++ "To add an exception for this directory, call:\n"
++ "\n"
++ "\tgit config --global --add safe.directory %s"),
++ dir.buf, quoted.buf);
++ }
++ *nongit_ok = 1;
++ break;
+ case GIT_DIR_NONE:
+ /*
+ * As a safeguard against setup_git_directory_gently_1 returning
+diff --git a/t/t0033-safe-directory.sh b/t/t0033-safe-directory.sh
+new file mode 100755
+index 0000000..239d93f
+--- /dev/null
++++ b/t/t0033-safe-directory.sh
+@@ -0,0 +1,49 @@
++#!/bin/sh
++
++test_description='verify safe.directory checks'
++
++. ./test-lib.sh
++
++GIT_TEST_ASSUME_DIFFERENT_OWNER=1
++export GIT_TEST_ASSUME_DIFFERENT_OWNER
++
++expect_rejected_dir () {
++ test_must_fail git status 2>err &&
++ grep "safe.directory" err
++}
++
++test_expect_success 'safe.directory is not set' '
++ expect_rejected_dir
++'
++
++test_expect_success 'safe.directory does not match' '
++ git config --global safe.directory bogus &&
++ expect_rejected_dir
++'
++
++test_expect_success 'path exist as different key' '
++ git config --global foo.bar "$(pwd)" &&
++ expect_rejected_dir
++'
++
++test_expect_success 'safe.directory matches' '
++ git config --global --add safe.directory "$(pwd)" &&
++ git status
++'
++
++test_expect_success 'safe.directory matches, but is reset' '
++ git config --global --add safe.directory "" &&
++ expect_rejected_dir
++'
++
++test_expect_success 'safe.directory=*' '
++ git config --global --add safe.directory "*" &&
++ git status
++'
++
++test_expect_success 'safe.directory=*, but is reset' '
++ git config --global --add safe.directory "" &&
++ expect_rejected_dir
++'
++
++test_done
+diff --git a/t/t0060-path-utils.sh b/t/t0060-path-utils.sh
+index 56db5c8..f538264 100755
+--- a/t/t0060-path-utils.sh
++++ b/t/t0060-path-utils.sh
+@@ -55,12 +55,15 @@ fi
+ ancestor() {
+ # We do some math with the expected ancestor length.
+ expected=$3
+- if test -n "$rootoff" && test "x$expected" != x-1; then
+- expected=$(($expected-$rootslash))
+- test $expected -lt 0 ||
+- expected=$(($expected+$rootoff))
+- fi
+- test_expect_success "longest ancestor: $1 $2 => $expected" \
++ case "$rootoff,$expected,$2" in
++ *,*,//*) ;; # leave UNC paths alone
++ [0-9]*,[0-9]*,/*)
++ # On Windows, expect MSYS2 pseudo root translation for
++ # Unix-style absolute paths
++ expected=$(($expected-$rootslash+$rootoff))
++ ;;
++ esac
++ test_expect_success $4 "longest ancestor: $1 $2 => $expected" \
+ "actual=\$(test-tool path-utils longest_ancestor_length '$1' '$2') &&
+ test \"\$actual\" = '$expected'"
+ }
+@@ -156,6 +159,11 @@ ancestor /foo/bar /foo 4
+ ancestor /foo/bar /foo:/bar 4
+ ancestor /foo/bar /bar -1
+
++# Windows-specific: DOS drives, network shares
++ancestor C:/Users/me C:/ 2 MINGW
++ancestor D:/Users/me C:/ -1 MINGW
++ancestor //server/share/my-directory //server/share/ 14 MINGW
++
+ test_expect_success 'strip_path_suffix' '
+ test c:/msysgit = $(test-tool path-utils strip_path_suffix \
+ c:/msysgit/libexec//git-core libexec/git-core)
diff --git a/debian/patches/CVE-2022-29187.patch b/debian/patches/CVE-2022-29187.patch
new file mode 100644
index 0000000..95765ad
--- /dev/null
+++ b/debian/patches/CVE-2022-29187.patch
@@ -0,0 +1,340 @@
+Origin: https://github.com/git/git/commit/3b0bf2704980b1ed6018622bdf5377ec22289688
+Origin: https://github.com/git/git/commit/5f1a3fec8c304decaa9af2bf503712050a4a84e0
+Origin: https://github.com/git/git/commit/ae9abbb63eea74441e3e8b153dc6ec1f94c373b4
+Origin: https://github.com/git/git/commit/b9063afda17a2aa6310423c9f7b776c41f753091
+Origin: https://github.com/git/git/commit/6b11e3d52e919cce91011f4f9025e6f4b61375f2
+Reviewed-by: Aron Xu <aron@debian.org>
+Last-Updated: 2023-01-26
+
+diff --git a/Documentation/config/safe.txt b/Documentation/config/safe.txt
+index 6d764fe..74627c5 100644
+--- a/Documentation/config/safe.txt
++++ b/Documentation/config/safe.txt
+@@ -26,3 +26,17 @@ directory was listed in the `safe.directory` list. If `safe.directory=*`
+ is set in system config and you want to re-enable this protection, then
+ initialize your list with an empty value before listing the repositories
+ that you deem safe.
+++
++As explained, Git only allows you to access repositories owned by
++yourself, i.e. the user who is running Git, by default. When Git
++is running as 'root' in a non Windows platform that provides sudo,
++however, git checks the SUDO_UID environment variable that sudo creates
++and will allow access to the uid recorded as its value in addition to
++the id from 'root'.
++This is to make it easy to perform a common sequence during installation
++"make && sudo make install". A git process running under 'sudo' runs as
++'root' but the 'sudo' command exports the environment variable to record
++which id the original user has.
++If that is not what you would prefer and want git to only trust
++repositories that are owned by root instead, then you can remove
++the `SUDO_UID` variable from root's environment before invoking git.
+diff --git a/git-compat-util.h b/git-compat-util.h
+index 63ba89d..f505f81 100644
+--- a/git-compat-util.h
++++ b/git-compat-util.h
+@@ -393,12 +393,68 @@ static inline int git_offset_1st_component(const char *path)
+ #endif
+
+ #ifndef is_path_owned_by_current_user
++
++#ifdef __TANDEM
++#define ROOT_UID 65535
++#else
++#define ROOT_UID 0
++#endif
++
++/*
++ * Do not use this function when
++ * (1) geteuid() did not say we are running as 'root', or
++ * (2) using this function will compromise the system.
++ *
++ * PORTABILITY WARNING:
++ * This code assumes uid_t is unsigned because that is what sudo does.
++ * If your uid_t type is signed and all your ids are positive then it
++ * should all work fine.
++ * If your version of sudo uses negative values for uid_t or it is
++ * buggy and return an overflowed value in SUDO_UID, then git might
++ * fail to grant access to your repository properly or even mistakenly
++ * grant access to someone else.
++ * In the unlikely scenario this happened to you, and that is how you
++ * got to this message, we would like to know about it; so sent us an
++ * email to git@vger.kernel.org indicating which platform you are
++ * using and which version of sudo, so we can improve this logic and
++ * maybe provide you with a patch that would prevent this issue again
++ * in the future.
++ */
++static inline void extract_id_from_env(const char *env, uid_t *id)
++{
++ const char *real_uid = getenv(env);
++
++ /* discard anything empty to avoid a more complex check below */
++ if (real_uid && *real_uid) {
++ char *endptr = NULL;
++ unsigned long env_id;
++
++ errno = 0;
++ /* silent overflow errors could trigger a bug here */
++ env_id = strtoul(real_uid, &endptr, 10);
++ if (!*endptr && !errno)
++ *id = env_id;
++ }
++}
++
+ static inline int is_path_owned_by_current_uid(const char *path)
+ {
+ struct stat st;
++ uid_t euid;
++
+ if (lstat(path, &st))
+ return 0;
+- return st.st_uid == geteuid();
++
++ euid = geteuid();
++ if (euid == ROOT_UID)
++ {
++ if (st.st_uid == ROOT_UID)
++ return 1;
++ else
++ extract_id_from_env("SUDO_UID", &euid);
++ }
++
++ return st.st_uid == euid;
+ }
+
+ #define is_path_owned_by_current_user is_path_owned_by_current_uid
+diff --git a/setup.c b/setup.c
+index aad9ace..9dcecda 100644
+--- a/setup.c
++++ b/setup.c
+@@ -1054,14 +1054,32 @@ static int safe_directory_cb(const char *key, const char *value, void *d)
+ return 0;
+ }
+
+-static int ensure_valid_ownership(const char *path)
++/*
++ * Check if a repository is safe, by verifying the ownership of the
++ * worktree (if any), the git directory, and the gitfile (if any).
++ *
++ * Exemptions for known-safe repositories can be added via `safe.directory`
++ * config settings; for non-bare repositories, their worktree needs to be
++ * added, for bare ones their git directory.
++ */
++static int ensure_valid_ownership(const char *gitfile,
++ const char *worktree, const char *gitdir)
+ {
+- struct safe_directory_data data = { .path = path };
++ struct safe_directory_data data = {
++ .path = worktree ? worktree : gitdir
++ };
+
+ if (!git_env_bool("GIT_TEST_ASSUME_DIFFERENT_OWNER", 0) &&
+- is_path_owned_by_current_user(path))
++ (!gitfile || is_path_owned_by_current_user(gitfile)) &&
++ (!worktree || is_path_owned_by_current_user(worktree)) &&
++ (!gitdir || is_path_owned_by_current_user(gitdir)))
+ return 1;
+
++ /*
++ * data.path is the "path" that identifies the repository and it is
++ * constant regardless of what failed above. data.is_safe should be
++ * initialized to false, and might be changed by the callback.
++ */
+ read_very_early_config(safe_directory_cb, &data);
+
+ return data.is_safe;
+@@ -1149,6 +1167,8 @@ static enum discovery_result setup_git_directory_gently_1(struct strbuf *dir,
+ current_device = get_device_or_die(dir->buf, NULL, 0);
+ for (;;) {
+ int offset = dir->len, error_code = 0;
++ char *gitdir_path = NULL;
++ char *gitfile = NULL;
+
+ if (offset > min_offset)
+ strbuf_addch(dir, '/');
+@@ -1159,21 +1179,50 @@ static enum discovery_result setup_git_directory_gently_1(struct strbuf *dir,
+ if (die_on_error ||
+ error_code == READ_GITFILE_ERR_NOT_A_FILE) {
+ /* NEEDSWORK: fail if .git is not file nor dir */
+- if (is_git_directory(dir->buf))
++ if (is_git_directory(dir->buf)) {
+ gitdirenv = DEFAULT_GIT_DIR_ENVIRONMENT;
++ gitdir_path = xstrdup(dir->buf);
++ }
+ } else if (error_code != READ_GITFILE_ERR_STAT_FAILED)
+ return GIT_DIR_INVALID_GITFILE;
+- }
++ } else
++ gitfile = xstrdup(dir->buf);
++ /*
++ * Earlier, we tentatively added DEFAULT_GIT_DIR_ENVIRONMENT
++ * to check that directory for a repository.
++ * Now trim that tentative addition away, because we want to
++ * focus on the real directory we are in.
++ */
+ strbuf_setlen(dir, offset);
+ if (gitdirenv) {
+- if (!ensure_valid_ownership(dir->buf))
+- return GIT_DIR_INVALID_OWNERSHIP;
+- strbuf_addstr(gitdir, gitdirenv);
+- return GIT_DIR_DISCOVERED;
++ enum discovery_result ret;
++
++ if (ensure_valid_ownership(gitfile,
++ dir->buf,
++ (gitdir_path ? gitdir_path : gitdirenv))) {
++ strbuf_addstr(gitdir, gitdirenv);
++ ret = GIT_DIR_DISCOVERED;
++ } else
++ ret = GIT_DIR_INVALID_OWNERSHIP;
++
++ /*
++ * Earlier, during discovery, we might have allocated
++ * string copies for gitdir_path or gitfile so make
++ * sure we don't leak by freeing them now, before
++ * leaving the loop and function.
++ *
++ * Note: gitdirenv will be non-NULL whenever these are
++ * allocated, therefore we need not take care of releasing
++ * them outside of this conditional block.
++ */
++ free(gitdir_path);
++ free(gitfile);
++
++ return ret;
+ }
+
+ if (is_git_directory(dir->buf)) {
+- if (!ensure_valid_ownership(dir->buf))
++ if (!ensure_valid_ownership(NULL, NULL, dir->buf))
+ return GIT_DIR_INVALID_OWNERSHIP;
+ strbuf_addstr(gitdir, ".");
+ return GIT_DIR_BARE;
+@@ -1306,7 +1355,7 @@ const char *setup_git_directory_gently(int *nongit_ok)
+ struct strbuf quoted = STRBUF_INIT;
+
+ sq_quote_buf_pretty(&quoted, dir.buf);
+- die(_("unsafe repository ('%s' is owned by someone else)\n"
++ die(_("detected dubious ownership in repository at '%s'\n"
+ "To add an exception for this directory, call:\n"
+ "\n"
+ "\tgit config --global --add safe.directory %s"),
+diff --git a/t/lib-sudo.sh b/t/lib-sudo.sh
+new file mode 100644
+index 0000000..b4d7788
+--- /dev/null
++++ b/t/lib-sudo.sh
+@@ -0,0 +1,15 @@
++# Helpers for running git commands under sudo.
++
++# Runs a scriplet passed through stdin under sudo.
++run_with_sudo () {
++ local ret
++ local RUN="$TEST_DIRECTORY/$$.sh"
++ write_script "$RUN" "$TEST_SHELL_PATH"
++ # avoid calling "$RUN" directly so sudo doesn't get a chance to
++ # override the shell, add aditional restrictions or even reject
++ # running the script because its security policy deem it unsafe
++ sudo "$TEST_SHELL_PATH" -c "\"$RUN\""
++ ret=$?
++ rm -f "$RUN"
++ return $ret
++}
+diff --git a/t/t0034-root-safe-directory.sh b/t/t0034-root-safe-directory.sh
+new file mode 100755
+index 0000000..ff31176
+--- /dev/null
++++ b/t/t0034-root-safe-directory.sh
+@@ -0,0 +1,93 @@
++#!/bin/sh
++
++test_description='verify safe.directory checks while running as root'
++
++. ./test-lib.sh
++. "$TEST_DIRECTORY"/lib-sudo.sh
++
++if [ "$GIT_TEST_ALLOW_SUDO" != "YES" ]
++then
++ skip_all="You must set env var GIT_TEST_ALLOW_SUDO=YES in order to run this test"
++ test_done
++fi
++
++if ! test_have_prereq NOT_ROOT
++then
++ skip_all="These tests do not support running as root"
++ test_done
++fi
++
++test_lazy_prereq SUDO '
++ sudo -n id -u >u &&
++ id -u root >r &&
++ test_cmp u r &&
++ command -v git >u &&
++ sudo command -v git >r &&
++ test_cmp u r
++'
++
++if ! test_have_prereq SUDO
++then
++ skip_all="Your sudo/system configuration is either too strict or unsupported"
++ test_done
++fi
++
++test_expect_success SUDO 'setup' '
++ sudo rm -rf root &&
++ mkdir -p root/r &&
++ (
++ cd root/r &&
++ git init
++ )
++'
++
++test_expect_success SUDO 'sudo git status as original owner' '
++ (
++ cd root/r &&
++ git status &&
++ sudo git status
++ )
++'
++
++test_expect_success SUDO 'setup root owned repository' '
++ sudo mkdir -p root/p &&
++ sudo git init root/p
++'
++
++test_expect_success 'cannot access if owned by root' '
++ (
++ cd root/p &&
++ test_must_fail git status
++ )
++'
++
++test_expect_success 'can access if addressed explicitly' '
++ (
++ cd root/p &&
++ GIT_DIR=.git GIT_WORK_TREE=. git status
++ )
++'
++
++test_expect_success SUDO 'can access with sudo if root' '
++ (
++ cd root/p &&
++ sudo git status
++ )
++'
++
++test_expect_success SUDO 'can access with sudo if root by removing SUDO_UID' '
++ (
++ cd root/p &&
++ run_with_sudo <<-END
++ unset SUDO_UID &&
++ git status
++ END
++ )
++'
++
++# this MUST be always the last test
++test_expect_success SUDO 'cleanup' '
++ sudo rm -rf root
++'
++
++test_done
diff --git a/debian/patches/CVE-2022-39253.patch b/debian/patches/CVE-2022-39253.patch
new file mode 100644
index 0000000..e481e0e
--- /dev/null
+++ b/debian/patches/CVE-2022-39253.patch
@@ -0,0 +1,969 @@
+Origin: https://github.com/git/git/commit/6f054f9fb3a501c35b55c65e547a244f14c38d56
+Origin: https://github.com/git/git/commit/7de0c306f7b758d3fb537c18c2751f6250cea7a0
+Origin: https://github.com/git/git/commit/8a96dbcb339d25ba1813632319ea4052bc586ddf
+Origin: https://github.com/git/git/commit/99f4abb8dae4c9c604e5d5cf255958bbe537b928
+Origin: https://github.com/git/git/commit/f8d510ed0b357787c8d035d64f240bd82b424dc4
+Origin: https://github.com/git/git/commit/ac7e57fa288260341bdbd5e9abcdd24eaf214740
+Origin: https://github.com/git/git/commit/225d2d50ccef4baae410a96b9dc9e3978d164826
+Origin: https://github.com/git/git/commit/0f21b8f468566b991eea60bb7bdf2fce9265e367
+Origin: https://github.com/git/git/commit/0d3beb71dad7906f576b0de9cea32164549163fe
+Origin: https://github.com/git/git/commit/f4a32a550f9d40471fb42ed1e5c8612dfe4a83b1
+Origin: https://github.com/git/git/commit/a1d4f67c12ac172f835e6d5e4e0a197075e2146b
+Reviewed-by: Aron Xu <aron@debian.org>
+Last-Updated: 2023-01-26
+
+diff --git a/Documentation/config/protocol.txt b/Documentation/config/protocol.txt
+index 756591d..7993891 100644
+--- a/Documentation/config/protocol.txt
++++ b/Documentation/config/protocol.txt
+@@ -1,10 +1,10 @@
+ protocol.allow::
+ If set, provide a user defined default policy for all protocols which
+ don't explicitly have a policy (`protocol.<name>.allow`). By default,
+- if unset, known-safe protocols (http, https, git, ssh, file) have a
++ if unset, known-safe protocols (http, https, git, ssh) have a
+ default policy of `always`, known-dangerous protocols (ext) have a
+- default policy of `never`, and all other protocols have a default
+- policy of `user`. Supported policies:
++ default policy of `never`, and all other protocols (including file)
++ have a default policy of `user`. Supported policies:
+ +
+ --
+
+diff --git a/builtin/clone.c b/builtin/clone.c
+index e335734..e626073 100644
+--- a/builtin/clone.c
++++ b/builtin/clone.c
+@@ -420,13 +420,11 @@ static void copy_or_link_directory(struct strbuf *src, struct strbuf *dest,
+ int src_len, dest_len;
+ struct dir_iterator *iter;
+ int iter_status;
+- unsigned int flags;
+ struct strbuf realpath = STRBUF_INIT;
+
+ mkdir_if_missing(dest->buf, 0777);
+
+- flags = DIR_ITERATOR_PEDANTIC | DIR_ITERATOR_FOLLOW_SYMLINKS;
+- iter = dir_iterator_begin(src->buf, flags);
++ iter = dir_iterator_begin(src->buf, DIR_ITERATOR_PEDANTIC);
+
+ if (!iter)
+ die_errno(_("failed to start iterator over '%s'"), src->buf);
+@@ -442,6 +440,10 @@ static void copy_or_link_directory(struct strbuf *src, struct strbuf *dest,
+ strbuf_setlen(dest, dest_len);
+ strbuf_addstr(dest, iter->relative_path);
+
++ if (S_ISLNK(iter->st.st_mode))
++ die(_("symlink '%s' exists, refusing to clone with --local"),
++ iter->relative_path);
++
+ if (S_ISDIR(iter->st.st_mode)) {
+ mkdir_if_missing(dest->buf, 0777);
+ continue;
+diff --git a/t/lib-submodule-update.sh b/t/lib-submodule-update.sh
+index 4b714e9..cc5b58b 100644
+--- a/t/lib-submodule-update.sh
++++ b/t/lib-submodule-update.sh
+@@ -196,6 +196,7 @@ test_git_directory_exists () {
+ # the submodule repo if it doesn't exist and configures the most problematic
+ # settings for diff.ignoreSubmodules.
+ prolog () {
++ test_config_global protocol.file.allow always &&
+ (test -d submodule_update_repo || create_lib_submodule_repo) &&
+ test_config_global diff.ignoreSubmodules all &&
+ test_config diff.ignoreSubmodules all
+diff --git a/t/t1091-sparse-checkout-builtin.sh b/t/t1091-sparse-checkout-builtin.sh
+index 84acfc4..749c8f1 100755
+--- a/t/t1091-sparse-checkout-builtin.sh
++++ b/t/t1091-sparse-checkout-builtin.sh
+@@ -449,7 +449,8 @@ test_expect_success 'interaction with submodules' '
+ (
+ cd super &&
+ mkdir modules &&
+- git submodule add ../repo modules/child &&
++ git -c protocol.file.allow=always \
++ submodule add ../repo modules/child &&
+ git add . &&
+ git commit -m "add submodule" &&
+ git sparse-checkout init --cone &&
+diff --git a/t/t1500-rev-parse.sh b/t/t1500-rev-parse.sh
+index 408b97d..acef9fd 100755
+--- a/t/t1500-rev-parse.sh
++++ b/t/t1500-rev-parse.sh
+@@ -163,7 +163,8 @@ test_expect_success 'showing the superproject correctly' '
+ test_commit -C super test_commit &&
+ test_create_repo sub &&
+ test_commit -C sub test_commit &&
+- git -C super submodule add ../sub dir/sub &&
++ git -c protocol.file.allow=always \
++ -C super submodule add ../sub dir/sub &&
+ echo $(pwd)/super >expect &&
+ git -C super/dir/sub rev-parse --show-superproject-working-tree >out &&
+ test_cmp expect out &&
+diff --git a/t/t2400-worktree-add.sh b/t/t2400-worktree-add.sh
+index 5a74954..cd02f78 100755
+--- a/t/t2400-worktree-add.sh
++++ b/t/t2400-worktree-add.sh
+@@ -597,6 +597,7 @@ test_expect_success '"add" should not fail because of another bad worktree' '
+ '
+
+ test_expect_success '"add" with uninitialized submodule, with submodule.recurse unset' '
++ test_config_global protocol.file.allow always &&
+ test_create_repo submodule &&
+ test_commit -C submodule first &&
+ test_create_repo project &&
+@@ -612,6 +613,7 @@ test_expect_success '"add" with uninitialized submodule, with submodule.recurse
+ '
+
+ test_expect_success '"add" with initialized submodule, with submodule.recurse unset' '
++ test_config_global protocol.file.allow always &&
+ git -C project-clone submodule update --init &&
+ git -C project-clone worktree add ../project-4
+ '
+diff --git a/t/t2403-worktree-move.sh b/t/t2403-worktree-move.sh
+index a4e1a17..e8246ee 100755
+--- a/t/t2403-worktree-move.sh
++++ b/t/t2403-worktree-move.sh
+@@ -138,7 +138,8 @@ test_expect_success 'move a repo with uninitialized submodule' '
+ (
+ cd withsub &&
+ test_commit initial &&
+- git submodule add "$PWD"/.git sub &&
++ git -c protocol.file.allow=always \
++ submodule add "$PWD"/.git sub &&
+ git commit -m withsub &&
+ git worktree add second HEAD &&
+ git worktree move second third
+@@ -148,7 +149,7 @@ test_expect_success 'move a repo with uninitialized submodule' '
+ test_expect_success 'not move a repo with initialized submodule' '
+ (
+ cd withsub &&
+- git -C third submodule update &&
++ git -c protocol.file.allow=always -C third submodule update &&
+ test_must_fail git worktree move third forth
+ )
+ '
+@@ -227,6 +228,7 @@ test_expect_success 'remove cleans up .git/worktrees when empty' '
+ '
+
+ test_expect_success 'remove a repo with uninitialized submodule' '
++ test_config_global protocol.file.allow always &&
+ (
+ cd withsub &&
+ git worktree add to-remove HEAD &&
+@@ -235,6 +237,7 @@ test_expect_success 'remove a repo with uninitialized submodule' '
+ '
+
+ test_expect_success 'not remove a repo with initialized submodule' '
++ test_config_global protocol.file.allow always &&
+ (
+ cd withsub &&
+ git worktree add to-remove HEAD &&
+diff --git a/t/t2405-worktree-submodule.sh b/t/t2405-worktree-submodule.sh
+index e1b2bfd..51120d5 100755
+--- a/t/t2405-worktree-submodule.sh
++++ b/t/t2405-worktree-submodule.sh
+@@ -7,6 +7,7 @@ test_description='Combination of submodules and multiple worktrees'
+ base_path=$(pwd -P)
+
+ test_expect_success 'setup: create origin repos' '
++ git config --global protocol.file.allow always &&
+ git init origin/sub &&
+ test_commit -C origin/sub file1 &&
+ git init origin/main &&
+diff --git a/t/t3200-branch.sh b/t/t3200-branch.sh
+index 3ec3e1d..631a0b5 100755
+--- a/t/t3200-branch.sh
++++ b/t/t3200-branch.sh
+@@ -279,6 +279,7 @@ test_expect_success 'deleting checked-out branch from repo that is a submodule'
+ git init repo1 &&
+ git init repo1/sub &&
+ test_commit -C repo1/sub x &&
++ test_config_global protocol.file.allow always &&
+ git -C repo1 submodule add ./sub &&
+ git -C repo1 commit -m "adding sub" &&
+
+diff --git a/t/t3420-rebase-autostash.sh b/t/t3420-rebase-autostash.sh
+index ca33173..80df13a 100755
+--- a/t/t3420-rebase-autostash.sh
++++ b/t/t3420-rebase-autostash.sh
+@@ -307,7 +307,7 @@ test_expect_success 'autostash is saved on editor failure with conflict' '
+ test_expect_success 'autostash with dirty submodules' '
+ test_when_finished "git reset --hard && git checkout master" &&
+ git checkout -b with-submodule &&
+- git submodule add ./ sub &&
++ git -c protocol.file.allow=always submodule add ./ sub &&
+ test_tick &&
+ git commit -m add-submodule &&
+ echo changed >sub/file0 &&
+diff --git a/t/t3426-rebase-submodule.sh b/t/t3426-rebase-submodule.sh
+index 0ad3a07..fb21f67 100755
+--- a/t/t3426-rebase-submodule.sh
++++ b/t/t3426-rebase-submodule.sh
+@@ -47,7 +47,8 @@ test_expect_success 'rebase interactive ignores modified submodules' '
+ git init sub &&
+ git -C sub commit --allow-empty -m "Initial commit" &&
+ git init super &&
+- git -C super submodule add ../sub &&
++ git -c protocol.file.allow=always \
++ -C super submodule add ../sub &&
+ git -C super config submodule.sub.ignore dirty &&
+ >super/foo &&
+ git -C super add foo &&
+diff --git a/t/t3512-cherry-pick-submodule.sh b/t/t3512-cherry-pick-submodule.sh
+index 6ece1d8..697bc68 100755
+--- a/t/t3512-cherry-pick-submodule.sh
++++ b/t/t3512-cherry-pick-submodule.sh
+@@ -10,6 +10,8 @@ KNOWN_FAILURE_NOFF_MERGE_ATTEMPTS_TO_MERGE_REMOVED_SUBMODULE_FILES=1
+ test_submodule_switch "cherry-pick"
+
+ test_expect_success 'unrelated submodule/file conflict is ignored' '
++ test_config_global protocol.file.allow always &&
++
+ test_create_repo sub &&
+
+ touch sub/file &&
+diff --git a/t/t3600-rm.sh b/t/t3600-rm.sh
+index efec8d1..99dab76 100755
+--- a/t/t3600-rm.sh
++++ b/t/t3600-rm.sh
+@@ -321,7 +321,7 @@ test_expect_success 'rm removes empty submodules from work tree' '
+
+ test_expect_success 'rm removes removed submodule from index and .gitmodules' '
+ git reset --hard &&
+- git submodule update &&
++ git -c protocol.file.allow=always submodule update &&
+ rm -rf submod &&
+ git rm submod &&
+ git status -s -uno --ignore-submodules=none >actual &&
+@@ -627,6 +627,7 @@ cat >expect.deepmodified <<EOF
+ EOF
+
+ test_expect_success 'setup subsubmodule' '
++ test_config_global protocol.file.allow always &&
+ git reset --hard &&
+ git submodule update &&
+ (
+diff --git a/t/t3906-stash-submodule.sh b/t/t3906-stash-submodule.sh
+index a52e53d..0f7348e 100755
+--- a/t/t3906-stash-submodule.sh
++++ b/t/t3906-stash-submodule.sh
+@@ -36,7 +36,7 @@ setup_basic () {
+ git init main &&
+ (
+ cd main &&
+- git submodule add ../sub &&
++ git -c protocol.file.allow=always submodule add ../sub &&
+ test_commit main_file
+ )
+ }
+diff --git a/t/t4059-diff-submodule-not-initialized.sh b/t/t4059-diff-submodule-not-initialized.sh
+index 49bca7b..d489230 100755
+--- a/t/t4059-diff-submodule-not-initialized.sh
++++ b/t/t4059-diff-submodule-not-initialized.sh
+@@ -49,7 +49,7 @@ test_expect_success 'setup - submodules' '
+ '
+
+ test_expect_success 'setup - git submodule add' '
+- git submodule add ./sm2 sm1 &&
++ git -c protocol.file.allow=always submodule add ./sm2 sm1 &&
+ commit_file sm1 .gitmodules &&
+ git diff-tree -p --no-commit-id --submodule=log HEAD -- sm1 >actual &&
+ cat >expected <<-EOF &&
+diff --git a/t/t4060-diff-submodule-option-diff-format.sh b/t/t4060-diff-submodule-option-diff-format.sh
+index fc8229c..57b1912 100755
+--- a/t/t4060-diff-submodule-option-diff-format.sh
++++ b/t/t4060-diff-submodule-option-diff-format.sh
+@@ -759,9 +759,9 @@ test_expect_success 'diff --submodule=diff with .git file' '
+ '
+
+ test_expect_success 'setup nested submodule' '
+- git submodule add -f ./sm2 &&
++ git -c protocol.file.allow=always submodule add -f ./sm2 &&
+ git commit -a -m "add sm2" &&
+- git -C sm2 submodule add ../sm2 nested &&
++ git -c protocol.file.allow=always -C sm2 submodule add ../sm2 nested &&
+ git -C sm2 commit -a -m "nested sub" &&
+ head10=$(git -C sm2 rev-parse --short --verify HEAD)
+ '
+diff --git a/t/t4067-diff-partial-clone.sh b/t/t4067-diff-partial-clone.sh
+index 804f2a8..28f42a4 100755
+--- a/t/t4067-diff-partial-clone.sh
++++ b/t/t4067-diff-partial-clone.sh
+@@ -77,6 +77,7 @@ test_expect_success 'diff skips same-OID blobs' '
+
+ test_expect_success 'when fetching missing objects, diff skips GITLINKs' '
+ test_when_finished "rm -rf sub server client trace" &&
++ test_config_global protocol.file.allow always &&
+
+ test_create_repo sub &&
+ test_commit -C sub first &&
+diff --git a/t/t4208-log-magic-pathspec.sh b/t/t4208-log-magic-pathspec.sh
+index 6cdbe47..aeaf0d5 100755
+--- a/t/t4208-log-magic-pathspec.sh
++++ b/t/t4208-log-magic-pathspec.sh
+@@ -126,6 +126,7 @@ test_expect_success 'command line pathspec parsing for "git log"' '
+
+ test_expect_success 'tree_entry_interesting does not match past submodule boundaries' '
+ test_when_finished "rm -rf repo submodule" &&
++ test_config_global protocol.file.allow always &&
+ git init submodule &&
+ test_commit -C submodule initial &&
+ git init repo &&
+diff --git a/t/t5510-fetch.sh b/t/t5510-fetch.sh
+index 2013051..b60ba0f 100755
+--- a/t/t5510-fetch.sh
++++ b/t/t5510-fetch.sh
+@@ -627,6 +627,7 @@ test_expect_success 'fetch.writeCommitGraph' '
+ '
+
+ test_expect_success 'fetch.writeCommitGraph with submodules' '
++ test_config_global protocol.file.allow always &&
+ git clone dups super &&
+ (
+ cd super &&
+diff --git a/t/t5526-fetch-submodules.sh b/t/t5526-fetch-submodules.sh
+index 53d7b8e..aa04eac 100755
+--- a/t/t5526-fetch-submodules.sh
++++ b/t/t5526-fetch-submodules.sh
+@@ -35,6 +35,7 @@ add_upstream_commit() {
+ }
+
+ test_expect_success setup '
++ git config --global protocol.file.allow always &&
+ mkdir deepsubmodule &&
+ (
+ cd deepsubmodule &&
+diff --git a/t/t5545-push-options.sh b/t/t5545-push-options.sh
+index 38e6f73..77ce917 100755
+--- a/t/t5545-push-options.sh
++++ b/t/t5545-push-options.sh
+@@ -113,6 +113,7 @@ test_expect_success 'push options and submodules' '
+ test_commit -C parent one &&
+ git -C parent push --mirror up &&
+
++ test_config_global protocol.file.allow always &&
+ git -C parent submodule add ../upstream workbench &&
+ git -C parent/workbench remote add up ../../upstream &&
+ git -C parent commit -m "add submodule" &&
+diff --git a/t/t5572-pull-submodule.sh b/t/t5572-pull-submodule.sh
+index 37fd06b..9b3b4af 100755
+--- a/t/t5572-pull-submodule.sh
++++ b/t/t5572-pull-submodule.sh
+@@ -46,6 +46,10 @@ KNOWN_FAILURE_NOFF_MERGE_DOESNT_CREATE_EMPTY_SUBMODULE_DIR=1
+ KNOWN_FAILURE_NOFF_MERGE_ATTEMPTS_TO_MERGE_REMOVED_SUBMODULE_FILES=1
+ test_submodule_switch_func "git_pull_noff"
+
++test_expect_success 'setup' '
++ git config --global protocol.file.allow always
++'
++
+ test_expect_success 'pull --recurse-submodule setup' '
+ test_create_repo child &&
+ test_commit -C child bar &&
+diff --git a/t/t5601-clone.sh b/t/t5601-clone.sh
+index 7df3c53..50d4821 100755
+--- a/t/t5601-clone.sh
++++ b/t/t5601-clone.sh
+@@ -738,6 +738,7 @@ test_expect_success 'batch missing blob request does not inadvertently try to fe
+ echo aa >server/a &&
+ echo bb >server/b &&
+ # Also add a gitlink pointing to an arbitrary repository
++ test_config_global protocol.file.allow always &&
+ git -C server submodule add "$(pwd)/repo_for_submodule" c &&
+ git -C server add a b c &&
+ git -C server commit -m x &&
+diff --git a/t/t5604-clone-reference.sh b/t/t5604-clone-reference.sh
+index 2f7be23..9d32f1c 100755
+--- a/t/t5604-clone-reference.sh
++++ b/t/t5604-clone-reference.sh
+@@ -300,8 +300,6 @@ test_expect_success SYMLINKS 'setup repo with manually symlinked or unknown file
+ ln -s ../an-object $obj &&
+
+ cd ../ &&
+- find . -type f | sort >../../../T.objects-files.raw &&
+- find . -type l | sort >../../../T.objects-symlinks.raw &&
+ echo unknown_content >unknown_file
+ ) &&
+ git -C T fsck &&
+@@ -310,19 +308,27 @@ test_expect_success SYMLINKS 'setup repo with manually symlinked or unknown file
+
+
+ test_expect_success SYMLINKS 'clone repo with symlinked or unknown files at objects/' '
+- for option in --local --no-hardlinks --shared --dissociate
++ # None of these options work when cloning locally, since T has
++ # symlinks in its `$GIT_DIR/objects` directory
++ for option in --local --no-hardlinks --dissociate
+ do
+- git clone $option T T$option || return 1 &&
+- git -C T$option fsck || return 1 &&
+- git -C T$option rev-list --all --objects >T$option.objects &&
+- test_cmp T.objects T$option.objects &&
+- (
+- cd T$option/.git/objects &&
+- find . -type f | sort >../../../T$option.objects-files.raw &&
+- find . -type l | sort >../../../T$option.objects-symlinks.raw
+- )
++ test_must_fail git clone $option T T$option 2>err || return 1 &&
++ test_i18ngrep "symlink.*exists" err || return 1
+ done &&
+
++ # But `--shared` clones should still work, even when specifying
++ # a local path *and* that repository has symlinks present in its
++ # `$GIT_DIR/objects` directory.
++ git clone --shared T T--shared &&
++ git -C T--shared fsck &&
++ git -C T--shared rev-list --all --objects >T--shared.objects &&
++ test_cmp T.objects T--shared.objects &&
++ (
++ cd T--shared/.git/objects &&
++ find . -type f | sort >../../../T--shared.objects-files.raw &&
++ find . -type l | sort >../../../T--shared.objects-symlinks.raw
++ ) &&
++
+ for raw in $(ls T*.raw)
+ do
+ sed -e "s!/../!/Y/!; s![0-9a-f]\{38,\}!Z!" -e "/commit-graph/d" \
+@@ -330,26 +336,6 @@ test_expect_success SYMLINKS 'clone repo with symlinked or unknown files at obje
+ sort $raw.de-sha-1 >$raw.de-sha || return 1
+ done &&
+
+- cat >expected-files <<-EOF &&
+- ./Y/Z
+- ./Y/Z
+- ./Y/Z
+- ./a-loose-dir/Z
+- ./an-object
+- ./info/packs
+- ./pack/pack-Z.idx
+- ./pack/pack-Z.pack
+- ./packs/pack-Z.idx
+- ./packs/pack-Z.pack
+- ./unknown_file
+- EOF
+-
+- for option in --local --no-hardlinks --dissociate
+- do
+- test_cmp expected-files T$option.objects-files.raw.de-sha || return 1 &&
+- test_must_be_empty T$option.objects-symlinks.raw.de-sha || return 1
+- done &&
+-
+ echo ./info/alternates >expected-files &&
+ test_cmp expected-files T--shared.objects-files.raw &&
+ test_must_be_empty T--shared.objects-symlinks.raw
+diff --git a/t/t5614-clone-submodules-shallow.sh b/t/t5614-clone-submodules-shallow.sh
+index e4e6ea4..d361738 100755
+--- a/t/t5614-clone-submodules-shallow.sh
++++ b/t/t5614-clone-submodules-shallow.sh
+@@ -24,6 +24,7 @@ test_expect_success 'setup' '
+
+ test_expect_success 'nonshallow clone implies nonshallow submodule' '
+ test_when_finished "rm -rf super_clone" &&
++ test_config_global protocol.file.allow always &&
+ git clone --recurse-submodules "file://$pwd/." super_clone &&
+ git -C super_clone log --oneline >lines &&
+ test_line_count = 3 lines &&
+@@ -33,6 +34,7 @@ test_expect_success 'nonshallow clone implies nonshallow submodule' '
+
+ test_expect_success 'shallow clone with shallow submodule' '
+ test_when_finished "rm -rf super_clone" &&
++ test_config_global protocol.file.allow always &&
+ git clone --recurse-submodules --depth 2 --shallow-submodules "file://$pwd/." super_clone &&
+ git -C super_clone log --oneline >lines &&
+ test_line_count = 2 lines &&
+@@ -42,6 +44,7 @@ test_expect_success 'shallow clone with shallow submodule' '
+
+ test_expect_success 'shallow clone does not imply shallow submodule' '
+ test_when_finished "rm -rf super_clone" &&
++ test_config_global protocol.file.allow always &&
+ git clone --recurse-submodules --depth 2 "file://$pwd/." super_clone &&
+ git -C super_clone log --oneline >lines &&
+ test_line_count = 2 lines &&
+@@ -51,6 +54,7 @@ test_expect_success 'shallow clone does not imply shallow submodule' '
+
+ test_expect_success 'shallow clone with non shallow submodule' '
+ test_when_finished "rm -rf super_clone" &&
++ test_config_global protocol.file.allow always &&
+ git clone --recurse-submodules --depth 2 --no-shallow-submodules "file://$pwd/." super_clone &&
+ git -C super_clone log --oneline >lines &&
+ test_line_count = 2 lines &&
+@@ -60,6 +64,7 @@ test_expect_success 'shallow clone with non shallow submodule' '
+
+ test_expect_success 'non shallow clone with shallow submodule' '
+ test_when_finished "rm -rf super_clone" &&
++ test_config_global protocol.file.allow always &&
+ git clone --recurse-submodules --no-local --shallow-submodules "file://$pwd/." super_clone &&
+ git -C super_clone log --oneline >lines &&
+ test_line_count = 3 lines &&
+@@ -69,6 +74,7 @@ test_expect_success 'non shallow clone with shallow submodule' '
+
+ test_expect_success 'clone follows shallow recommendation' '
+ test_when_finished "rm -rf super_clone" &&
++ test_config_global protocol.file.allow always &&
+ git config -f .gitmodules submodule.sub.shallow true &&
+ git add .gitmodules &&
+ git commit -m "recommend shallow for sub" &&
+@@ -87,6 +93,7 @@ test_expect_success 'clone follows shallow recommendation' '
+
+ test_expect_success 'get unshallow recommended shallow submodule' '
+ test_when_finished "rm -rf super_clone" &&
++ test_config_global protocol.file.allow always &&
+ git clone --no-local "file://$pwd/." super_clone &&
+ (
+ cd super_clone &&
+@@ -103,6 +110,7 @@ test_expect_success 'get unshallow recommended shallow submodule' '
+
+ test_expect_success 'clone follows non shallow recommendation' '
+ test_when_finished "rm -rf super_clone" &&
++ test_config_global protocol.file.allow always &&
+ git config -f .gitmodules submodule.sub.shallow false &&
+ git add .gitmodules &&
+ git commit -m "recommend non shallow for sub" &&
+diff --git a/t/t5616-partial-clone.sh b/t/t5616-partial-clone.sh
+index d98c550..a6138e8 100755
+--- a/t/t5616-partial-clone.sh
++++ b/t/t5616-partial-clone.sh
+@@ -171,6 +171,8 @@ test_expect_success 'partial clone with transfer.fsckobjects=1 works with submod
+ test_config -C src_with_sub uploadpack.allowfilter 1 &&
+ test_config -C src_with_sub uploadpack.allowanysha1inwant 1 &&
+
++ test_config_global protocol.file.allow always &&
++
+ git -C src_with_sub submodule add "file://$(pwd)/submodule" mysub &&
+ git -C src_with_sub commit -m "commit with submodule" &&
+
+diff --git a/t/t5617-clone-submodules-remote.sh b/t/t5617-clone-submodules-remote.sh
+index 1a041df..0152dc0 100755
+--- a/t/t5617-clone-submodules-remote.sh
++++ b/t/t5617-clone-submodules-remote.sh
+@@ -7,6 +7,7 @@ test_description='Test cloning repos with submodules using remote-tracking branc
+ pwd=$(pwd)
+
+ test_expect_success 'setup' '
++ git config --global protocol.file.allow always &&
+ git checkout -b master &&
+ test_commit commit1 &&
+ mkdir sub &&
+diff --git a/t/t6008-rev-list-submodule.sh b/t/t6008-rev-list-submodule.sh
+index c4af9ca..a65e5f2 100755
+--- a/t/t6008-rev-list-submodule.sh
++++ b/t/t6008-rev-list-submodule.sh
+@@ -23,7 +23,7 @@ test_expect_success 'setup' '
+
+ : > super-file &&
+ git add super-file &&
+- git submodule add "$(pwd)" sub &&
++ git -c protocol.file.allow=always submodule add "$(pwd)" sub &&
+ git symbolic-ref HEAD refs/heads/super &&
+ test_tick &&
+ git commit -m super-initial &&
+diff --git a/t/t6134-pathspec-in-submodule.sh b/t/t6134-pathspec-in-submodule.sh
+index c670668..2fde65b 100755
+--- a/t/t6134-pathspec-in-submodule.sh
++++ b/t/t6134-pathspec-in-submodule.sh
+@@ -9,7 +9,7 @@ test_expect_success 'setup a submodule' '
+ : >pretzel/a &&
+ git -C pretzel add a &&
+ git -C pretzel commit -m "add a file" -- a &&
+- git submodule add ./pretzel sub &&
++ git -c protocol.file.allow=always submodule add ./pretzel sub &&
+ git commit -a -m "add submodule" &&
+ git submodule deinit --all
+ '
+diff --git a/t/t7001-mv.sh b/t/t7001-mv.sh
+index 63d5f41..6156eeb 100755
+--- a/t/t7001-mv.sh
++++ b/t/t7001-mv.sh
+@@ -307,6 +307,7 @@ test_expect_success SYMLINKS 'check moved symlink' '
+ rm -f moved symlink
+
+ test_expect_success 'setup submodule' '
++ test_config_global protocol.file.allow always &&
+ git commit -m initial &&
+ git reset --hard &&
+ git submodule add ./. sub &&
+@@ -513,6 +514,7 @@ test_expect_success 'moving a submodule in nested directories' '
+ '
+
+ test_expect_success 'moving nested submodules' '
++ test_config_global protocol.file.allow always &&
+ git commit -am "cleanup commit" &&
+ mkdir sub_nested_nested &&
+ (cd sub_nested_nested &&
+diff --git a/t/t7064-wtstatus-pv2.sh b/t/t7064-wtstatus-pv2.sh
+index 601b478..2fdb6ec 100755
+--- a/t/t7064-wtstatus-pv2.sh
++++ b/t/t7064-wtstatus-pv2.sh
+@@ -462,6 +462,7 @@ test_expect_success 'create and add submodule, submodule appears clean (A. S...)
+ git checkout initial-branch &&
+ git clone . sub_repo &&
+ git clone . super_repo &&
++ test_config_global protocol.file.allow always &&
+ ( cd super_repo &&
+ git submodule add ../sub_repo sub1 &&
+
+diff --git a/t/t7300-clean.sh b/t/t7300-clean.sh
+index cb5e34d..713f113 100755
+--- a/t/t7300-clean.sh
++++ b/t/t7300-clean.sh
+@@ -480,6 +480,7 @@ test_expect_success 'should not clean submodules' '
+ git init &&
+ test_commit msg hello.world
+ ) &&
++ test_config_global protocol.file.allow always &&
+ git submodule add ./repo/.git sub1 &&
+ git commit -m "sub1" &&
+ git branch before_sub2 &&
+diff --git a/t/t7400-submodule-basic.sh b/t/t7400-submodule-basic.sh
+index fec7e02..bf1a4df 100755
+--- a/t/t7400-submodule-basic.sh
++++ b/t/t7400-submodule-basic.sh
+@@ -11,6 +11,10 @@ subcommands of git submodule.
+
+ . ./test-lib.sh
+
++test_expect_success 'setup - enable local submodules' '
++ git config --global protocol.file.allow always
++'
++
+ test_expect_success 'submodule deinit works on empty repository' '
+ git submodule deinit --all
+ '
+diff --git a/t/t7403-submodule-sync.sh b/t/t7403-submodule-sync.sh
+index 0726799..3bc904b 100755
+--- a/t/t7403-submodule-sync.sh
++++ b/t/t7403-submodule-sync.sh
+@@ -11,6 +11,8 @@ These tests exercise the "git submodule sync" subcommand.
+ . ./test-lib.sh
+
+ test_expect_success setup '
++ git config --global protocol.file.allow always &&
++
+ echo file >file &&
+ git add file &&
+ test_tick &&
+diff --git a/t/t7406-submodule-update.sh b/t/t7406-submodule-update.sh
+index acb8766..328b9b7 100755
+--- a/t/t7406-submodule-update.sh
++++ b/t/t7406-submodule-update.sh
+@@ -22,6 +22,7 @@ compare_head()
+
+
+ test_expect_success 'setup a submodule tree' '
++ git config --global protocol.file.allow always &&
+ echo file > file &&
+ git add file &&
+ test_tick &&
+diff --git a/t/t7407-submodule-foreach.sh b/t/t7407-submodule-foreach.sh
+index 6b2aa91..a3404ac 100755
+--- a/t/t7407-submodule-foreach.sh
++++ b/t/t7407-submodule-foreach.sh
+@@ -13,6 +13,7 @@ that are currently checked out.
+
+
+ test_expect_success 'setup a submodule tree' '
++ git config --global protocol.file.allow always &&
+ echo file > file &&
+ git add file &&
+ test_tick &&
+diff --git a/t/t7408-submodule-reference.sh b/t/t7408-submodule-reference.sh
+index a3892f4..02feb85 100755
+--- a/t/t7408-submodule-reference.sh
++++ b/t/t7408-submodule-reference.sh
+@@ -17,6 +17,10 @@ test_alternate_is_used () {
+ test_cmp expect actual
+ }
+
++test_expect_success 'setup' '
++ git config --global protocol.file.allow always
++'
++
+ test_expect_success 'preparing first repository' '
+ test_create_repo A &&
+ (
+diff --git a/t/t7409-submodule-detached-work-tree.sh b/t/t7409-submodule-detached-work-tree.sh
+index fc018e3..e12eed5 100755
+--- a/t/t7409-submodule-detached-work-tree.sh
++++ b/t/t7409-submodule-detached-work-tree.sh
+@@ -12,6 +12,10 @@ on detached working trees
+ TEST_NO_CREATE_REPO=1
+ . ./test-lib.sh
+
++test_expect_success 'setup' '
++ git config --global protocol.file.allow always
++'
++
+ test_expect_success 'submodule on detached working tree' '
+ git init --bare remote &&
+ test_create_repo bundle1 &&
+diff --git a/t/t7411-submodule-config.sh b/t/t7411-submodule-config.sh
+index ad28e93..c583c4e 100755
+--- a/t/t7411-submodule-config.sh
++++ b/t/t7411-submodule-config.sh
+@@ -12,6 +12,9 @@ from the database and from the worktree works.
+ TEST_NO_CREATE_REPO=1
+ . ./test-lib.sh
+
++test_expect_success 'setup' '
++ git config --global protocol.file.allow always
++'
+ test_expect_success 'submodule config cache setup' '
+ mkdir submodule &&
+ (cd submodule &&
+diff --git a/t/t7413-submodule-is-active.sh b/t/t7413-submodule-is-active.sh
+index c8e7e98..c8b5ac2 100755
+--- a/t/t7413-submodule-is-active.sh
++++ b/t/t7413-submodule-is-active.sh
+@@ -9,6 +9,7 @@ submodules which are "active" and interesting to the user.
+ . ./test-lib.sh
+
+ test_expect_success 'setup' '
++ git config --global protocol.file.allow always &&
+ git init sub &&
+ test_commit -C sub initial &&
+ git init super &&
+diff --git a/t/t7414-submodule-mistakes.sh b/t/t7414-submodule-mistakes.sh
+index f2e7df5..cf95603 100755
+--- a/t/t7414-submodule-mistakes.sh
++++ b/t/t7414-submodule-mistakes.sh
+@@ -30,7 +30,8 @@ test_expect_success 'no warning when updating entry' '
+
+ test_expect_success 'submodule add does not warn' '
+ test_when_finished "git rm -rf submodule .gitmodules" &&
+- git submodule add ./embed submodule 2>stderr &&
++ git -c protocol.file.allow=always \
++ submodule add ./embed submodule 2>stderr &&
+ test_i18ngrep ! warning stderr
+ '
+
+diff --git a/t/t7415-submodule-names.sh b/t/t7415-submodule-names.sh
+index f70368b..f37456f 100755
+--- a/t/t7415-submodule-names.sh
++++ b/t/t7415-submodule-names.sh
+@@ -8,6 +8,10 @@ real-world setup that confirms we catch this in practice.
+ . ./test-lib.sh
+ . "$TEST_DIRECTORY"/lib-pack.sh
+
++test_expect_success 'setup' '
++ git config --global protocol.file.allow always
++'
++
+ test_expect_success 'check names' '
+ cat >expect <<-\EOF &&
+ valid
+diff --git a/t/t7416-submodule-dash-url.sh b/t/t7416-submodule-dash-url.sh
+index d21dc8b..3ebd985 100755
+--- a/t/t7416-submodule-dash-url.sh
++++ b/t/t7416-submodule-dash-url.sh
+@@ -3,6 +3,10 @@
+ test_description='check handling of disallowed .gitmodule urls'
+ . ./test-lib.sh
+
++test_expect_success 'setup' '
++ git config --global protocol.file.allow always
++'
++
+ test_expect_success 'create submodule with protected dash in url' '
+ git init upstream &&
+ git -C upstream commit --allow-empty -m base &&
+diff --git a/t/t7417-submodule-path-url.sh b/t/t7417-submodule-path-url.sh
+index f7e7e94..b17d180 100755
+--- a/t/t7417-submodule-path-url.sh
++++ b/t/t7417-submodule-path-url.sh
+@@ -3,6 +3,10 @@
+ test_description='check handling of .gitmodule path with dash'
+ . ./test-lib.sh
+
++test_expect_success 'setup' '
++ git config --global protocol.file.allow always
++'
++
+ test_expect_success 'create submodule with dash in path' '
+ git init upstream &&
+ git -C upstream commit --allow-empty -m base &&
+diff --git a/t/t7418-submodule-sparse-gitmodules.sh b/t/t7418-submodule-sparse-gitmodules.sh
+index 3f7f271..16331c3 100755
+--- a/t/t7418-submodule-sparse-gitmodules.sh
++++ b/t/t7418-submodule-sparse-gitmodules.sh
+@@ -14,6 +14,10 @@ also by committing .gitmodules and then just removing it from the filesystem.
+
+ . ./test-lib.sh
+
++test_expect_success 'setup' '
++ git config --global protocol.file.allow always
++'
++
+ test_expect_success 'sparse checkout setup which hides .gitmodules' '
+ git init upstream &&
+ git init submodule &&
+diff --git a/t/t7419-submodule-set-branch.sh b/t/t7419-submodule-set-branch.sh
+index 3b925c3..5357093 100755
+--- a/t/t7419-submodule-set-branch.sh
++++ b/t/t7419-submodule-set-branch.sh
+@@ -12,6 +12,10 @@ as expected.
+ TEST_NO_CREATE_REPO=1
+ . ./test-lib.sh
+
++test_expect_success 'setup' '
++ git config --global protocol.file.allow always
++'
++
+ test_expect_success 'submodule config cache setup' '
+ mkdir submodule &&
+ (cd submodule &&
+diff --git a/t/t7420-submodule-set-url.sh b/t/t7420-submodule-set-url.sh
+index ef0cb6e..d6bf62b 100755
+--- a/t/t7420-submodule-set-url.sh
++++ b/t/t7420-submodule-set-url.sh
+@@ -12,6 +12,10 @@ as expected.
+ TEST_NO_CREATE_REPO=1
+ . ./test-lib.sh
+
++test_expect_success 'setup' '
++ git config --global protocol.file.allow always
++'
++
+ test_expect_success 'submodule config cache setup' '
+ mkdir submodule &&
+ (
+diff --git a/t/t7421-submodule-summary-add.sh b/t/t7421-submodule-summary-add.sh
+index b070f13..ce64d8b 100755
+--- a/t/t7421-submodule-summary-add.sh
++++ b/t/t7421-submodule-summary-add.sh
+@@ -12,6 +12,10 @@ while making sure to add submodules using `git submodule add` instead of
+
+ . ./test-lib.sh
+
++test_expect_success 'setup' '
++ git config --global protocol.file.allow always
++'
++
+ test_expect_success 'summary test environment setup' '
+ git init sm &&
+ test_commit -C sm "add file" file file-content file-tag &&
+diff --git a/t/t7506-status-submodule.sh b/t/t7506-status-submodule.sh
+index 3fcb447..459300c 100755
+--- a/t/t7506-status-submodule.sh
++++ b/t/t7506-status-submodule.sh
+@@ -251,6 +251,7 @@ test_expect_success 'status with merge conflict in .gitmodules' '
+ test_create_repo_with_commit sub1 &&
+ test_tick &&
+ test_create_repo_with_commit sub2 &&
++ test_config_global protocol.file.allow always &&
+ (
+ cd super &&
+ prev=$(git rev-parse HEAD) &&
+@@ -326,6 +327,7 @@ test_expect_success 'diff --submodule with merge conflict in .gitmodules' '
+ # sub2 will have an untracked file
+ # sub3 will have an untracked repository
+ test_expect_success 'setup superproject with untracked file in nested submodule' '
++ test_config_global protocol.file.allow always &&
+ (
+ cd super &&
+ git clean -dfx &&
+diff --git a/t/t7507-commit-verbose.sh b/t/t7507-commit-verbose.sh
+index ed2653d..bd0ae4b 100755
+--- a/t/t7507-commit-verbose.sh
++++ b/t/t7507-commit-verbose.sh
+@@ -74,6 +74,7 @@ test_expect_success 'diff in message is retained with -v' '
+
+ test_expect_success 'submodule log is stripped out too with -v' '
+ git config diff.submodule log &&
++ test_config_global protocol.file.allow always &&
+ git submodule add ./. sub &&
+ git commit -m "sub added" &&
+ (
+diff --git a/t/t7800-difftool.sh b/t/t7800-difftool.sh
+index a578b35..cca5069 100755
+--- a/t/t7800-difftool.sh
++++ b/t/t7800-difftool.sh
+@@ -626,6 +626,7 @@ test_expect_success 'difftool --no-symlinks detects conflict ' '
+
+ test_expect_success 'difftool properly honors gitlink and core.worktree' '
+ test_when_finished rm -rf submod/ule &&
++ test_config_global protocol.file.allow always &&
+ git submodule add ./. submod/ule &&
+ test_config -C submod/ule diff.tool checktrees &&
+ test_config -C submod/ule difftool.checktrees.cmd '\''
+diff --git a/t/t7814-grep-recurse-submodules.sh b/t/t7814-grep-recurse-submodules.sh
+index 828cb3b..f465c0d 100755
+--- a/t/t7814-grep-recurse-submodules.sh
++++ b/t/t7814-grep-recurse-submodules.sh
+@@ -193,6 +193,7 @@ test_expect_success !MINGW 'grep recurse submodule colon in name' '
+ git -C "su:b" commit -m "add fi:le" &&
+ test_tick &&
+
++ test_config_global protocol.file.allow always &&
+ git -C parent submodule add "../su:b" "su:b" &&
+ git -C parent commit -m "add submodule" &&
+ test_tick &&
+@@ -227,6 +228,7 @@ test_expect_success 'grep history with moved submoules' '
+ git -C sub commit -m "add file" &&
+ test_tick &&
+
++ test_config_global protocol.file.allow always &&
+ git -C parent submodule add ../sub dir/sub &&
+ git -C parent commit -m "add submodule" &&
+ test_tick &&
+@@ -271,6 +273,7 @@ test_expect_success 'grep using relative path' '
+ mkdir parent/src &&
+ echo "(1|2)d(3|4)" >parent/src/file2 &&
+ git -C parent add src/file2 &&
++ test_config_global protocol.file.allow always &&
+ git -C parent submodule add ../sub &&
+ git -C parent commit -m "add files and submodule" &&
+ test_tick &&
+@@ -313,6 +316,7 @@ test_expect_success 'grep from a subdir' '
+ mkdir parent/src &&
+ echo "(1|2)d(3|4)" >parent/src/file &&
+ git -C parent add src/file &&
++ test_config_global protocol.file.allow always &&
+ git -C parent submodule add ../sub src/sub &&
+ git -C parent submodule add ../sub sub &&
+ git -C parent commit -m "add files and submodules" &&
+diff --git a/t/t9304-fast-import-marks.sh b/t/t9304-fast-import-marks.sh
+index d4359db..73f3ca2 100755
+--- a/t/t9304-fast-import-marks.sh
++++ b/t/t9304-fast-import-marks.sh
+@@ -25,6 +25,7 @@ test_expect_success 'import with large marks file' '
+ '
+
+ test_expect_success 'setup dump with submodule' '
++ test_config_global protocol.file.allow always &&
+ git submodule add "$PWD" sub &&
+ git commit -m "add submodule" &&
+ git fast-export HEAD >dump
+diff --git a/t/t9350-fast-export.sh b/t/t9350-fast-export.sh
+index 1372842..703428f 100755
+--- a/t/t9350-fast-export.sh
++++ b/t/t9350-fast-export.sh
+@@ -265,6 +265,7 @@ test_expect_success 'signed-tags=warn-strip' '
+
+ test_expect_success 'setup submodule' '
+
++ test_config_global protocol.file.allow always &&
+ git checkout -f master &&
+ mkdir sub &&
+ (
+@@ -290,6 +291,7 @@ test_expect_success 'setup submodule' '
+
+ test_expect_success 'submodule fast-export | fast-import' '
+
++ test_config_global protocol.file.allow always &&
+ SUBENT1=$(git ls-tree master^ sub) &&
+ SUBENT2=$(git ls-tree master sub) &&
+ rm -rf new &&
+diff --git a/transport.c b/transport.c
+index 679a35e..d2e3a90 100644
+--- a/transport.c
++++ b/transport.c
+@@ -964,8 +964,7 @@ static enum protocol_allow_config get_protocol_config(const char *type)
+ if (!strcmp(type, "http") ||
+ !strcmp(type, "https") ||
+ !strcmp(type, "git") ||
+- !strcmp(type, "ssh") ||
+- !strcmp(type, "file"))
++ !strcmp(type, "ssh"))
+ return PROTOCOL_ALLOW_ALWAYS;
+
+ /* known scary; err on the side of caution */
diff --git a/debian/patches/CVE-2022-39260.patch b/debian/patches/CVE-2022-39260.patch
new file mode 100644
index 0000000..259ba0a
--- /dev/null
+++ b/debian/patches/CVE-2022-39260.patch
@@ -0,0 +1,148 @@
+Origin: https://github.com/git/git/commit/32696a4cbe90929ae79ea442f5102c513ce3dfaa
+Origin: https://github.com/git/git/commit/71ad7fe1bcec2a115bd0ab187240348358aa7f21
+Origin: https://github.com/git/git/commit/0ca6ead81edd4fb1984b69aae87c1189e3025530
+Reviewed-by: Aron Xu <aron@debian.org>
+Last-Updated: 2023-01-26
+
+diff --git a/alias.c b/alias.c
+index c471538..00abde0 100644
+--- a/alias.c
++++ b/alias.c
+@@ -46,14 +46,16 @@ void list_aliases(struct string_list *list)
+
+ #define SPLIT_CMDLINE_BAD_ENDING 1
+ #define SPLIT_CMDLINE_UNCLOSED_QUOTE 2
++#define SPLIT_CMDLINE_ARGC_OVERFLOW 3
+ static const char *split_cmdline_errors[] = {
+ N_("cmdline ends with \\"),
+- N_("unclosed quote")
++ N_("unclosed quote"),
++ N_("too many arguments"),
+ };
+
+ int split_cmdline(char *cmdline, const char ***argv)
+ {
+- int src, dst, count = 0, size = 16;
++ size_t src, dst, count = 0, size = 16;
+ char quoted = 0;
+
+ ALLOC_ARRAY(*argv, size);
+@@ -96,6 +98,11 @@ int split_cmdline(char *cmdline, const char ***argv)
+ return -SPLIT_CMDLINE_UNCLOSED_QUOTE;
+ }
+
++ if (count >= INT_MAX) {
++ FREE_AND_NULL(*argv);
++ return -SPLIT_CMDLINE_ARGC_OVERFLOW;
++ }
++
+ ALLOC_GROW(*argv, count + 1, size);
+ (*argv)[count] = NULL;
+
+diff --git a/shell.c b/shell.c
+index cef7ffd..02cfd96 100644
+--- a/shell.c
++++ b/shell.c
+@@ -47,6 +47,8 @@ static void cd_to_homedir(void)
+ die("could not chdir to user's home directory");
+ }
+
++#define MAX_INTERACTIVE_COMMAND (4*1024*1024)
++
+ static void run_shell(void)
+ {
+ int done = 0;
+@@ -67,22 +69,46 @@ static void run_shell(void)
+ run_command_v_opt(help_argv, RUN_SILENT_EXEC_FAILURE);
+
+ do {
+- struct strbuf line = STRBUF_INIT;
+ const char *prog;
+ char *full_cmd;
+ char *rawargs;
++ size_t len;
+ char *split_args;
+ const char **argv;
+ int code;
+ int count;
+
+ fprintf(stderr, "git> ");
+- if (git_read_line_interactively(&line) == EOF) {
++
++ /*
++ * Avoid using a strbuf or git_read_line_interactively() here.
++ * We don't want to allocate arbitrary amounts of memory on
++ * behalf of a possibly untrusted client, and we're subject to
++ * OS limits on command length anyway.
++ */
++ fflush(stdout);
++ rawargs = xmalloc(MAX_INTERACTIVE_COMMAND);
++ if (!fgets(rawargs, MAX_INTERACTIVE_COMMAND, stdin)) {
+ fprintf(stderr, "\n");
+- strbuf_release(&line);
++ free(rawargs);
+ break;
+ }
+- rawargs = strbuf_detach(&line, NULL);
++ len = strlen(rawargs);
++
++ /*
++ * If we truncated due to our input buffer size, reject the
++ * command. That's better than running bogus input, and
++ * there's a good chance it's just malicious garbage anyway.
++ */
++ if (len >= MAX_INTERACTIVE_COMMAND - 1)
++ die("invalid command format: input too long");
++
++ if (len > 0 && rawargs[len - 1] == '\n') {
++ if (--len > 0 && rawargs[len - 1] == '\r')
++ --len;
++ rawargs[len] = '\0';
++ }
++
+ split_args = xstrdup(rawargs);
+ count = split_cmdline(split_args, &argv);
+ if (count < 0) {
+diff --git a/t/t9850-shell.sh b/t/t9850-shell.sh
+new file mode 100755
+index 0000000..cfc71c3
+--- /dev/null
++++ b/t/t9850-shell.sh
+@@ -0,0 +1,37 @@
++#!/bin/sh
++
++test_description='git shell tests'
++. ./test-lib.sh
++
++test_expect_success 'shell allows upload-pack' '
++ printf 0000 >input &&
++ git upload-pack . <input >expect &&
++ git shell -c "git-upload-pack $SQ.$SQ" <input >actual &&
++ test_cmp expect actual
++'
++
++test_expect_success 'shell forbids other commands' '
++ test_must_fail git shell -c "git config foo.bar baz"
++'
++
++test_expect_success 'shell forbids interactive use by default' '
++ test_must_fail git shell
++'
++
++test_expect_success 'shell allows interactive command' '
++ mkdir git-shell-commands &&
++ write_script git-shell-commands/ping <<-\EOF &&
++ echo pong
++ EOF
++ echo pong >expect &&
++ echo ping | git shell >actual &&
++ test_cmp expect actual
++'
++
++test_expect_success 'shell complains of overlong commands' '
++ perl -e "print \"a\" x 2**12 for (0..2**19)" |
++ test_must_fail git shell 2>err &&
++ grep "too long" err
++'
++
++test_done
diff --git a/debian/patches/CVE-2022-41903.patch b/debian/patches/CVE-2022-41903.patch
new file mode 100644
index 0000000..ea2a2c5
--- /dev/null
+++ b/debian/patches/CVE-2022-41903.patch
@@ -0,0 +1,445 @@
+Origin: https://github.com/git/git/commit/a244dc5b0a629290881641467c7a545de7508ab2
+Origin: https://github.com/git/git/commit/81dc898df9b4b4035534a927f3234a3839b698bf
+Origin: https://github.com/git/git/commit/b49f309aa16febeddb65e82526640a91bbba3be3
+Origin: https://github.com/git/git/commit/f6e0b9f38987ad5e47bab551f8760b70689a5905
+Origin: https://github.com/git/git/commit/1de69c0cdd388b0a5b7bdde0bfa0bda514a354b0
+Origin: https://github.com/git/git/commit/48050c42c73c28b0c001d63d11dffac7e116847b
+Origin: https://github.com/git/git/commit/522cc87fdc25449222a5894a428eebf4b8d5eaa9
+Origin: https://github.com/git/git/commit/17d23e8a3812a5ca3dd6564e74d5250f22e5d76d
+Origin: https://github.com/git/git/commit/937b71cc8b5b998963a7f9a33312ba3549d55510
+Origin: https://github.com/git/git/commit/81c2d4c3a5ba0e6ab8c348708441fed170e63a82
+Origin: https://github.com/git/git/commit/f930a2394303b902e2973f4308f96529f736b8bc
+Origin: https://github.com/git/git/commit/304a50adff6480ede46b68f7545baab542cbfb46
+Reviewed-by: Aron Xu <aron@debian.org>
+Last-Updated: 2023-01-26
+
+diff --git a/column.c b/column.c
+index 1261e18..fbf8863 100644
+--- a/column.c
++++ b/column.c
+@@ -23,7 +23,7 @@ struct column_data {
+ /* return length of 's' in letters, ANSI escapes stripped */
+ static int item_length(const char *s)
+ {
+- return utf8_strnwidth(s, -1, 1);
++ return utf8_strnwidth(s, strlen(s), 1);
+ }
+
+ /*
+diff --git a/git-compat-util.h b/git-compat-util.h
+index f505f81..0ac1b7f 100644
+--- a/git-compat-util.h
++++ b/git-compat-util.h
+@@ -918,6 +918,14 @@ static inline size_t st_sub(size_t a, size_t b)
+ return a - b;
+ }
+
++static inline int cast_size_t_to_int(size_t a)
++{
++ if (a > INT_MAX)
++ die("number too large to represent as int on this platform: %"PRIuMAX,
++ (uintmax_t)a);
++ return (int)a;
++}
++
+ #ifdef HAVE_ALLOCA_H
+ # include <alloca.h>
+ # define xalloca(size) (alloca(size))
+diff --git a/pretty.c b/pretty.c
+index 7a7708a..e228557 100644
+--- a/pretty.c
++++ b/pretty.c
+@@ -13,6 +13,13 @@
+ #include "gpg-interface.h"
+ #include "trailer.h"
+
++/*
++ * The limit for formatting directives, which enable the caller to append
++ * arbitrarily many bytes to the formatted buffer. This includes padding
++ * and wrapping formatters.
++ */
++#define FORMATTING_LIMIT (16 * 1024)
++
+ static char *user_format;
+ static struct cmt_fmt_map {
+ const char *name;
+@@ -915,7 +922,9 @@ static void strbuf_wrap(struct strbuf *sb, size_t pos,
+ if (pos)
+ strbuf_add(&tmp, sb->buf, pos);
+ strbuf_add_wrapped_text(&tmp, sb->buf + pos,
+- (int) indent1, (int) indent2, (int) width);
++ cast_size_t_to_int(indent1),
++ cast_size_t_to_int(indent2),
++ cast_size_t_to_int(width));
+ strbuf_swap(&tmp, sb);
+ strbuf_release(&tmp);
+ }
+@@ -1041,9 +1050,18 @@ static size_t parse_padding_placeholder(const char *placeholder,
+ const char *end = start + strcspn(start, ",)");
+ char *next;
+ int width;
+- if (!end || end == start)
++ if (!*end || end == start)
+ return 0;
+ width = strtol(start, &next, 10);
++
++ /*
++ * We need to limit the amount of padding, or otherwise this
++ * would allow the user to pad the buffer by arbitrarily many
++ * bytes and thus cause resource exhaustion.
++ */
++ if (width < -FORMATTING_LIMIT || width > FORMATTING_LIMIT)
++ return 0;
++
+ if (next == start || width == 0)
+ return 0;
+ if (width < 0) {
+@@ -1203,6 +1221,16 @@ static size_t format_commit_one(struct strbuf *sb, /* in UTF-8 */
+ if (*next != ')')
+ return 0;
+ }
++
++ /*
++ * We need to limit the format here as it allows the
++ * user to prepend arbitrarily many bytes to the buffer
++ * when rewrapping.
++ */
++ if (width > FORMATTING_LIMIT ||
++ indent1 > FORMATTING_LIMIT ||
++ indent2 > FORMATTING_LIMIT)
++ return 0;
+ rewrap_message_tail(sb, c, width, indent1, indent2);
+ return end - placeholder + 1;
+ } else
+@@ -1473,19 +1501,21 @@ static size_t format_and_pad_commit(struct strbuf *sb, /* in UTF-8 */
+ struct format_commit_context *c)
+ {
+ struct strbuf local_sb = STRBUF_INIT;
+- int total_consumed = 0, len, padding = c->padding;
++ size_t total_consumed = 0;
++ int len, padding = c->padding;
++
+ if (padding < 0) {
+ const char *start = strrchr(sb->buf, '\n');
+ int occupied;
+ if (!start)
+ start = sb->buf;
+- occupied = utf8_strnwidth(start, -1, 1);
++ occupied = utf8_strnwidth(start, strlen(start), 1);
+ occupied += c->pretty_ctx->graph_width;
+ padding = (-padding) - occupied;
+ }
+ while (1) {
+ int modifier = *placeholder == 'C';
+- int consumed = format_commit_one(&local_sb, placeholder, c);
++ size_t consumed = format_commit_one(&local_sb, placeholder, c);
+ total_consumed += consumed;
+
+ if (!modifier)
+@@ -1497,7 +1527,7 @@ static size_t format_and_pad_commit(struct strbuf *sb, /* in UTF-8 */
+ placeholder++;
+ total_consumed++;
+ }
+- len = utf8_strnwidth(local_sb.buf, -1, 1);
++ len = utf8_strnwidth(local_sb.buf, local_sb.len, 1);
+
+ if (c->flush_type == flush_left_and_steal) {
+ const char *ch = sb->buf + sb->len - 1;
+@@ -1512,7 +1542,7 @@ static size_t format_and_pad_commit(struct strbuf *sb, /* in UTF-8 */
+ if (*ch != 'm')
+ break;
+ p = ch - 1;
+- while (ch - p < 10 && *p != '\033')
++ while (p > sb->buf && ch - p < 10 && *p != '\033')
+ p--;
+ if (*p != '\033' ||
+ ch + 1 - p != display_mode_esc_sequence_len(p))
+@@ -1551,7 +1581,7 @@ static size_t format_and_pad_commit(struct strbuf *sb, /* in UTF-8 */
+ }
+ strbuf_addbuf(sb, &local_sb);
+ } else {
+- int sb_len = sb->len, offset = 0;
++ size_t sb_len = sb->len, offset = 0;
+ if (c->flush_type == flush_left)
+ offset = padding - len;
+ else if (c->flush_type == flush_both)
+@@ -1574,8 +1604,7 @@ static size_t format_commit_item(struct strbuf *sb, /* in UTF-8 */
+ const char *placeholder,
+ void *context)
+ {
+- int consumed;
+- size_t orig_len;
++ size_t consumed, orig_len;
+ enum {
+ NO_MAGIC,
+ ADD_LF_BEFORE_NON_EMPTY,
+@@ -1596,9 +1625,21 @@ static size_t format_commit_item(struct strbuf *sb, /* in UTF-8 */
+ default:
+ break;
+ }
+- if (magic != NO_MAGIC)
++ if (magic != NO_MAGIC) {
+ placeholder++;
+
++ switch (placeholder[0]) {
++ case 'w':
++ /*
++ * `%+w()` cannot ever expand to a non-empty string,
++ * and it potentially changes the layout of preceding
++ * contents. We're thus not able to handle the magic in
++ * this combination and refuse the pattern.
++ */
++ return 0;
++ };
++ }
++
+ orig_len = sb->len;
+ if (((struct format_commit_context *)context)->flush_type != no_flush)
+ consumed = format_and_pad_commit(sb, placeholder, context);
+diff --git a/t/t4205-log-pretty-formats.sh b/t/t4205-log-pretty-formats.sh
+index 204c149..84c61df 100755
+--- a/t/t4205-log-pretty-formats.sh
++++ b/t/t4205-log-pretty-formats.sh
+@@ -867,4 +867,80 @@ test_expect_success 'log --pretty=reference is colored appropriately' '
+ test_cmp expect actual
+ '
+
++test_expect_success 'log --pretty with space stealing' '
++ printf mm0 >expect &&
++ git log -1 --pretty="format:mm%>>|(1)%x30" >actual &&
++ test_cmp expect actual
++'
++
++test_expect_success 'log --pretty with invalid padding format' '
++ printf "%s%%<(20" "$(git rev-parse HEAD)" >expect &&
++ git log -1 --pretty="format:%H%<(20" >actual &&
++ test_cmp expect actual
++'
++
++test_expect_success 'log --pretty with magical wrapping directives' '
++ commit_id=$(git commit-tree HEAD^{tree} -m "describe me") &&
++ git tag describe-me $commit_id &&
++ printf "\n(tag:\ndescribe-me)%%+w(2)" >expect &&
++ git log -1 --pretty="format:%w(1)%+d%+w(2)" $commit_id >actual &&
++ test_cmp expect actual
++'
++
++test_expect_success SIZE_T_IS_64BIT 'log --pretty with overflowing wrapping directive' '
++ printf "%%w(2147483649,1,1)0" >expect &&
++ git log -1 --pretty="format:%w(2147483649,1,1)%x30" >actual &&
++ test_cmp expect actual &&
++ printf "%%w(1,2147483649,1)0" >expect &&
++ git log -1 --pretty="format:%w(1,2147483649,1)%x30" >actual &&
++ test_cmp expect actual &&
++ printf "%%w(1,1,2147483649)0" >expect &&
++ git log -1 --pretty="format:%w(1,1,2147483649)%x30" >actual &&
++ test_cmp expect actual
++'
++
++test_expect_success SIZE_T_IS_64BIT 'log --pretty with overflowing padding directive' '
++ printf "%%<(2147483649)0" >expect &&
++ git log -1 --pretty="format:%<(2147483649)%x30" >actual &&
++ test_cmp expect actual
++'
++
++test_expect_success 'log --pretty with padding and preceding control chars' '
++ printf "\20\20 0" >expect &&
++ git log -1 --pretty="format:%x10%x10%>|(4)%x30" >actual &&
++ test_cmp expect actual
++'
++
++test_expect_success 'log --pretty truncation with control chars' '
++ test_commit "$(printf "\20\20\20\20xxxx")" file contents commit-with-control-chars &&
++ printf "\20\20\20\20x.." >expect &&
++ git log -1 --pretty="format:%<(3,trunc)%s" commit-with-control-chars >actual &&
++ test_cmp expect actual
++'
++
++test_expect_success EXPENSIVE,SIZE_T_IS_64BIT 'log --pretty with huge commit message' '
++ # We only assert that this command does not crash. This needs to be
++ # executed with the address sanitizer to demonstrate failure.
++ git log -1 --pretty="format:%>(2147483646)%x41%41%>(2147483646)%x41" >/dev/null
++'
++
++test_expect_success EXPENSIVE,SIZE_T_IS_64BIT 'set up huge commit' '
++ test-tool genzeros 2147483649 | tr "\000" "1" >expect &&
++ huge_commit=$(git commit-tree -F expect HEAD^{tree})
++'
++
++test_expect_success EXPENSIVE,SIZE_T_IS_64BIT 'log --pretty with huge commit message' '
++ git log -1 --format="%B%<(1)%x30" $huge_commit >actual &&
++ echo 0 >>expect &&
++ test_cmp expect actual
++'
++
++test_expect_success EXPENSIVE,SIZE_T_IS_64BIT 'log --pretty with huge commit message does not cause allocation failure' '
++ test_must_fail git log -1 --format="%<(1)%B" $huge_commit 2>error &&
++ cat >expect <<-EOF &&
++ fatal: number too large to represent as int on this platform: 2147483649
++ EOF
++ test_cmp expect error
++'
++
+ test_done
+diff --git a/t/test-lib.sh b/t/test-lib.sh
+index 9fa7c1d..7d6e0f8 100644
+--- a/t/test-lib.sh
++++ b/t/test-lib.sh
+@@ -1686,6 +1686,10 @@ build_option () {
+ sed -ne "s/^$1: //p"
+ }
+
++test_lazy_prereq SIZE_T_IS_64BIT '
++ test 8 -eq "$(build_option sizeof-size_t)"
++'
++
+ test_lazy_prereq LONG_IS_64BIT '
+ test 8 -le "$(build_option sizeof-long)"
+ '
+diff --git a/utf8.c b/utf8.c
+index 5b39361..d8a16af 100644
+--- a/utf8.c
++++ b/utf8.c
+@@ -206,26 +206,34 @@ int utf8_width(const char **start, size_t *remainder_p)
+ * string, assuming that the string is utf8. Returns strlen() instead
+ * if the string does not look like a valid utf8 string.
+ */
+-int utf8_strnwidth(const char *string, int len, int skip_ansi)
++int utf8_strnwidth(const char *string, size_t len, int skip_ansi)
+ {
+- int width = 0;
+ const char *orig = string;
++ size_t width = 0;
+
+- if (len == -1)
+- len = strlen(string);
+ while (string && string < orig + len) {
+- int skip;
++ int glyph_width;
++ size_t skip;
++
+ while (skip_ansi &&
+ (skip = display_mode_esc_sequence_len(string)) != 0)
+ string += skip;
+- width += utf8_width(&string, NULL);
++
++ glyph_width = utf8_width(&string, NULL);
++ if (glyph_width > 0)
++ width += glyph_width;
+ }
+- return string ? width : len;
++
++ /*
++ * TODO: fix the interface of this function and `utf8_strwidth()` to
++ * return `size_t` instead of `int`.
++ */
++ return cast_size_t_to_int(string ? width : len);
+ }
+
+ int utf8_strwidth(const char *string)
+ {
+- return utf8_strnwidth(string, -1, 0);
++ return utf8_strnwidth(string, strlen(string), 0);
+ }
+
+ int is_utf8(const char *text)
+@@ -357,51 +365,52 @@ void strbuf_add_wrapped_bytes(struct strbuf *buf, const char *data, int len,
+ void strbuf_utf8_replace(struct strbuf *sb_src, int pos, int width,
+ const char *subst)
+ {
+- struct strbuf sb_dst = STRBUF_INIT;
+- char *src = sb_src->buf;
+- char *end = src + sb_src->len;
+- char *dst;
+- int w = 0, subst_len = 0;
++ const char *src = sb_src->buf, *end = sb_src->buf + sb_src->len;
++ struct strbuf dst;
++ int w = 0;
+
+- if (subst)
+- subst_len = strlen(subst);
+- strbuf_grow(&sb_dst, sb_src->len + subst_len);
+- dst = sb_dst.buf;
++ strbuf_init(&dst, sb_src->len);
+
+ while (src < end) {
+- char *old;
++ const char *old;
++ int glyph_width;
+ size_t n;
+
+ while ((n = display_mode_esc_sequence_len(src))) {
+- memcpy(dst, src, n);
++ strbuf_add(&dst, src, n);
+ src += n;
+- dst += n;
+ }
+
+ if (src >= end)
+ break;
+
+ old = src;
+- n = utf8_width((const char**)&src, NULL);
+- if (!src) /* broken utf-8, do nothing */
++ glyph_width = utf8_width((const char**)&src, NULL);
++ if (!src) /* broken utf-8, do nothing */
+ goto out;
+- if (n && w >= pos && w < pos + width) {
++
++ /*
++ * In case we see a control character we copy it into the
++ * buffer, but don't add it to the width.
++ */
++ if (glyph_width < 0)
++ glyph_width = 0;
++
++ if (glyph_width && w >= pos && w < pos + width) {
+ if (subst) {
+- memcpy(dst, subst, subst_len);
+- dst += subst_len;
++ strbuf_addstr(&dst, subst);
+ subst = NULL;
+ }
+- w += n;
+- continue;
++ } else {
++ strbuf_add(&dst, old, src - old);
+ }
+- memcpy(dst, old, src - old);
+- dst += src - old;
+- w += n;
++
++ w += glyph_width;
+ }
+- strbuf_setlen(&sb_dst, dst - sb_dst.buf);
+- strbuf_swap(sb_src, &sb_dst);
++
++ strbuf_swap(sb_src, &dst);
+ out:
+- strbuf_release(&sb_dst);
++ strbuf_release(&dst);
+ }
+
+ /*
+@@ -791,7 +800,7 @@ int skip_utf8_bom(char **text, size_t len)
+ void strbuf_utf8_align(struct strbuf *buf, align_type position, unsigned int width,
+ const char *s)
+ {
+- int slen = strlen(s);
++ size_t slen = strlen(s);
+ int display_len = utf8_strnwidth(s, slen, 0);
+ int utf8_compensation = slen - display_len;
+
+diff --git a/utf8.h b/utf8.h
+index fcd5167..6da1b6d 100644
+--- a/utf8.h
++++ b/utf8.h
+@@ -7,7 +7,7 @@ typedef unsigned int ucs_char_t; /* assuming 32bit int */
+
+ size_t display_mode_esc_sequence_len(const char *s);
+ int utf8_width(const char **start, size_t *remainder_p);
+-int utf8_strnwidth(const char *string, int len, int skip_ansi);
++int utf8_strnwidth(const char *string, size_t len, int skip_ansi);
+ int utf8_strwidth(const char *string);
+ int is_utf8(const char *text);
+ int is_encoding_utf8(const char *name);
diff --git a/debian/patches/CVE-2023-22490-1.patch b/debian/patches/CVE-2023-22490-1.patch
new file mode 100644
index 0000000..8593296
--- /dev/null
+++ b/debian/patches/CVE-2023-22490-1.patch
@@ -0,0 +1,174 @@
+From 58325b93c5b6212697b088371809e9948fee8052 Mon Sep 17 00:00:00 2001
+From: Taylor Blau <me@ttaylorr.com>
+Date: Tue, 24 Jan 2023 19:43:45 -0500
+Subject: [PATCH] t5619: demonstrate clone_local() with ambiguous transport
+
+When cloning a repository, Git must determine (a) what transport
+mechanism to use, and (b) whether or not the clone is local.
+
+Since f38aa83f9a (use local cloning if insteadOf makes a local URL,
+2014-07-17), the latter check happens after the remote has been
+initialized, and references the remote's URL instead of the local path.
+This is done to make it possible for a `url.<base>.insteadOf` rule to
+convert a remote URL into a local one, in which case the `clone_local()`
+mechanism should be used.
+
+However, with a specially crafted repository, Git can be tricked into
+using a non-local transport while still setting `is_local` to "1" and
+using the `clone_local()` optimization. The below test case
+demonstrates such an instance, and shows that it can be used to include
+arbitrary (known) paths in the working copy of a cloned repository on a
+victim's machine[^1], even if local file clones are forbidden by
+`protocol.file.allow`.
+
+This happens in a few parts:
+
+ 1. We first call `get_repo_path()` to see if the remote is a local
+ path. If it is, we replace the repo name with its absolute path.
+
+ 2. We then call `transport_get()` on the repo name and decide how to
+ access it. If it was turned into an absolute path in the previous
+ step, then we should always treat it like a file.
+
+ 3. We use `get_repo_path()` again, and set `is_local` as appropriate.
+ But it's already too late to rewrite the repo name as an absolute
+ path, since we've already fed it to the transport code.
+
+The attack works by including a submodule whose URL corresponds to a
+path on disk. In the below example, the repository "sub" is reachable
+via the dumb HTTP protocol at (something like):
+
+ http://127.0.0.1:NNNN/dumb/sub.git
+
+However, the path "http:/127.0.0.1:NNNN/dumb" (that is, a top-level
+directory called "http:", then nested directories "127.0.0.1:NNNN", and
+"dumb") exists within the repository, too.
+
+To determine this, it first picks the appropriate transport, which is
+dumb HTTP. It then uses the remote's URL in order to determine whether
+the repository exists locally on disk. However, the malicious repository
+also contains an embedded stub repository which is the target of a
+symbolic link at the local path corresponding to the "sub" repository on
+disk (i.e., there is a symbolic link at "http:/127.0.0.1/dumb/sub.git",
+pointing to the stub repository via ".git/modules/sub/../../../repo").
+
+This stub repository fools Git into thinking that a local repository
+exists at that URL and thus can be cloned locally. The affected call is
+in `get_repo_path()`, which in turn calls `get_repo_path_1()`, which
+locates a valid repository at that target.
+
+This then causes Git to set the `is_local` variable to "1", and in turn
+instructs Git to clone the repository using its local clone optimization
+via the `clone_local()` function.
+
+The exploit comes into play because the stub repository's top-level
+"$GIT_DIR/objects" directory is a symbolic link which can point to an
+arbitrary path on the victim's machine. `clone_local()` resolves the
+top-level "objects" directory through a `stat(2)` call, meaning that we
+read through the symbolic link and copy or hardlink the directory
+contents at the destination of the link.
+
+In other words, we can get steps (1) and (3) to disagree by leveraging
+the dangling symlink to pick a non-local transport in the first step,
+and then set is_local to "1" in the third step when cloning with
+`--separate-git-dir`, which makes the symlink non-dangling.
+
+This can result in data-exfiltration on the victim's machine when
+sensitive data is at a known path (e.g., "/home/$USER/.ssh").
+
+The appropriate fix is two-fold:
+
+ - Resolve the transport later on (to avoid using the local
+ clone optimization with a non-local transport).
+
+ - Avoid reading through the top-level "objects" directory when
+ (correctly) using the clone_local() optimization.
+
+This patch merely demonstrates the issue. The following two patches will
+implement each part of the above fix, respectively.
+
+[^1]: Provided that any target directory does not contain symbolic
+ links, in which case the changes from 6f054f9fb3 (builtin/clone.c:
+ disallow `--local` clones with symlinks, 2022-07-28) will abort the
+ clone.
+
+Reported-by: yvvdwf <yvvdwf@gmail.com>
+Signed-off-by: Taylor Blau <me@ttaylorr.com>
+Signed-off-by: Junio C Hamano <gitster@pobox.com>
+---
+ t/t5619-clone-local-ambiguous-transport.sh | 63 ++++++++++++++++++++++
+ 1 file changed, 63 insertions(+)
+ create mode 100755 t/t5619-clone-local-ambiguous-transport.sh
+
+diff --git a/t/t5619-clone-local-ambiguous-transport.sh b/t/t5619-clone-local-ambiguous-transport.sh
+new file mode 100755
+index 0000000000..7ebd31a150
+--- /dev/null
++++ b/t/t5619-clone-local-ambiguous-transport.sh
+@@ -0,0 +1,63 @@
++#!/bin/sh
++
++test_description='test local clone with ambiguous transport'
++
++. ./test-lib.sh
++. "$TEST_DIRECTORY/lib-httpd.sh"
++
++if ! test_have_prereq SYMLINKS
++then
++ skip_all='skipping test, symlink support unavailable'
++ test_done
++fi
++
++start_httpd
++
++REPO="$HTTPD_DOCUMENT_ROOT_PATH/sub.git"
++URI="$HTTPD_URL/dumb/sub.git"
++
++test_expect_success 'setup' '
++ mkdir -p sensitive &&
++ echo "secret" >sensitive/secret &&
++
++ git init --bare "$REPO" &&
++ test_commit_bulk -C "$REPO" --ref=main 1 &&
++
++ git -C "$REPO" update-ref HEAD main &&
++ git -C "$REPO" update-server-info &&
++
++ git init malicious &&
++ (
++ cd malicious &&
++
++ git submodule add "$URI" &&
++
++ mkdir -p repo/refs &&
++ touch repo/refs/.gitkeep &&
++ printf "ref: refs/heads/a" >repo/HEAD &&
++ ln -s "$(cd .. && pwd)/sensitive" repo/objects &&
++
++ mkdir -p "$HTTPD_URL/dumb" &&
++ ln -s "../../../.git/modules/sub/../../../repo/" "$URI" &&
++
++ git add . &&
++ git commit -m "initial commit"
++ ) &&
++
++ # Delete all of the references in our malicious submodule to
++ # avoid the client attempting to checkout any objects (which
++ # will be missing, and thus will cause the clone to fail before
++ # we can trigger the exploit).
++ git -C "$REPO" for-each-ref --format="delete %(refname)" >in &&
++ git -C "$REPO" update-ref --stdin <in &&
++ git -C "$REPO" update-server-info
++'
++
++test_expect_failure 'ambiguous transport does not lead to arbitrary file-inclusion' '
++ git clone malicious clone &&
++ git -C clone submodule update --init &&
++
++ test_path_is_missing clone/.git/modules/sub/objects/secret
++'
++
++test_done
+--
+2.30.2
+
diff --git a/debian/patches/CVE-2023-22490-2.patch b/debian/patches/CVE-2023-22490-2.patch
new file mode 100644
index 0000000..407f895
--- /dev/null
+++ b/debian/patches/CVE-2023-22490-2.patch
@@ -0,0 +1,117 @@
+From cf8f6ce02a13f4d1979a53241afbee15a293fce9 Mon Sep 17 00:00:00 2001
+From: Taylor Blau <me@ttaylorr.com>
+Date: Tue, 24 Jan 2023 19:43:48 -0500
+Subject: [PATCH] clone: delay picking a transport until after get_repo_path()
+
+In the previous commit, t5619 demonstrates an issue where two calls to
+`get_repo_path()` could trick Git into using its local clone mechanism
+in conjunction with a non-local transport.
+
+That sequence is:
+
+ - the starting state is that the local path https:/example.com/foo is a
+ symlink that points to ../../../.git/modules/foo. So it's dangling.
+
+ - get_repo_path() sees that no such path exists (because it's
+ dangling), and thus we do not canonicalize it into an absolute path
+
+ - because we're using --separate-git-dir, we create .git/modules/foo.
+ Now our symlink is no longer dangling!
+
+ - we pass the url to transport_get(), which sees it as an https URL.
+
+ - we call get_repo_path() again, on the url. This second call was
+ introduced by f38aa83f9a (use local cloning if insteadOf makes a
+ local URL, 2014-07-17). The idea is that we want to pull the url
+ fresh from the remote.c API, because it will apply any aliases.
+
+And of course now it sees that there is a local file, which is a
+mismatch with the transport we already selected.
+
+The issue in the above sequence is calling `transport_get()` before
+deciding whether or not the repository is indeed local, and not passing
+in an absolute path if it is local.
+
+This is reminiscent of a similar bug report in [1], where it was
+suggested to perform the `insteadOf` lookup earlier. Taking that
+approach may not be as straightforward, since the intent is to store the
+original URL in the config, but to actually fetch from the insteadOf
+one, so conflating the two early on is a non-starter.
+
+Note: we pass the path returned by `get_repo_path(remote->url[0])`,
+which should be the same as `repo_name` (aside from any `insteadOf`
+rewrites).
+
+We *could* pass `absolute_pathdup()` of the same argument, which
+86521acaca (Bring local clone's origin URL in line with that of a remote
+clone, 2008-09-01) indicates may differ depending on the presence of
+".git/" for a non-bare repo. That matters for forming relative submodule
+paths, but doesn't matter for the second call, since we're just feeding
+it to the transport code, which is fine either way.
+
+[1]: https://lore.kernel.org/git/CAMoD=Bi41mB3QRn3JdZL-FGHs4w3C2jGpnJB-CqSndO7FMtfzA@mail.gmail.com/
+
+Signed-off-by: Jeff King <peff@peff.net>
+Signed-off-by: Taylor Blau <me@ttaylorr.com>
+Signed-off-by: Junio C Hamano <gitster@pobox.com>
+---
+ builtin/clone.c | 8 ++++----
+ t/t5619-clone-local-ambiguous-transport.sh | 15 +++++++++++----
+ 2 files changed, 15 insertions(+), 8 deletions(-)
+
+diff --git a/builtin/clone.c b/builtin/clone.c
+index e626073b1f..c042b2e256 100644
+--- a/builtin/clone.c
++++ b/builtin/clone.c
+@@ -1201,10 +1201,6 @@ int cmd_clone(int argc, const char **argv, const char *prefix)
+ refspec_appendf(&remote->fetch, "+%s*:%s*", src_ref_prefix,
+ branch_top.buf);
+
+- transport = transport_get(remote, remote->url[0]);
+- transport_set_verbosity(transport, option_verbosity, option_progress);
+- transport->family = family;
+-
+ path = get_repo_path(remote->url[0], &is_bundle);
+ is_local = option_local != 0 && path && !is_bundle;
+ if (is_local) {
+@@ -1224,6 +1220,10 @@ int cmd_clone(int argc, const char **argv, const char *prefix)
+ }
+ if (option_local > 0 && !is_local)
+ warning(_("--local is ignored"));
++
++ transport = transport_get(remote, path ? path : remote->url[0]);
++ transport_set_verbosity(transport, option_verbosity, option_progress);
++ transport->family = family;
+ transport->cloning = 1;
+
+ transport_set_option(transport, TRANS_OPT_KEEP, "yes");
+diff --git a/t/t5619-clone-local-ambiguous-transport.sh b/t/t5619-clone-local-ambiguous-transport.sh
+index 7ebd31a150..cce62bf78d 100755
+--- a/t/t5619-clone-local-ambiguous-transport.sh
++++ b/t/t5619-clone-local-ambiguous-transport.sh
+@@ -53,11 +53,18 @@ test_expect_success 'setup' '
+ git -C "$REPO" update-server-info
+ '
+
+-test_expect_failure 'ambiguous transport does not lead to arbitrary file-inclusion' '
++test_expect_success 'ambiguous transport does not lead to arbitrary file-inclusion' '
+ git clone malicious clone &&
+- git -C clone submodule update --init &&
+-
+- test_path_is_missing clone/.git/modules/sub/objects/secret
++ test_must_fail git -C clone submodule update --init 2>err &&
++
++ test_path_is_missing clone/.git/modules/sub/objects/secret &&
++ # We would actually expect "transport .file. not allowed" here,
++ # but due to quirks of the URL detection in Git, we mis-parse
++ # the absolute path as a bogus URL and die before that step.
++ #
++ # This works for now, and if we ever fix the URL detection, it
++ # is OK to change this to detect the transport error.
++ grep "protocol .* is not supported" err
+ '
+
+ test_done
+--
+2.30.2
+
diff --git a/debian/patches/CVE-2023-22490-3.patch b/debian/patches/CVE-2023-22490-3.patch
new file mode 100644
index 0000000..be7e280
--- /dev/null
+++ b/debian/patches/CVE-2023-22490-3.patch
@@ -0,0 +1,150 @@
+From bffc762f87ae8d18c6001bf0044a76004245754c Mon Sep 17 00:00:00 2001
+From: Taylor Blau <me@ttaylorr.com>
+Date: Tue, 24 Jan 2023 19:43:51 -0500
+Subject: [PATCH] dir-iterator: prevent top-level symlinks without
+ FOLLOW_SYMLINKS
+
+When using the dir_iterator API, we first stat(2) the base path, and
+then use that as a starting point to enumerate the directory's contents.
+
+If the directory contains symbolic links, we will immediately die() upon
+encountering them without the `FOLLOW_SYMLINKS` flag. The same is not
+true when resolving the top-level directory, though.
+
+As explained in a previous commit, this oversight in 6f054f9fb3
+(builtin/clone.c: disallow `--local` clones with symlinks, 2022-07-28)
+can be used as an attack vector to include arbitrary files on a victim's
+filesystem from outside of the repository.
+
+Prevent resolving top-level symlinks unless the FOLLOW_SYMLINKS flag is
+given, which will cause clones of a repository with a symlink'd
+"$GIT_DIR/objects" directory to fail.
+
+Signed-off-by: Taylor Blau <me@ttaylorr.com>
+Signed-off-by: Junio C Hamano <gitster@pobox.com>
+---
+ dir-iterator.c | 13 +++++++++----
+ dir-iterator.h | 5 +++++
+ t/t0066-dir-iterator.sh | 27 ++++++++++++++++++++++++++-
+ t/t5604-clone-reference.sh | 16 ++++++++++++++++
+ 4 files changed, 56 insertions(+), 5 deletions(-)
+
+diff --git a/dir-iterator.c b/dir-iterator.c
+index b17e9f970a..3764dd81a1 100644
+--- a/dir-iterator.c
++++ b/dir-iterator.c
+@@ -203,7 +203,7 @@ struct dir_iterator *dir_iterator_begin(const char *path, unsigned int flags)
+ {
+ struct dir_iterator_int *iter = xcalloc(1, sizeof(*iter));
+ struct dir_iterator *dir_iterator = &iter->base;
+- int saved_errno;
++ int saved_errno, err;
+
+ strbuf_init(&iter->base.path, PATH_MAX);
+ strbuf_addstr(&iter->base.path, path);
+@@ -213,10 +213,15 @@ struct dir_iterator *dir_iterator_begin(const char *path, unsigned int flags)
+ iter->flags = flags;
+
+ /*
+- * Note: stat already checks for NULL or empty strings and
+- * inexistent paths.
++ * Note: stat/lstat already checks for NULL or empty strings and
++ * nonexistent paths.
+ */
+- if (stat(iter->base.path.buf, &iter->base.st) < 0) {
++ if (iter->flags & DIR_ITERATOR_FOLLOW_SYMLINKS)
++ err = stat(iter->base.path.buf, &iter->base.st);
++ else
++ err = lstat(iter->base.path.buf, &iter->base.st);
++
++ if (err < 0) {
+ saved_errno = errno;
+ goto error_out;
+ }
+diff --git a/dir-iterator.h b/dir-iterator.h
+index 08229157c6..e3b6ff2800 100644
+--- a/dir-iterator.h
++++ b/dir-iterator.h
+@@ -61,6 +61,11 @@
+ * not the symlinks themselves, which is the default behavior. Broken
+ * symlinks are ignored.
+ *
++ * Note: setting DIR_ITERATOR_FOLLOW_SYMLINKS affects resolving the
++ * starting path as well (e.g., attempting to iterate starting at a
++ * symbolic link pointing to a directory without FOLLOW_SYMLINKS will
++ * result in an error).
++ *
+ * Warning: circular symlinks are also followed when
+ * DIR_ITERATOR_FOLLOW_SYMLINKS is set. The iteration may end up with
+ * an ELOOP if they happen and DIR_ITERATOR_PEDANTIC is set.
+diff --git a/t/t0066-dir-iterator.sh b/t/t0066-dir-iterator.sh
+index 92910e4e6c..c826f60f6d 100755
+--- a/t/t0066-dir-iterator.sh
++++ b/t/t0066-dir-iterator.sh
+@@ -109,7 +109,9 @@ test_expect_success SYMLINKS 'setup dirs with symlinks' '
+ mkdir -p dir5/a/c &&
+ ln -s ../c dir5/a/b/d &&
+ ln -s ../ dir5/a/b/e &&
+- ln -s ../../ dir5/a/b/f
++ ln -s ../../ dir5/a/b/f &&
++
++ ln -s dir4 dir6
+ '
+
+ test_expect_success SYMLINKS 'dir-iterator should not follow symlinks by default' '
+@@ -145,4 +147,27 @@ test_expect_success SYMLINKS 'dir-iterator should follow symlinks w/ follow flag
+ test_cmp expected-follow-sorted-output actual-follow-sorted-output
+ '
+
++test_expect_success SYMLINKS 'dir-iterator does not resolve top-level symlinks' '
++ test_must_fail test-tool dir-iterator ./dir6 >out &&
++
++ grep "ENOTDIR" out
++'
++
++test_expect_success SYMLINKS 'dir-iterator resolves top-level symlinks w/ follow flag' '
++ cat >expected-follow-sorted-output <<-EOF &&
++ [d] (a) [a] ./dir6/a
++ [d] (a/f) [f] ./dir6/a/f
++ [d] (a/f/c) [c] ./dir6/a/f/c
++ [d] (b) [b] ./dir6/b
++ [d] (b/c) [c] ./dir6/b/c
++ [f] (a/d) [d] ./dir6/a/d
++ [f] (a/e) [e] ./dir6/a/e
++ EOF
++
++ test-tool dir-iterator --follow-symlinks ./dir6 >out &&
++ sort out >actual-follow-sorted-output &&
++
++ test_cmp expected-follow-sorted-output actual-follow-sorted-output
++'
++
+ test_done
+diff --git a/t/t5604-clone-reference.sh b/t/t5604-clone-reference.sh
+index 9d32f1c4a4..4ff21d7ccf 100755
+--- a/t/t5604-clone-reference.sh
++++ b/t/t5604-clone-reference.sh
+@@ -341,4 +341,20 @@ test_expect_success SYMLINKS 'clone repo with symlinked or unknown files at obje
+ test_must_be_empty T--shared.objects-symlinks.raw
+ '
+
++test_expect_success SYMLINKS 'clone repo with symlinked objects directory' '
++ test_when_finished "rm -fr sensitive malicious" &&
++
++ mkdir -p sensitive &&
++ echo "secret" >sensitive/file &&
++
++ git init malicious &&
++ rm -fr malicious/.git/objects &&
++ ln -s "$(pwd)/sensitive" ./malicious/.git/objects &&
++
++ test_must_fail git clone --local malicious clone 2>err &&
++
++ test_path_is_missing clone &&
++ grep "failed to start iterator over" err
++'
++
+ test_done
+--
+2.30.2
+
diff --git a/debian/patches/CVE-2023-23946.patch b/debian/patches/CVE-2023-23946.patch
new file mode 100644
index 0000000..17c51d7
--- /dev/null
+++ b/debian/patches/CVE-2023-23946.patch
@@ -0,0 +1,179 @@
+From fade728df1221598f42d391cf377e9e84a32053f Mon Sep 17 00:00:00 2001
+From: Patrick Steinhardt <ps@pks.im>
+Date: Thu, 2 Feb 2023 11:54:34 +0100
+Subject: [PATCH] apply: fix writing behind newly created symbolic links
+
+When writing files git-apply(1) initially makes sure that none of the
+files it is about to create are behind a symlink:
+
+```
+ $ git init repo
+ Initialized empty Git repository in /tmp/repo/.git/
+ $ cd repo/
+ $ ln -s dir symlink
+ $ git apply - <<EOF
+ diff --git a/symlink/file b/symlink/file
+ new file mode 100644
+ index 0000000..e69de29
+ EOF
+ error: affected file 'symlink/file' is beyond a symbolic link
+```
+
+This safety mechanism is crucial to ensure that we don't write outside
+of the repository's working directory. It can be fooled though when the
+patch that is being applied creates the symbolic link in the first
+place, which can lead to writing files in arbitrary locations.
+
+Fix this by checking whether the path we're about to create is
+beyond a symlink or not. Tightening these checks like this should be
+fine as we already have these precautions in Git as explained
+above. Ideally, we should update the check we do up-front before
+starting to reflect the computed changes to the working tree so that
+we catch this case as well, but as part of embargoed security work,
+adding an equivalent check just before we try to write out a file
+should serve us well as a reasonable first step.
+
+Digging back into history shows that this vulnerability has existed
+since at least Git v2.9.0. As Git v2.8.0 and older don't build on my
+system anymore I cannot tell whether older versions are affected, as
+well.
+
+Reported-by: Joern Schneeweisz <jschneeweisz@gitlab.com>
+Signed-off-by: Patrick Steinhardt <ps@pks.im>
+Signed-off-by: Junio C Hamano <gitster@pobox.com>
+---
+ apply.c | 27 ++++++++++++++
+ t/t4115-apply-symlink.sh | 81 ++++++++++++++++++++++++++++++++++++++++
+ 2 files changed, 108 insertions(+)
+
+diff --git a/apply.c b/apply.c
+index 668b16e989..d80382c940 100644
+--- a/apply.c
++++ b/apply.c
+@@ -4400,6 +4400,33 @@ static int create_one_file(struct apply_state *state,
+ if (state->cached)
+ return 0;
+
++ /*
++ * We already try to detect whether files are beyond a symlink in our
++ * up-front checks. But in the case where symlinks are created by any
++ * of the intermediate hunks it can happen that our up-front checks
++ * didn't yet see the symlink, but at the point of arriving here there
++ * in fact is one. We thus repeat the check for symlinks here.
++ *
++ * Note that this does not make the up-front check obsolete as the
++ * failure mode is different:
++ *
++ * - The up-front checks cause us to abort before we have written
++ * anything into the working directory. So when we exit this way the
++ * working directory remains clean.
++ *
++ * - The checks here happen in the middle of the action where we have
++ * already started to apply the patch. The end result will be a dirty
++ * working directory.
++ *
++ * Ideally, we should update the up-front checks to catch what would
++ * happen when we apply the patch before we damage the working tree.
++ * We have all the information necessary to do so. But for now, as a
++ * part of embargoed security work, having this check would serve as a
++ * reasonable first step.
++ */
++ if (path_is_beyond_symlink(state, path))
++ return error(_("affected file '%s' is beyond a symbolic link"), path);
++
+ res = try_create_file(state, path, mode, buf, size);
+ if (res < 0)
+ return -1;
+diff --git a/t/t4115-apply-symlink.sh b/t/t4115-apply-symlink.sh
+index 872fcda6cb..1acb7b2582 100755
+--- a/t/t4115-apply-symlink.sh
++++ b/t/t4115-apply-symlink.sh
+@@ -44,4 +44,85 @@ test_expect_success 'apply --index symlink patch' '
+
+ '
+
++test_expect_success 'symlink setup' '
++ ln -s .git symlink &&
++ git add symlink &&
++ git commit -m "add symlink"
++'
++
++test_expect_success SYMLINKS 'symlink escape when creating new files' '
++ test_when_finished "git reset --hard && git clean -dfx" &&
++
++ cat >patch <<-EOF &&
++ diff --git a/symlink b/renamed-symlink
++ similarity index 100%
++ rename from symlink
++ rename to renamed-symlink
++ --
++ diff --git /dev/null b/renamed-symlink/create-me
++ new file mode 100644
++ index 0000000..039727e
++ --- /dev/null
++ +++ b/renamed-symlink/create-me
++ @@ -0,0 +1,1 @@
++ +busted
++ EOF
++
++ test_must_fail git apply patch 2>stderr &&
++ cat >expected_stderr <<-EOF &&
++ error: affected file ${SQ}renamed-symlink/create-me${SQ} is beyond a symbolic link
++ EOF
++ test_cmp expected_stderr stderr &&
++ ! test_path_exists .git/create-me
++'
++
++test_expect_success SYMLINKS 'symlink escape when modifying file' '
++ test_when_finished "git reset --hard && git clean -dfx" &&
++ touch .git/modify-me &&
++
++ cat >patch <<-EOF &&
++ diff --git a/symlink b/renamed-symlink
++ similarity index 100%
++ rename from symlink
++ rename to renamed-symlink
++ --
++ diff --git a/renamed-symlink/modify-me b/renamed-symlink/modify-me
++ index 1111111..2222222 100644
++ --- a/renamed-symlink/modify-me
++ +++ b/renamed-symlink/modify-me
++ @@ -0,0 +1,1 @@
++ +busted
++ EOF
++
++ test_must_fail git apply patch 2>stderr &&
++ cat >expected_stderr <<-EOF &&
++ error: renamed-symlink/modify-me: No such file or directory
++ EOF
++ test_cmp expected_stderr stderr &&
++ test_must_be_empty .git/modify-me
++'
++
++test_expect_success SYMLINKS 'symlink escape when deleting file' '
++ test_when_finished "git reset --hard && git clean -dfx && rm .git/delete-me" &&
++ touch .git/delete-me &&
++
++ cat >patch <<-EOF &&
++ diff --git a/symlink b/renamed-symlink
++ similarity index 100%
++ rename from symlink
++ rename to renamed-symlink
++ --
++ diff --git a/renamed-symlink/delete-me b/renamed-symlink/delete-me
++ deleted file mode 100644
++ index 1111111..0000000 100644
++ EOF
++
++ test_must_fail git apply patch 2>stderr &&
++ cat >expected_stderr <<-EOF &&
++ error: renamed-symlink/delete-me: No such file or directory
++ EOF
++ test_cmp expected_stderr stderr &&
++ test_path_is_file .git/delete-me
++'
++
+ test_done
+--
+2.30.2
+
diff --git a/debian/patches/series b/debian/patches/series
new file mode 100644
index 0000000..a89fc6b
--- /dev/null
+++ b/debian/patches/series
@@ -0,0 +1,10 @@
+CVE-2022-24765.patch
+CVE-2022-29187.patch
+CVE-2022-23521.patch
+CVE-2022-39253.patch
+CVE-2022-39260.patch
+CVE-2022-41903.patch
+CVE-2023-22490-1.patch
+CVE-2023-22490-2.patch
+CVE-2023-22490-3.patch
+CVE-2023-23946.patch