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..f4dbbbe49739b745a25aee6bb80cf1c847a5d951 --- /dev/null +++ b/backport-CVE-2024-32002-dir-introduce-readdir_skip_dot_and_dotdot-helper.patch @@ -0,0 +1,66 @@ +From 906fc557b70b2b2995785c9b37e212d2f86b469e 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 +--- + dir.c | 11 ++++++++++++++++--------- + dir.h | 2 ++ + 2 files changed, 13 insertions(+), 0 deletions(-) + +diff --git a/dir.c b/dir.c +index ff004b298b2a3f..b909cf9b03da8a 100644 +--- a/dir.c ++++ b/dir.c +@@ -59,6 +59,17 @@ void dir_init(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; +diff --git a/dir.h b/dir.h +index 70c750e3053ded..6b3fac0829452a 100644 +--- a/dir.h ++++ b/dir.h +@@ -342,6 +342,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/backport-CVE-2024-32002-submodule-helper-add-const-to-passed-module_clone_da.patch b/backport-CVE-2024-32002-submodule-helper-add-const-to-passed-module_clone_da.patch new file mode 100644 index 0000000000000000000000000000000000000000..f9860a33a0b4e1232d675d76aa1a42cebf4a7508 --- /dev/null +++ b/backport-CVE-2024-32002-submodule-helper-add-const-to-passed-module_clone_da.patch @@ -0,0 +1,178 @@ +From 6fac5b2f352efc8c246d6d5be63a66b7b0fc0209 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?=C3=86var=20Arnfj=C3=B6r=C3=B0=20Bjarmason?= + +Date: Thu, 1 Sep 2022 01:17:56 +0200 +Subject: [PATCH] submodule--helper: add "const" to passed "module_clone_data" +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit + +Add "const" to the "struct module_clone_data" that we pass to +clone_submodule(), which makes the ownership clear, and stops us from +clobbering the "clone_data->path". + +We still need to add to the "reference" member, which is a "struct +string_list". Let's do this by having clone_submodule() create its +own, and copy the contents over, allowing us to pass it as a +separate parameter. + +This new "struct string_list" still leaks memory, just as the "struct +module_clone_data" did before. let's not fix that for now, to fix that +we'll need to add some "goto cleanup" to the relevant code. That will +eventually be done in follow-up commits, this change makes it easier +to fix the memory leak. + +The scope of the new "reference" variable in add_submodule() could be +narrowed to the "else" block, but as we'll eventually free it with a +"goto cleanup" let's declare it at the start of the function. + +Signed-off-by: Ævar Arnfjörð Bjarmason +Reviewed-by: Glen Choo +Signed-off-by: Junio C Hamano +--- + builtin/submodule--helper.c | 49 ++++++++++++++++++++----------------- + 1 file changed, 26 insertions(+), 23 deletions(-) + +diff --git a/builtin/submodule--helper.c b/builtin/submodule--helper.c +index fe32abd45e67e9..6b4ee8a31bb5eb 100644 +--- a/builtin/submodule--helper.c ++++ b/builtin/submodule--helper.c +@@ -1434,7 +1434,6 @@ struct module_clone_data { + const char *name; + const char *url; + const char *depth; +- struct string_list reference; + unsigned int quiet: 1; + unsigned int progress: 1; + unsigned int dissociate: 1; +@@ -1442,7 +1441,7 @@ struct module_clone_data { + unsigned int require_init: 1; + int single_branch; + }; +-#define MODULE_CLONE_DATA_INIT { .reference = STRING_LIST_INIT_NODUP, .single_branch = -1 } ++#define MODULE_CLONE_DATA_INIT { .single_branch = -1 } + + struct submodule_alternate_setup { + const char *submodule_name; +@@ -1569,22 +1567,24 @@ static void prepare_possible_alternates(const char *sm_name, + free(error_strategy); + } + +-static int clone_submodule(struct module_clone_data *clone_data) ++static int clone_submodule(const struct module_clone_data *clone_data, ++ struct string_list *reference) + { + char *p, *sm_gitdir; + char *sm_alternate = NULL, *error_strategy = NULL; + struct strbuf sb = STRBUF_INIT; + struct child_process cp = CHILD_PROCESS_INIT; ++ const char *clone_data_path; + + strbuf_addf(&sb, "%s/modules/%s", get_git_dir(), clone_data->name); + sm_gitdir = absolute_pathdup(sb.buf); + strbuf_reset(&sb); + + if (!is_absolute_path(clone_data->path)) { +- strbuf_addf(&sb, "%s/%s", get_git_work_tree(), clone_data->path); +- clone_data->path = strbuf_detach(&sb, NULL); ++ clone_data_path = xstrfmt("%s/%s", get_git_work_tree(), ++ clone_data->path); + } else { +- clone_data->path = xstrdup(clone_data->path); ++ clone_data_path = xstrdup(clone_data->path); + } + + if (validate_submodule_git_dir(sm_gitdir, clone_data->name) < 0) +@@ -1590,7 +1590,7 @@ static int clone_submodule(struct module_clone_data *clone_data) + if (safe_create_leading_directories_const(sm_gitdir) < 0) + die(_("could not create directory '%s'"), sm_gitdir); + +- prepare_possible_alternates(clone_data->name, &clone_data->reference); ++ prepare_possible_alternates(clone_data->name, reference); + + argv_array_push(&cp.args, "clone"); + argv_array_push(&cp.args, "--no-checkout"); +@@ -1600,9 +1600,9 @@ static int clone_submodule(struct module_clone_data *clone_data) + argv_array_push(&cp.args, "--progress"); + if (clone_data->depth && *(clone_data->depth)) + argv_array_pushl(&cp.args, "--depth", clone_data->depth, NULL); +- if (clone_data->reference.nr) { ++ if (reference->nr) { + struct string_list_item *item; +- for_each_string_list_item(item, &clone_data->reference) ++ for_each_string_list_item(item, reference) + argv_array_pushl(&cp.args, "--reference", + item->string, NULL); + } +@@ -1622,7 +1622,7 @@ static int clone_submodule(struct module_clone_data *clone_data) + + argv_array_push(&cp.args, "--"); + argv_array_push(&cp.args, clone_data->url); +- argv_array_push(&cp.args, clone_data->path); ++ argv_array_push(&cp.args, clone_data_path); + + cp.git_cmd = 1; + prepare_submodule_repo_env(&cp.env_array); +@@ -1630,23 +1630,23 @@ static int clone_submodule(struct module_clone_data *clone_data) + + if(run_command(&cp)) + die(_("clone of '%s' into submodule path '%s' failed"), +- clone_data->url, clone_data->path); ++ clone_data->url, clone_data_path); + } else { +- if (clone_data->require_init && !access(clone_data->path, X_OK) && +- !is_empty_dir(clone_data->path)) +- die(_("directory not empty: '%s'"), clone_data->path); +- if (safe_create_leading_directories_const(clone_data->path) < 0) +- die(_("could not create directory '%s'"), clone_data->path); ++ if (clone_data->require_init && !access(clone_data_path, X_OK) && ++ !is_empty_dir(clone_data_path)) ++ die(_("directory not empty: '%s'"), clone_data_path); ++ if (safe_create_leading_directories_const(clone_data_path) < 0) ++ die(_("could not create directory '%s'"), clone_data_path); + strbuf_addf(&sb, "%s/index", sm_gitdir); + unlink_or_warn(sb.buf); + strbuf_reset(&sb); + } + +- connect_work_tree_and_git_dir(clone_data->path, sm_gitdir, 0); ++ connect_work_tree_and_git_dir(clone_data_path, sm_gitdir, 0); + +- p = git_pathdup_submodule(clone_data->path, "config"); ++ p = git_pathdup_submodule(clone_data_path, "config"); + if (!p) +- die(_("could not get submodule directory for '%s'"), clone_data->path); ++ die(_("could not get submodule directory for '%s'"), clone_data_path); + + /* setup alternateLocation and alternateErrorStrategy in the cloned submodule if needed */ + git_config_get_string("submodule.alternateLocation", &sm_alternate); +@@ -1673,7 +1673,7 @@ static int module_clone(int argc, const char **argv, const char *prefix) + { + int dissociate = 0, quiet = 0, progress = 0, require_init = 0; + struct module_clone_data clone_data = MODULE_CLONE_DATA_INIT; +- ++ struct string_list reference = STRING_LIST_INIT_NODUP; + struct option module_clone_options[] = { + OPT_STRING(0, "prefix", &clone_data.prefix, + N_("path"), +@@ -1686,7 +1687,7 @@ static int module_clone(int argc, const char **argv, const char *prefix) + OPT_STRING(0, "url", &clone_data.url, + N_("string"), + N_("url where to clone the submodule from")), +- OPT_STRING_LIST(0, "reference", &clone_data.reference, ++ OPT_STRING_LIST(0, "reference", &reference, + N_("repo"), + N_("reference repository")), + OPT_BOOL(0, "dissociate", &dissociate, +@@ -1725,7 +1726,7 @@ static int module_clone(int argc, const char **argv, const char *prefix) + usage_with_options(git_submodule_helper_usage, + module_clone_options); + +- clone_submodule(&clone_data); ++ clone_submodule(&clone_data, &reference); + return 0; + } + +-- +2.33.0 + diff --git a/backport-CVE-2024-32002-submodule-helper-fix-a-leak-in-clone_submodule.patch b/backport-CVE-2024-32002-submodule-helper-fix-a-leak-in-clone_submodule.patch new file mode 100644 index 0000000000000000000000000000000000000000..28983fbe71e1a76c83207ffb98dad5f5966a91a6 --- /dev/null +++ b/backport-CVE-2024-32002-submodule-helper-fix-a-leak-in-clone_submodule.patch @@ -0,0 +1,141 @@ +From e77b3da6bb60e9af5963c9e42442afe53af1780b Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?=C3=86var=20Arnfj=C3=B6r=C3=B0=20Bjarmason?= + +Date: Thu, 1 Sep 2022 01:14:08 +0200 +Subject: [PATCH] submodule--helper: fix a leak in "clone_submodule" +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit + +Fix a memory leak of the "clone_data_path" variable that we copy or +derive from the "struct module_clone_data" in clone_submodule(). This +code was refactored in preceding commits, but the leak has been with +us since f8eaa0ba98b (submodule--helper, module_clone: always operate +on absolute paths, 2016-03-31). + +For the "else" case we don't need to xstrdup() the "clone_data->path", +and we don't need to free our own "clone_data_path". We can therefore +assign the "clone_data->path" to our own "clone_data_path" right away, +and only override it (and remember to free it!) if we need to +xstrfmt() a replacement. + +In the case of the module_clone() caller it's from "argv", and doesn't +need to be free'd, and in the case of the add_submodule() caller we +get a pointer to "sm_path", which doesn't need to be directly free'd +either. + +Fixing this leak makes several tests pass, so let's mark them as +passing with TEST_PASSES_SANITIZE_LEAK=true. + +Signed-off-by: Ævar Arnfjörð Bjarmason +Reviewed-by: Glen Choo +Signed-off-by: Junio C Hamano +--- + builtin/submodule--helper.c | 10 +++++----- + t/t1500-rev-parse.sh | 1 + + t/t6008-rev-list-submodule.sh | 1 + + t/t7414-submodule-mistakes.sh | 2 ++ + t/t7506-status-submodule.sh | 1 + + t/t7507-commit-verbose.sh | 2 ++ + 6 files changed, 12 insertions(+), 5 deletions(-) + +diff --git a/builtin/submodule--helper.c b/builtin/submodule--helper.c +index 3a2a92da514156..d308b14904c867 100644 +--- a/builtin/submodule--helper.c ++++ b/builtin/submodule--helper.c +@@ -1587,18 +1587,17 @@ static int clone_submodule(const struct module_clone_data *clone_data, + char *sm_alternate = NULL, *error_strategy = NULL; + struct strbuf sb = STRBUF_INIT; + struct child_process cp = CHILD_PROCESS_INIT; +- const char *clone_data_path; ++ const char *clone_data_path = clone_data->path; ++ char *to_free = NULL; + + strbuf_addf(&sb, "%s/modules/%s", get_git_dir(), clone_data->name); + sm_gitdir = absolute_pathdup(sb.buf); + strbuf_reset(&sb); + + if (!is_absolute_path(clone_data->path)) { +- clone_data_path = xstrfmt("%s/%s", get_git_work_tree(), +- clone_data->path); +- } else { +- clone_data_path = xstrdup(clone_data->path); + } ++ clone_data_path = to_free = xstrfmt("%s/%s", get_git_work_tree(), ++ clone_data->path); + + if (validate_submodule_git_dir(sm_gitdir, clone_data->name) < 0) + die(_("refusing to create/use '%s' in another submodule's " +@@ -1678,6 +1677,7 @@ static int clone_submodule(const struct module_clone_data *clone_data, + strbuf_release(&sb); + free(sm_gitdir); + free(p); ++ free(to_free); + return 0; + } + +diff --git a/t/t1500-rev-parse.sh b/t/t1500-rev-parse.sh +index 1c2df08333bc60..0e13bcb4ebbf70 100755 +--- a/t/t1500-rev-parse.sh ++++ b/t/t1500-rev-parse.sh +@@ -4,6 +4,7 @@ test_description='test git rev-parse' + #!/bin/sh + + test_description='test git rev-parse' ++TEST_PASSES_SANITIZE_LEAK=true + . ./test-lib.sh + + # usage: [options] label is-bare is-inside-git is-inside-work prefix git-dir absolute-git-dir +diff --git a/t/t6008-rev-list-submodule.sh b/t/t6008-rev-list-submodule.sh +index 3153a0d8910464..12e67e187ef214 100755 +--- a/t/t6008-rev-list-submodule.sh ++++ b/t/t6008-rev-list-submodule.sh +@@ -8,6 +8,7 @@ test_description='git rev-list involving submodules that this repo has' + + test_description='git rev-list involving submodules that this repo has' + ++TEST_PASSES_SANITIZE_LEAK=true + . ./test-lib.sh + + test_expect_success 'setup' ' +diff --git a/t/t7414-submodule-mistakes.sh b/t/t7414-submodule-mistakes.sh +index f2e7df59cf24c2..3269298197c2c2 100755 +--- a/t/t7414-submodule-mistakes.sh ++++ b/t/t7414-submodule-mistakes.sh +@@ -1,6 +1,8 @@ + #!/bin/sh + + test_description='handling of common mistakes people may make with submodules' ++ ++TEST_PASSES_SANITIZE_LEAK=true + . ./test-lib.sh + + test_expect_success 'create embedded repository' ' +diff --git a/t/t7506-status-submodule.sh b/t/t7506-status-submodule.sh +index 3fcb44767f5105..f5426a8e589fb6 100755 +--- a/t/t7506-status-submodule.sh ++++ b/t/t7506-status-submodule.sh +@@ -2,6 +2,7 @@ + + test_description='git status for submodule' + ++TEST_PASSES_SANITIZE_LEAK=true + . ./test-lib.sh + + test_create_repo_with_commit () { +diff --git a/t/t7507-commit-verbose.sh b/t/t7507-commit-verbose.sh +index ed2653d46fe6cd..92462a22374042 100755 +--- a/t/t7507-commit-verbose.sh ++++ b/t/t7507-commit-verbose.sh +@@ -1,6 +1,8 @@ + #!/bin/sh + + test_description='verbose commit template' ++ ++TEST_PASSES_SANITIZE_LEAK=true + . ./test-lib.sh + + write_script "check-for-diff" <<\EOF && +-- +2.33.0 + diff --git a/backport-CVE-2024-32002-submodule-helper-refactor-module_clone.patch b/backport-CVE-2024-32002-submodule-helper-refactor-module_clone.patch new file mode 100644 index 0000000000000000000000000000000000000000..8d9f37ba76a72459a2481b09808def93adc1fbb6 --- /dev/null +++ b/backport-CVE-2024-32002-submodule-helper-refactor-module_clone.patch @@ -0,0 +1,313 @@ +From a98b02c11289879868dd0d5f80e894d8d01dc73b Mon Sep 17 00:00:00 2001 +From: Atharva Raykar +Date: Sat, 10 Jul 2021 13:18:00 +0530 +Subject: [PATCH] submodule--helper: refactor module_clone() + +Separate out the core logic of module_clone() from the flag +parsing---this way we can call the equivalent of the `submodule--helper +clone` subcommand directly within C, without needing to push arguments +in a strvec. + +Signed-off-by: Atharva Raykar +Mentored-by: Christian Couder +Mentored-by: Shourya Shukla +Suggested-by: Junio C Hamano +Signed-off-by: Junio C Hamano +--- + builtin/submodule--helper.c | 241 +++++++++++++++++++----------------- + 1 file changed, 128 insertions(+), 113 deletions(-) + +diff --git a/builtin/submodule--helper.c b/builtin/submodule--helper.c +index d55f6262e9c93b..ae246a35f94819 100644 +--- a/builtin/submodule--helper.c ++++ b/builtin/submodule--helper.c +@@ -1658,45 +1658,20 @@ static int module_deinit(int argc, const char **argv, const char *prefix) + return 0; + } + +-static int clone_submodule(const char *path, const char *gitdir, const char *url, +- const char *depth, struct string_list *reference, int dissociate, +- int quiet, int progress, int single_branch) +-{ +- struct child_process cp = CHILD_PROCESS_INIT; +- +- argv_array_push(&cp.args, "clone"); +- argv_array_push(&cp.args, "--no-checkout"); +- if (quiet) +- argv_array_push(&cp.args, "--quiet"); +- if (progress) +- argv_array_push(&cp.args, "--progress"); +- if (depth && *depth) +- argv_array_pushl(&cp.args, "--depth", depth, NULL); +- if (reference->nr) { +- struct string_list_item *item; +- for_each_string_list_item(item, reference) +- argv_array_pushl(&cp.args, "--reference", +- item->string, NULL); +- } +- if (dissociate) +- argv_array_push(&cp.args, "--dissociate"); +- if (gitdir && *gitdir) +- argv_array_pushl(&cp.args, "--separate-git-dir", gitdir, NULL); +- if (single_branch >= 0) +- argv_array_push(&cp.args, single_branch ? +- "--single-branch" : +- "--no-single-branch"); +- +- argv_array_push(&cp.args, "--"); +- argv_array_push(&cp.args, url); +- argv_array_push(&cp.args, path); +- +- cp.git_cmd = 1; +- prepare_submodule_repo_env(&cp.env_array); +- cp.no_stdin = 1; +- +- return run_command(&cp); +-} ++struct module_clone_data { ++ const char *prefix; ++ const char *path; ++ const char *name; ++ const char *url; ++ const char *depth; ++ struct string_list reference; ++ unsigned int quiet: 1; ++ unsigned int progress: 1; ++ unsigned int dissociate: 1; ++ unsigned int require_init: 1; ++ int single_branch; ++}; ++#define MODULE_CLONE_DATA_INIT { .reference = STRING_LIST_INIT_NODUP, .single_branch = -1 } + + struct submodule_alternate_setup { + const char *submodule_name; +@@ -1802,37 +1777,128 @@ static void prepare_possible_alternates(const char *sm_name, + free(error_strategy); + } + +-static int module_clone(int argc, const char **argv, const char *prefix) ++static int clone_submodule(struct module_clone_data *clone_data) + { +- const char *name = NULL, *url = NULL, *depth = NULL; +- int quiet = 0; +- int progress = 0; +- char *p, *path = NULL, *sm_gitdir; +- struct strbuf sb = STRBUF_INIT; +- struct string_list reference = STRING_LIST_INIT_NODUP; +- int dissociate = 0, require_init = 0; ++ char *p, *sm_gitdir; + char *sm_alternate = NULL, *error_strategy = NULL; +- int single_branch = -1; ++ struct strbuf sb = STRBUF_INIT; ++ struct child_process cp = CHILD_PROCESS_INIT; ++ ++ strbuf_addf(&sb, "%s/modules/%s", get_git_dir(), clone_data->name); ++ sm_gitdir = absolute_pathdup(sb.buf); ++ strbuf_reset(&sb); ++ ++ if (!is_absolute_path(clone_data->path)) { ++ strbuf_addf(&sb, "%s/%s", get_git_work_tree(), clone_data->path); ++ clone_data->path = strbuf_detach(&sb, NULL); ++ } else { ++ clone_data->path = xstrdup(clone_data->path); ++ } ++ ++ if (validate_submodule_git_dir(sm_gitdir, clone_data->name) < 0) ++ die(_("refusing to create/use '%s' in another submodule's " ++ "git dir"), sm_gitdir); ++ ++ if (!file_exists(sm_gitdir)) { ++ if (safe_create_leading_directories_const(sm_gitdir) < 0) ++ die(_("could not create directory '%s'"), sm_gitdir); ++ ++ prepare_possible_alternates(clone_data->name, &clone_data->reference); ++ ++ argv_array_push(&cp.args, "clone"); ++ argv_array_push(&cp.args, "--no-checkout"); ++ if (clone_data->quiet) ++ argv_array_push(&cp.args, "--quiet"); ++ if (clone_data->progress) ++ argv_array_push(&cp.args, "--progress"); ++ if (clone_data->depth && *(clone_data->depth)) ++ argv_array_pushl(&cp.args, "--depth", clone_data->depth, NULL); ++ if (clone_data->reference.nr) { ++ struct string_list_item *item; ++ for_each_string_list_item(item, &clone_data->reference) ++ argv_array_pushl(&cp.args, "--reference", ++ item->string, NULL); ++ } ++ if (clone_data->dissociate) ++ argv_array_push(&cp.args, "--dissociate"); ++ if (sm_gitdir && *sm_gitdir) ++ argv_array_pushl(&cp.args, "--separate-git-dir", sm_gitdir, NULL); ++ if (clone_data->single_branch >= 0) ++ argv_array_push(&cp.args, clone_data->single_branch ? ++ "--single-branch" : ++ "--no-single-branch"); ++ ++ argv_array_push(&cp.args, "--"); ++ argv_array_push(&cp.args, clone_data->url); ++ argv_array_push(&cp.args, clone_data->path); ++ ++ cp.git_cmd = 1; ++ prepare_submodule_repo_env(&cp.env_array); ++ cp.no_stdin = 1; ++ ++ if(run_command(&cp)) ++ die(_("clone of '%s' into submodule path '%s' failed"), ++ clone_data->url, clone_data->path); ++ } else { ++ if (clone_data->require_init && !access(clone_data->path, X_OK) && ++ !is_empty_dir(clone_data->path)) ++ die(_("directory not empty: '%s'"), clone_data->path); ++ if (safe_create_leading_directories_const(clone_data->path) < 0) ++ die(_("could not create directory '%s'"), clone_data->path); ++ strbuf_addf(&sb, "%s/index", sm_gitdir); ++ unlink_or_warn(sb.buf); ++ strbuf_reset(&sb); ++ } ++ ++ connect_work_tree_and_git_dir(clone_data->path, sm_gitdir, 0); ++ ++ p = git_pathdup_submodule(clone_data->path, "config"); ++ if (!p) ++ die(_("could not get submodule directory for '%s'"), clone_data->path); ++ ++ /* setup alternateLocation and alternateErrorStrategy in the cloned submodule if needed */ ++ git_config_get_string("submodule.alternateLocation", &sm_alternate); ++ if (sm_alternate) ++ git_config_set_in_file(p, "submodule.alternateLocation", ++ sm_alternate); ++ git_config_get_string("submodule.alternateErrorStrategy", &error_strategy); ++ if (error_strategy) ++ git_config_set_in_file(p, "submodule.alternateErrorStrategy", ++ error_strategy); ++ ++ free(sm_alternate); ++ free(error_strategy); ++ ++ strbuf_release(&sb); ++ free(sm_gitdir); ++ free(p); ++ return 0; ++} ++ ++static int module_clone(int argc, const char **argv, const char *prefix) ++{ ++ int dissociate = 0, quiet = 0, progress = 0, require_init = 0; ++ struct module_clone_data clone_data = MODULE_CLONE_DATA_INIT; + + struct option module_clone_options[] = { +- OPT_STRING(0, "prefix", &prefix, ++ OPT_STRING(0, "prefix", &clone_data.prefix, + N_("path"), + N_("alternative anchor for relative paths")), +- OPT_STRING(0, "path", &path, ++ OPT_STRING(0, "path", &clone_data.path, + N_("path"), + N_("where the new submodule will be cloned to")), +- OPT_STRING(0, "name", &name, ++ OPT_STRING(0, "name", &clone_data.name, + N_("string"), + N_("name of the new submodule")), +- OPT_STRING(0, "url", &url, ++ OPT_STRING(0, "url", &clone_data.url, + N_("string"), + N_("url where to clone the submodule from")), +- OPT_STRING_LIST(0, "reference", &reference, ++ OPT_STRING_LIST(0, "reference", &clone_data.reference, + N_("repo"), + N_("reference repository")), + OPT_BOOL(0, "dissociate", &dissociate, + N_("use --reference only while cloning")), +- OPT_STRING(0, "depth", &depth, ++ OPT_STRING(0, "depth", &clone_data.depth, + N_("string"), + N_("depth for shallow clones")), + OPT__QUIET(&quiet, "Suppress output for cloning a submodule"), +@@ -1840,7 +1906,7 @@ static int module_clone(int argc, const char **argv, const char *prefix) + N_("force cloning progress")), + OPT_BOOL(0, "require-init", &require_init, + N_("disallow cloning into non-empty directory")), +- OPT_BOOL(0, "single-branch", &single_branch, ++ OPT_BOOL(0, "single-branch", &clone_data.single_branch, + N_("clone only one branch, HEAD or --branch")), + OPT_END() + }; +@@ -1856,67 +1922,16 @@ static int module_clone(int argc, const char **argv, const char *prefix) + argc = parse_options(argc, argv, prefix, module_clone_options, + git_submodule_helper_usage, 0); + +- if (argc || !url || !path || !*path) ++ clone_data.dissociate = !!dissociate; ++ clone_data.quiet = !!quiet; ++ clone_data.progress = !!progress; ++ clone_data.require_init = !!require_init; ++ ++ if (argc || !clone_data.url || !clone_data.path || !*(clone_data.path)) + usage_with_options(git_submodule_helper_usage, + module_clone_options); + +- strbuf_addf(&sb, "%s/modules/%s", get_git_dir(), name); +- sm_gitdir = absolute_pathdup(sb.buf); +- strbuf_reset(&sb); +- +- if (!is_absolute_path(path)) { +- strbuf_addf(&sb, "%s/%s", get_git_work_tree(), path); +- path = strbuf_detach(&sb, NULL); +- } else +- path = xstrdup(path); +- +- if (validate_submodule_git_dir(sm_gitdir, name) < 0) +- die(_("refusing to create/use '%s' in another submodule's " +- "git dir"), sm_gitdir); +- +- if (!file_exists(sm_gitdir)) { +- if (safe_create_leading_directories_const(sm_gitdir) < 0) +- die(_("could not create directory '%s'"), sm_gitdir); +- +- prepare_possible_alternates(name, &reference); +- +- if (clone_submodule(path, sm_gitdir, url, depth, &reference, dissociate, +- quiet, progress, single_branch)) +- die(_("clone of '%s' into submodule path '%s' failed"), +- url, path); +- } else { +- if (require_init && !access(path, X_OK) && !is_empty_dir(path)) +- die(_("directory not empty: '%s'"), path); +- if (safe_create_leading_directories_const(path) < 0) +- die(_("could not create directory '%s'"), path); +- strbuf_addf(&sb, "%s/index", sm_gitdir); +- unlink_or_warn(sb.buf); +- strbuf_reset(&sb); +- } +- +- connect_work_tree_and_git_dir(path, sm_gitdir, 0); +- +- p = git_pathdup_submodule(path, "config"); +- if (!p) +- die(_("could not get submodule directory for '%s'"), path); +- +- /* setup alternateLocation and alternateErrorStrategy in the cloned submodule if needed */ +- git_config_get_string("submodule.alternateLocation", &sm_alternate); +- if (sm_alternate) +- git_config_set_in_file(p, "submodule.alternateLocation", +- sm_alternate); +- git_config_get_string("submodule.alternateErrorStrategy", &error_strategy); +- if (error_strategy) +- git_config_set_in_file(p, "submodule.alternateErrorStrategy", +- error_strategy); +- +- free(sm_alternate); +- free(error_strategy); +- +- strbuf_release(&sb); +- free(sm_gitdir); +- free(path); +- free(p); ++ clone_submodule(&clone_data); + return 0; + } + +-- +2.33.0 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..7bd56571040abfbb5965e391e24ef5b0d3b3738d --- /dev/null +++ b/backport-CVE-2024-32002-submodules-submodule-paths-must-not-contain-symlinks.patch @@ -0,0 +1,151 @@ +From 97065761333fd62db1912d81b489db938d8c991d 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 +--- + builtin/submodule--helper.c | 35 +++++++++++++++++++++++++++ + t/t7406-submodule-update.sh | 48 +++++++++++++++++++++++++++++++++++++ + 2 files changed, 83 insertions(+) + +diff --git a/builtin/submodule--helper.c b/builtin/submodule--helper.c +index b76e13ddce3d22..4c1a7dbcdac907 100644 +--- a/builtin/submodule--helper.c ++++ b/builtin/submodule--helper.c +@@ -1351,11 +1351,34 @@ 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 clone_submodule(const struct module_clone_data *clone_data, + struct string_list *reference) + { + char *p, *sm_gitdir; + char *sm_alternate = NULL, *error_strategy = NULL; ++ struct stat st; + struct strbuf sb = STRBUF_INIT; + struct child_process cp = CHILD_PROCESS_INIT; + const char *clone_data_path = clone_data->path; +@@ -1398,6 +1398,10 @@ static int clone_submodule(const struct module_clone_data *clone_data, + "git dir"), sm_gitdir); + + if (!file_exists(sm_gitdir)) { ++ if (clone_data->require_init && !stat(clone_data_path, &st) && ++ !is_empty_dir(clone_data_path)) ++ die(_("directory not empty: '%s'"), clone_data_path); ++ + if (safe_create_leading_directories_const(sm_gitdir) < 0) + die(_("could not create directory '%s'"), sm_gitdir); + +@@ -1441,6 +1441,14 @@ static int clone_submodule(const struct module_clone_data *clone_data, + if(run_command(&cp)) + die(_("clone of '%s' into submodule path '%s' failed"), + clone_data->url, clone_data_path); ++ ++ if (clone_data->require_init && !stat(clone_data_path, &st) && ++ !dir_contains_only_dotgit(clone_data_path)) { ++ char *dot_git = xstrfmt("%s/.git", clone_data_path); ++ unlink(dot_git); ++ free(dot_git); ++ die(_("directory not empty: '%s'"), clone_data_path); ++ } + } else { + if (clone_data->require_init && !access(clone_data_path, X_OK) && + !is_empty_dir(clone_data_path)) +diff --git a/t/t7406-submodule-update.sh b/t/t7406-submodule-update.sh +index f094e3d7f3642d..63c24f7f7ca860 100755 +--- a/t/t7406-submodule-update.sh ++++ b/t/t7406-submodule-update.sh +@@ -1179,4 +1179,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.33.0 + diff --git a/git.spec b/git.spec index ec9ac6d12a85269d0d82c6995170e36a326f86db..5a9069a151284fe5c8c2eb9386b586058ced94a0 100644 --- a/git.spec +++ b/git.spec @@ -1,7 +1,7 @@ %global gitexecdir %{_libexecdir}/git-core Name: git Version: 2.27.0 -Release: 16 +Release: 17 Summary: A popular and widely used Version Control System License: GPLv2+ or LGPLv2.1 URL: https://git-scm.com/ @@ -72,6 +72,11 @@ 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-submodule-helper-refactor-module_clone.patch +Patch63: backport-CVE-2024-32002-submodule-helper-add-const-to-passed-module_clone_da.patch +Patch64: backport-CVE-2024-32002-submodule-helper-fix-a-leak-in-clone_submodule.patch +Patch65: 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 +326,12 @@ make %{?_smp_mflags} test %{_mandir}/man7/git*.7.* %changelog +* Mon May 20 2024 fuanan - 2.27.0-17 +- Type:CVE +- ID:CVE-2024-32002 +- SUG:NA +- DESC:Fix CVE-2024-32002 + * Thu May 16 2024 fuanan - 2.27.0-16 - Type:CVE - ID:CVE-2024-32021 CVE-2024-32004 CVE-2024-32020 CVE-2024-32465