diff options
author | Johannes Schindelin <johannes.schindelin@gmx.de> | 2024-04-10 22:04:48 +0200 |
---|---|---|
committer | Johannes Schindelin <johannes.schindelin@gmx.de> | 2024-04-19 12:38:50 +0200 |
commit | 8e97ec3662a54b07e4c19bb761e95cf87bd54364 (patch) | |
tree | df6bcdd503eb7093979e0c26581d0f3e3be379da /builtin/submodule--helper.c | |
parent | Git 2.43.3 (diff) | |
parent | Git 2.42.2 (diff) | |
download | git-8e97ec3662a54b07e4c19bb761e95cf87bd54364.tar.xz git-8e97ec3662a54b07e4c19bb761e95cf87bd54364.zip |
Sync with 2.42.2
* maint-2.42: (39 commits)
Git 2.42.2
Git 2.41.1
Git 2.40.2
Git 2.39.4
fsck: warn about symlink pointing inside a gitdir
core.hooksPath: add some protection while cloning
init.templateDir: consider this config setting protected
clone: prevent hooks from running during a clone
Add a helper function to compare file contents
init: refactor the template directory discovery into its own function
find_hook(): refactor the `STRIP_EXTENSION` logic
clone: when symbolic links collide with directories, keep the latter
entry: report more colliding paths
t5510: verify that D/F confusion cannot lead to an RCE
submodule: require the submodule path to contain directories only
clone_submodule: avoid using `access()` on directories
submodules: submodule paths must not contain symlinks
clone: prevent clashing git dirs when cloning submodule in parallel
t7423: add tests for symlinked submodule directories
has_dir_name(): do not get confused by characters < '/'
...
Diffstat (limited to 'builtin/submodule--helper.c')
-rw-r--r-- | builtin/submodule--helper.c | 86 |
1 files changed, 84 insertions, 2 deletions
diff --git a/builtin/submodule--helper.c b/builtin/submodule--helper.c index fda50f2af1..cc0db6336c 100644 --- a/builtin/submodule--helper.c +++ b/builtin/submodule--helper.c @@ -303,6 +303,9 @@ static void runcommand_in_submodule_cb(const struct cache_entry *list_item, struct child_process cp = CHILD_PROCESS_INIT; char *displaypath; + if (validate_submodule_path(path) < 0) + exit(128); + displaypath = get_submodule_displaypath(path, info->prefix, info->super_prefix); @@ -634,6 +637,9 @@ static void status_submodule(const char *path, const struct object_id *ce_oid, .free_removed_argv_elements = 1, }; + if (validate_submodule_path(path) < 0) + exit(128); + if (!submodule_from_path(the_repository, null_oid(), path)) die(_("no submodule mapping found in .gitmodules for path '%s'"), path); @@ -1238,6 +1244,9 @@ static void sync_submodule(const char *path, const char *prefix, if (!is_submodule_active(the_repository, path)) return; + if (validate_submodule_path(path) < 0) + exit(128); + sub = submodule_from_path(the_repository, null_oid(), path); if (sub && sub->url) { @@ -1381,6 +1390,9 @@ static void deinit_submodule(const char *path, const char *prefix, struct strbuf sb_config = STRBUF_INIT; char *sub_git_dir = xstrfmt("%s/.git", path); + if (validate_submodule_path(path) < 0) + exit(128); + sub = submodule_from_path(the_repository, null_oid(), path); if (!sub || !sub->name) @@ -1662,16 +1674,42 @@ static char *clone_submodule_sm_gitdir(const char *name) return sm_gitdir; } +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; char *sm_gitdir = clone_submodule_sm_gitdir(clone_data->name); char *sm_alternate = NULL, *error_strategy = NULL; + struct stat st; struct child_process cp = CHILD_PROCESS_INIT; const char *clone_data_path = clone_data->path; char *to_free = NULL; + if (validate_submodule_path(clone_data_path) < 0) + exit(128); + if (!is_absolute_path(clone_data->path)) clone_data_path = to_free = xstrfmt("%s/%s", get_git_work_tree(), clone_data->path); @@ -1681,6 +1719,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); @@ -1725,10 +1767,18 @@ 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 { char *path; - if (clone_data->require_init && !access(clone_data_path, X_OK) && + 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(clone_data_path) < 0) @@ -1738,6 +1788,23 @@ static int clone_submodule(const struct module_clone_data *clone_data, free(path); } + /* + * We already performed this check at the beginning of this function, + * before cloning the objects. This tries to detect racy behavior e.g. + * in parallel clones, where another process could easily have made the + * gitdir nested _after_ it was created. + * + * To prevent further harm coming from this unintentionally-nested + * gitdir, let's disable it by deleting the `HEAD` file. + */ + if (validate_submodule_git_dir(sm_gitdir, clone_data->name) < 0) { + char *head = xstrfmt("%s/HEAD", sm_gitdir); + unlink(head); + free(head); + die(_("refusing to create/use '%s' in another submodule's " + "git dir"), sm_gitdir); + } + connect_work_tree_and_git_dir(clone_data_path, sm_gitdir, 0); p = git_pathdup_submodule(clone_data_path, "config"); @@ -2517,6 +2584,9 @@ static int update_submodule(struct update_data *update_data) { int ret; + if (validate_submodule_path(update_data->sm_path) < 0) + return -1; + ret = determine_submodule_update_strategy(the_repository, update_data->just_cloned, update_data->sm_path, @@ -2624,12 +2694,21 @@ static int update_submodules(struct update_data *update_data) for (i = 0; i < suc.update_clone_nr; i++) { struct update_clone_data ucd = suc.update_clone[i]; - int code; + int code = 128; oidcpy(&update_data->oid, &ucd.oid); update_data->just_cloned = ucd.just_cloned; update_data->sm_path = ucd.sub->path; + /* + * Verify that the submodule path does not contain any + * symlinks; if it does, it might have been tampered with. + * TODO: allow exempting it via + * `safe.submodule.path` or something + */ + if (validate_submodule_path(update_data->sm_path) < 0) + goto fail; + code = ensure_core_worktree(update_data->sm_path); if (code) goto fail; @@ -3356,6 +3435,9 @@ static int module_add(int argc, const char **argv, const char *prefix) normalize_path_copy(add_data.sm_path, add_data.sm_path); strip_dir_trailing_slashes(add_data.sm_path); + if (validate_submodule_path(add_data.sm_path) < 0) + exit(128); + die_on_index_match(add_data.sm_path, force); die_on_repo_without_commits(add_data.sm_path); |