diff --git a/backport-CVE-2024-32002-dir-introduce-readdir_skip_dot_and_dotdot-helper.patch b/backport-CVE-2024-32002-dir-introduce-readdir_skip_dot_and_dotdot-helper.patch new file mode 100644 index 0000000000000000000000000000000000000000..15d8533f46493400d9fe6541db0b21725316b6b9 --- /dev/null +++ b/backport-CVE-2024-32002-dir-introduce-readdir_skip_dot_and_dotdot-helper.patch @@ -0,0 +1,265 @@ +From 04810fdebbf3cfd509cec1e7103de502e3193970 Mon Sep 17 00:00:00 2001 +From: Elijah Newren +Date: Thu, 27 May 2021 04:53:56 +0000 +Subject: [PATCH] dir: introduce readdir_skip_dot_and_dotdot() helper + +Many places in the code were doing + while ((d = readdir(dir)) != NULL) { + if (is_dot_or_dotdot(d->d_name)) + continue; + ...process d... + } +Introduce a readdir_skip_dot_and_dotdot() helper to make that a one-liner: + while ((d = readdir_skip_dot_and_dotdot(dir)) != NULL) { + ...process d... + } + +This helper particularly simplifies checks for empty directories. + +Also use this helper in read_cached_dir() so that our statistics are +consistent across platforms. (In other words, read_cached_dir() should +have been using is_dot_or_dotdot() and skipping such entries, but did +not and left it to treat_path() to detect and mark such entries as +path_none.) + +Signed-off-by: Elijah Newren +Signed-off-by: Junio C Hamano +Signed-off-by: qiaojijun +--- + builtin/clean.c | 4 +--- + builtin/worktree.c | 4 +--- + diff-no-index.c | 5 ++--- + dir.c | 25 ++++++++++++++++--------- + dir.h | 2 ++ + entry.c | 5 +---- + notes-merge.c | 5 +---- + packfile.c | 5 +---- + rerere.c | 4 +--- + worktree.c | 12 +++--------- + 10 files changed, 29 insertions(+), 42 deletions(-) + +diff --git a/builtin/clean.c b/builtin/clean.c +index 4ca12bc..e9d4021 100644 +--- a/builtin/clean.c ++++ b/builtin/clean.c +@@ -189,10 +189,8 @@ static int remove_dirs(struct strbuf *path, const char *prefix, int force_flag, + strbuf_complete(path, '/'); + + len = path->len; +- while ((e = readdir(dir)) != NULL) { ++ while ((e = readdir_skip_dot_and_dotdot(dir)) != NULL) { + struct stat st; +- if (is_dot_or_dotdot(e->d_name)) +- continue; + + strbuf_setlen(path, len); + strbuf_addstr(path, e->d_name); +diff --git a/builtin/worktree.c b/builtin/worktree.c +index d99db35..3be644e 100644 +--- a/builtin/worktree.c ++++ b/builtin/worktree.c +@@ -140,9 +140,7 @@ static void prune_worktrees(void) + struct dirent *d; + if (!dir) + return; +- while ((d = readdir(dir)) != NULL) { +- if (is_dot_or_dotdot(d->d_name)) +- continue; ++ while ((d = readdir_skip_dot_and_dotdot(dir)) != NULL) { + strbuf_reset(&reason); + if (!prune_worktree(d->d_name, &reason)) + continue; +diff --git a/diff-no-index.c b/diff-no-index.c +index 7814eab..e5cc878 100644 +--- a/diff-no-index.c ++++ b/diff-no-index.c +@@ -26,9 +26,8 @@ static int read_directory_contents(const char *path, struct string_list *list) + if (!(dir = opendir(path))) + return error("Could not open directory %s", path); + +- while ((e = readdir(dir))) +- if (!is_dot_or_dotdot(e->d_name)) +- string_list_insert(list, e->d_name); ++ while ((e = readdir_skip_dot_and_dotdot(dir))) ++ string_list_insert(list, e->d_name); + + closedir(dir); + return 0; +diff --git a/dir.c b/dir.c +index d97e955..bc7a6df 100644 +--- a/dir.c ++++ b/dir.c +@@ -54,6 +54,17 @@ static enum path_treatment read_directory_recursive(struct dir_struct *dir, + static int resolve_dtype(int dtype, struct index_state *istate, + const char *path, int len); + ++struct dirent *readdir_skip_dot_and_dotdot(DIR *dirp) ++{ ++ struct dirent *e; ++ ++ while ((e = readdir(dirp)) != NULL) { ++ if (!is_dot_or_dotdot(e->d_name)) ++ break; ++ } ++ return e; ++} ++ + int count_slashes(const char *s) + { + int cnt = 0; +@@ -2287,7 +2298,7 @@ static int read_cached_dir(struct cached_dir *cdir) + struct dirent *de; + + if (cdir->fdir) { +- de = readdir(cdir->fdir); ++ de = readdir_skip_dot_and_dotdot(cdir->fdir); + if (!de) { + cdir->d_name = NULL; + cdir->d_type = DT_UNKNOWN; +@@ -2856,11 +2867,9 @@ int is_empty_dir(const char *path) + if (!dir) + return 0; + +- while ((e = readdir(dir)) != NULL) +- if (!is_dot_or_dotdot(e->d_name)) { +- ret = 0; +- break; +- } ++ e = readdir_skip_dot_and_dotdot(dir); ++ if (e) ++ ret = 0; + + closedir(dir); + return ret; +@@ -2900,10 +2909,8 @@ static int remove_dir_recurse(struct strbuf *path, int flag, int *kept_up) + strbuf_complete(path, '/'); + + len = path->len; +- while ((e = readdir(dir)) != NULL) { ++ while ((e = readdir_skip_dot_and_dotdot(dir)) != NULL) { + struct stat st; +- if (is_dot_or_dotdot(e->d_name)) +- continue; + + strbuf_setlen(path, len); + strbuf_addstr(path, e->d_name); +diff --git a/dir.h b/dir.h +index 5855c06..8c12d1f 100644 +--- a/dir.h ++++ b/dir.h +@@ -339,6 +339,8 @@ struct dir_struct { + unsigned unmanaged_exclude_files; + }; + ++struct dirent *readdir_skip_dot_and_dotdot(DIR *dirp); ++ + /*Count the number of slashes for string s*/ + int count_slashes(const char *s); + +diff --git a/entry.c b/entry.c +index 00b4903..365ece0 100644 +--- a/entry.c ++++ b/entry.c +@@ -56,12 +56,9 @@ static void remove_subtree(struct strbuf *path) + + if (!dir) + die_errno("cannot opendir '%s'", path->buf); +- while ((de = readdir(dir)) != NULL) { ++ while ((de = readdir_skip_dot_and_dotdot(dir)) != NULL) { + struct stat st; + +- if (is_dot_or_dotdot(de->d_name)) +- continue; +- + strbuf_addch(path, '/'); + strbuf_addstr(path, de->d_name); + if (lstat(path->buf, &st)) +diff --git a/notes-merge.c b/notes-merge.c +index 2fe724f..efd2014 100644 +--- a/notes-merge.c ++++ b/notes-merge.c +@@ -695,13 +695,10 @@ int notes_merge_commit(struct notes_merge_options *o, + + strbuf_addch(&path, '/'); + baselen = path.len; +- while ((e = readdir(dir)) != NULL) { ++ while ((e = readdir_skip_dot_and_dotdot(dir)) != NULL) { + struct stat st; + struct object_id obj_oid, blob_oid; + +- if (is_dot_or_dotdot(e->d_name)) +- continue; +- + if (get_oid_hex(e->d_name, &obj_oid)) { + if (o->verbosity >= 3) + printf("Skipping non-SHA1 entry '%s%s'\n", +diff --git a/packfile.c b/packfile.c +index f4e7529..45ff52e 100644 +--- a/packfile.c ++++ b/packfile.c +@@ -817,10 +817,7 @@ void for_each_file_in_pack_dir(const char *objdir, + } + strbuf_addch(&path, '/'); + dirnamelen = path.len; +- while ((de = readdir(dir)) != NULL) { +- if (is_dot_or_dotdot(de->d_name)) +- continue; +- ++ while ((de = readdir_skip_dot_and_dotdot(dir)) != NULL) { + strbuf_setlen(&path, dirnamelen); + strbuf_addstr(&path, de->d_name); + +diff --git a/rerere.c b/rerere.c +index 9281131..26fb967 100644 +--- a/rerere.c ++++ b/rerere.c +@@ -1198,13 +1198,11 @@ void rerere_gc(struct repository *r, struct string_list *rr) + if (!dir) + die_errno(_("unable to open rr-cache directory")); + /* Collect stale conflict IDs ... */ +- while ((e = readdir(dir))) { ++ while ((e = readdir_skip_dot_and_dotdot(dir))) { + struct rerere_dir *rr_dir; + struct rerere_id id; + int now_empty; + +- if (is_dot_or_dotdot(e->d_name)) +- continue; + rr_dir = find_rerere_dir(e->d_name); + if (!rr_dir) + continue; /* or should we remove e->d_name? */ +diff --git a/worktree.c b/worktree.c +index ee82235..45c2912 100644 +--- a/worktree.c ++++ b/worktree.c +@@ -146,10 +146,8 @@ struct worktree **get_worktrees(unsigned flags) + dir = opendir(path.buf); + strbuf_release(&path); + if (dir) { +- while ((d = readdir(dir)) != NULL) { ++ while ((d = readdir_skip_dot_and_dotdot(dir)) != NULL) { + struct worktree *linked = NULL; +- if (is_dot_or_dotdot(d->d_name)) +- continue; + + if ((linked = get_linked_worktree(d->d_name))) { + ALLOC_GROW(list, counter + 1, alloc); +@@ -492,13 +490,9 @@ int submodule_uses_worktrees(const char *path) + if (!dir) + return 0; + +- while ((d = readdir(dir)) != NULL) { +- if (is_dot_or_dotdot(d->d_name)) +- continue; +- ++ d = readdir_skip_dot_and_dotdot(dir); ++ if (d != NULL) + ret = 1; +- break; +- } + closedir(dir); + return ret; + } +-- +2.20.1 + diff --git a/backport-CVE-2024-32002-submodules-submodule-paths-must-not-contain-symlinks.patch b/backport-CVE-2024-32002-submodules-submodule-paths-must-not-contain-symlinks.patch new file mode 100644 index 0000000000000000000000000000000000000000..8f2ffc2e23a9f50504e6188671cfd2b53fe37270 --- /dev/null +++ b/backport-CVE-2024-32002-submodules-submodule-paths-must-not-contain-symlinks.patch @@ -0,0 +1,159 @@ +From 1f4a91f7c2b938b6b9a2c7bd86db9c9d9d90fc7f Mon Sep 17 00:00:00 2001 +From: Johannes Schindelin +Date: Fri, 22 Mar 2024 11:19:22 +0100 +Subject: [PATCH] submodules: submodule paths must not contain symlinks + +When creating a submodule path, we must be careful not to follow +symbolic links. Otherwise we may follow a symbolic link pointing to +a gitdir (which are valid symbolic links!) e.g. while cloning. + +On case-insensitive filesystems, however, we blindly replace a directory +that has been created as part of the `clone` operation with a symlink +when the path to the latter differs only in case from the former's path. + +Let's simply avoid this situation by expecting not ever having to +overwrite any existing file/directory/symlink upon cloning. That way, we +won't even replace a directory that we just created. + +This addresses CVE-2024-32002. + +Reported-by: Filip Hejsek +Signed-off-by: Johannes Schindelin +Reference: https://git.kernel.org/pub/scm/git/git.git/commit/?id=97065761333fd62db1912d81b489db938d8c991d +Conflicts: + builtin/submodule--helper.c + t/t7406-submodule-update.sh +Signed-off-by: qiaojijun +--- + builtin/submodule--helper.c | 36 ++++++++++++++++++++++++++++ + t/t7406-submodule-update.sh | 48 +++++++++++++++++++++++++++++++++++++ + 2 files changed, 84 insertions(+) + +diff --git a/builtin/submodule--helper.c b/builtin/submodule--helper.c +index 46c03d2..e83d188 100644 +--- a/builtin/submodule--helper.c ++++ b/builtin/submodule--helper.c +@@ -1377,6 +1377,28 @@ static void prepare_possible_alternates(const char *sm_name, + free(error_strategy); + } + ++static int dir_contains_only_dotgit(const char *path) ++{ ++ DIR *dir = opendir(path); ++ struct dirent *e; ++ int ret = 1; ++ ++ if (!dir) ++ return 0; ++ ++ e = readdir_skip_dot_and_dotdot(dir); ++ if (!e) ++ ret = 0; ++ else if (strcmp(DEFAULT_GIT_DIR_ENVIRONMENT, e->d_name) || ++ (e = readdir_skip_dot_and_dotdot(dir))) { ++ error("unexpected item '%s' in '%s'", e->d_name, path); ++ ret = 0; ++ } ++ ++ closedir(dir); ++ return ret; ++} ++ + static int module_clone(int argc, const char **argv, const char *prefix) + { + const char *name = NULL, *url = NULL, *depth = NULL; +@@ -1388,6 +1410,8 @@ static int module_clone(int argc, const char **argv, const char *prefix) + int dissociate = 0, require_init = 0; + char *sm_alternate = NULL, *error_strategy = NULL; + int single_branch = -1; ++ struct stat st; ++ + + struct option module_clone_options[] = { + OPT_STRING(0, "prefix", &prefix, +@@ -1450,6 +1474,10 @@ static int module_clone(int argc, const char **argv, const char *prefix) + "git dir"), sm_gitdir); + + if (!file_exists(sm_gitdir)) { ++ if (require_init && !stat(path, &st) && ++ !is_empty_dir(path)) ++ die(_("directory not empty: '%s'"), path); ++ + if (safe_create_leading_directories_const(sm_gitdir) < 0) + die(_("could not create directory '%s'"), sm_gitdir); + +@@ -1459,6 +1487,14 @@ static int module_clone(int argc, const char **argv, const char *prefix) + quiet, progress, single_branch)) + die(_("clone of '%s' into submodule path '%s' failed"), + url, path); ++ ++ if (require_init && !stat(path, &st) && ++ !dir_contains_only_dotgit(path)) { ++ char *dot_git = xstrfmt("%s/.git", path); ++ unlink(dot_git); ++ free(dot_git); ++ die(_("directory not empty: '%s'"), path); ++ } + } else { + if (require_init && !access(path, X_OK) && !is_empty_dir(path)) + die(_("directory not empty: '%s'"), path); +diff --git a/t/t7406-submodule-update.sh b/t/t7406-submodule-update.sh +index 4fb447a..03283dc 100755 +--- a/t/t7406-submodule-update.sh ++++ b/t/t7406-submodule-update.sh +@@ -1006,4 +1006,52 @@ test_expect_success 'git clone passes the parallel jobs config on to submodules' + rm -rf super4 + ' + ++test_expect_success CASE_INSENSITIVE_FS,SYMLINKS \ ++ 'submodule paths must not follow symlinks' ' ++ ++ # This is only needed because we want to run this in a self-contained ++ # test without having to spin up an HTTP server; However, it would not ++ # be needed in a real-world scenario where the submodule is simply ++ # hosted on a public site. ++ test_config_global protocol.file.allow always && ++ ++ # Make sure that Git tries to use symlinks on Windows ++ test_config_global core.symlinks true && ++ ++ tell_tale_path="$PWD/tell.tale" && ++ git init hook && ++ ( ++ cd hook && ++ mkdir -p y/hooks && ++ write_script y/hooks/post-checkout <<-EOF && ++ echo HOOK-RUN >&2 ++ echo hook-run >"$tell_tale_path" ++ EOF ++ git add y/hooks/post-checkout && ++ test_tick && ++ git commit -m post-checkout ++ ) && ++ ++ hook_repo_path="$(pwd)/hook" && ++ git init captain && ++ ( ++ cd captain && ++ git submodule add --name x/y "$hook_repo_path" A/modules/x && ++ test_tick && ++ git commit -m add-submodule && ++ ++ printf .git >dotgit.txt && ++ git hash-object -w --stdin dot-git.hash && ++ printf "120000 %s 0\ta\n" "$(cat dot-git.hash)" >index.info && ++ git update-index --index-info err && ++ grep "directory not empty" err && ++ test_path_is_missing "$tell_tale_path" ++' ++ + test_done +-- +2.20.1 + diff --git a/git.spec b/git.spec index 06448b2fce511b34dc7a8c17e2532ae8f9355727..aa3c8fc80b53337d552125c673f11638edcd85e8 100644 --- a/git.spec +++ b/git.spec @@ -1,7 +1,7 @@ %global gitexecdir %{_libexecdir}/git-core Name: git Version: 2.27.0 -Release: 19 +Release: 20 Summary: A popular and widely used Version Control System License: GPLv2+ or LGPLv2.1 URL: https://git-scm.com/ @@ -72,6 +72,8 @@ Patch57: backport-CVE-2024-32004-fetch-clone-detect-dubious-ownership-of-loca Patch58: backport-CVE-2024-32020-builtin-clone-refuse-local-clones-of-unsafe-reposito.patch Patch59: backport-CVE-2024-32465-wrapper.c-add-x-un-setenv-and-use-xsetenv-in.patch Patch60: backport-CVE-2024-32465-upload-pack-disable-lazy-fetching-by-default.patch +Patch61: backport-CVE-2024-32002-dir-introduce-readdir_skip_dot_and_dotdot-helper.patch +Patch62: backport-CVE-2024-32002-submodules-submodule-paths-must-not-contain-symlinks.patch BuildRequires: gcc gettext BuildRequires: openssl-devel libcurl-devel expat-devel systemd asciidoc xmlto glib2-devel libsecret-devel pcre2-devel desktop-file-utils @@ -321,6 +323,12 @@ make %{?_smp_mflags} test %{_mandir}/man7/git*.7.* %changelog +* Tue May 21 2024 qiaojijun - 2.27.0-20 +- Type:CVE +- ID:CVE-2024-32002 +- SUG:NA +- DESC:Fix CVE-2024-32002 + * Thu May 16 2024 fuanan - 2.27.0-19 - Type:CVE - ID:CVE-2024-32021 CVE-2024-32004 CVE-2024-32020 CVE-2024-32465