diff options
Diffstat (limited to 'debian/patches')
-rw-r--r-- | debian/patches/CVE-2022-23521.patch | 280 | ||||
-rw-r--r-- | debian/patches/CVE-2022-24765.patch | 499 | ||||
-rw-r--r-- | debian/patches/CVE-2022-29187.patch | 340 | ||||
-rw-r--r-- | debian/patches/CVE-2022-39253.patch | 969 | ||||
-rw-r--r-- | debian/patches/CVE-2022-39260.patch | 148 | ||||
-rw-r--r-- | debian/patches/CVE-2022-41903.patch | 445 | ||||
-rw-r--r-- | debian/patches/CVE-2023-22490-1.patch | 174 | ||||
-rw-r--r-- | debian/patches/CVE-2023-22490-2.patch | 117 | ||||
-rw-r--r-- | debian/patches/CVE-2023-22490-3.patch | 150 | ||||
-rw-r--r-- | debian/patches/CVE-2023-23946.patch | 179 | ||||
-rw-r--r-- | debian/patches/series | 10 |
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("ed, 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("ed, 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 |