diff options
Diffstat (limited to 'builtin/clone.c')
-rw-r--r-- | builtin/clone.c | 94 |
1 files changed, 69 insertions, 25 deletions
diff --git a/builtin/clone.c b/builtin/clone.c index 74ec14542e..23993b905b 100644 --- a/builtin/clone.c +++ b/builtin/clone.c @@ -8,7 +8,6 @@ * Clone a repository into a different directory that does not yet exist. */ -#define USE_THE_INDEX_VARIABLE #include "builtin.h" #include "abspath.h" #include "advice.h" @@ -329,7 +328,20 @@ static void copy_or_link_directory(struct strbuf *src, struct strbuf *dest, int src_len, dest_len; struct dir_iterator *iter; int iter_status; - struct strbuf realpath = STRBUF_INIT; + + /* + * Refuse copying directories by default which aren't owned by us. The + * code that performs either the copying or hardlinking is not prepared + * to handle various edge cases where an adversary may for example + * racily swap out files for symlinks. This can cause us to + * inadvertently use the wrong source file. + * + * Furthermore, even if we were prepared to handle such races safely, + * creating hardlinks across user boundaries is an inherently unsafe + * operation as the hardlinked files can be rewritten at will by the + * potentially-untrusted user. We thus refuse to do so by default. + */ + die_upon_dubious_ownership(NULL, NULL, src_repo); mkdir_if_missing(dest->buf, 0777); @@ -377,9 +389,27 @@ static void copy_or_link_directory(struct strbuf *src, struct strbuf *dest, if (unlink(dest->buf) && errno != ENOENT) die_errno(_("failed to unlink '%s'"), dest->buf); if (!option_no_hardlinks) { - strbuf_realpath(&realpath, src->buf, 1); - if (!link(realpath.buf, dest->buf)) + if (!link(src->buf, dest->buf)) { + struct stat st; + + /* + * Sanity-check whether the created hardlink + * actually links to the expected file now. This + * catches time-of-check-time-of-use bugs in + * case the source file was meanwhile swapped. + */ + if (lstat(dest->buf, &st)) + die(_("hardlink cannot be checked at '%s'"), dest->buf); + if (st.st_mode != iter->st.st_mode || + st.st_ino != iter->st.st_ino || + st.st_dev != iter->st.st_dev || + st.st_size != iter->st.st_size || + st.st_uid != iter->st.st_uid || + st.st_gid != iter->st.st_gid) + die(_("hardlink different from source at '%s'"), dest->buf); + continue; + } if (option_local > 0) die_errno(_("failed to create link '%s'"), dest->buf); option_no_hardlinks = 1; @@ -392,8 +422,6 @@ static void copy_or_link_directory(struct strbuf *src, struct strbuf *dest, strbuf_setlen(src, src_len); die(_("failed to iterate over '%s'"), src->buf); } - - strbuf_release(&realpath); } static void clone_local(const char *src_repo, const char *dest_repo) @@ -539,7 +567,8 @@ static void write_remote_refs(const struct ref *local_refs) struct ref_transaction *t; struct strbuf err = STRBUF_INIT; - t = ref_transaction_begin(&err); + t = ref_store_transaction_begin(get_main_ref_store(the_repository), + &err); if (!t) die("%s", err.buf); @@ -570,8 +599,9 @@ static void write_followtags(const struct ref *refs, const char *msg) OBJECT_INFO_QUICK | OBJECT_INFO_SKIP_FETCH_OBJECT)) continue; - update_ref(msg, ref->name, &ref->old_oid, NULL, 0, - UPDATE_REFS_DIE_ON_ERR); + refs_update_ref(get_main_ref_store(the_repository), msg, + ref->name, &ref->old_oid, NULL, 0, + UPDATE_REFS_DIE_ON_ERR); } } @@ -623,9 +653,9 @@ static void update_remote_refs(const struct ref *refs, struct strbuf head_ref = STRBUF_INIT; strbuf_addstr(&head_ref, branch_top); strbuf_addstr(&head_ref, "HEAD"); - if (create_symref(head_ref.buf, - remote_head_points_at->peer_ref->name, - msg) < 0) + if (refs_update_symref(get_main_ref_store(the_repository), head_ref.buf, + remote_head_points_at->peer_ref->name, + msg) < 0) die(_("unable to update %s"), head_ref.buf); strbuf_release(&head_ref); } @@ -637,33 +667,36 @@ static void update_head(const struct ref *our, const struct ref *remote, const char *head; if (our && skip_prefix(our->name, "refs/heads/", &head)) { /* Local default branch link */ - if (create_symref("HEAD", our->name, NULL) < 0) + if (refs_update_symref(get_main_ref_store(the_repository), "HEAD", our->name, NULL) < 0) die(_("unable to update HEAD")); if (!option_bare) { - update_ref(msg, "HEAD", &our->old_oid, NULL, 0, - UPDATE_REFS_DIE_ON_ERR); + refs_update_ref(get_main_ref_store(the_repository), + msg, "HEAD", &our->old_oid, NULL, 0, + UPDATE_REFS_DIE_ON_ERR); install_branch_config(0, head, remote_name, our->name); } } else if (our) { struct commit *c = lookup_commit_reference(the_repository, &our->old_oid); /* --branch specifies a non-branch (i.e. tags), detach HEAD */ - update_ref(msg, "HEAD", &c->object.oid, NULL, REF_NO_DEREF, - UPDATE_REFS_DIE_ON_ERR); + refs_update_ref(get_main_ref_store(the_repository), msg, + "HEAD", &c->object.oid, NULL, REF_NO_DEREF, + UPDATE_REFS_DIE_ON_ERR); } else if (remote) { /* * We know remote HEAD points to a non-branch, or * HEAD points to a branch but we don't know which one. * Detach HEAD in all these cases. */ - update_ref(msg, "HEAD", &remote->old_oid, NULL, REF_NO_DEREF, - UPDATE_REFS_DIE_ON_ERR); + refs_update_ref(get_main_ref_store(the_repository), msg, + "HEAD", &remote->old_oid, NULL, REF_NO_DEREF, + UPDATE_REFS_DIE_ON_ERR); } else if (unborn && skip_prefix(unborn, "refs/heads/", &head)) { /* * Unborn head from remote; same as "our" case above except * that we have no ref to update. */ - if (create_symref("HEAD", unborn, NULL) < 0) + if (refs_update_symref(get_main_ref_store(the_repository), "HEAD", unborn, NULL) < 0) die(_("unable to update HEAD")); if (!option_bare) install_branch_config(0, head, remote_name, unborn); @@ -704,7 +737,8 @@ static int checkout(int submodule_progress, int filter_submodules) if (option_no_checkout) return 0; - head = resolve_refdup("HEAD", RESOLVE_REF_READING, &oid, NULL); + head = refs_resolve_refdup(get_main_ref_store(the_repository), "HEAD", + RESOLVE_REF_READING, &oid, NULL); if (!head) { warning(_("remote HEAD refers to nonexistent ref, " "unable to checkout")); @@ -731,8 +765,8 @@ static int checkout(int submodule_progress, int filter_submodules) opts.preserve_ignored = 0; opts.fn = oneway_merge; opts.verbose_update = (option_verbosity >= 0); - opts.src_index = &the_index; - opts.dst_index = &the_index; + opts.src_index = the_repository->index; + opts.dst_index = the_repository->index; init_checkout_metadata(&opts.meta, head, &oid, NULL); tree = parse_tree_indirect(&oid); @@ -746,7 +780,7 @@ static int checkout(int submodule_progress, int filter_submodules) free(head); - if (write_locked_index(&the_index, &lock_file, COMMIT_LOCK)) + if (write_locked_index(the_repository->index, &lock_file, COMMIT_LOCK)) die(_("unable to write new index file")); err |= run_hooks_l("post-checkout", oid_to_hex(null_oid()), @@ -938,6 +972,8 @@ int cmd_clone(int argc, const char **argv, const char *prefix) int hash_algo; unsigned int ref_storage_format = REF_STORAGE_FORMAT_UNKNOWN; const int do_not_override_repo_unix_permissions = -1; + const char *template_dir; + char *template_dir_dup = NULL; struct transport_ls_refs_options transport_ls_refs_options = TRANSPORT_LS_REFS_OPTIONS_INIT; @@ -957,6 +993,13 @@ int cmd_clone(int argc, const char **argv, const char *prefix) usage_msg_opt(_("You must specify a repository to clone."), builtin_clone_usage, builtin_clone_options); + xsetenv("GIT_CLONE_PROTECTION_ACTIVE", "true", 0 /* allow user override */); + template_dir = get_template_dir(option_template); + if (*template_dir && !is_absolute_path(template_dir)) + template_dir = template_dir_dup = + absolute_pathdup(template_dir); + xsetenv("GIT_CLONE_TEMPLATE_DIR", template_dir, 1); + if (option_depth || option_since || option_not.nr) deepen = 1; if (option_single_branch == -1) @@ -1118,7 +1161,7 @@ int cmd_clone(int argc, const char **argv, const char *prefix) * repository, and reference backends may persist that information into * their on-disk data structures. */ - init_db(git_dir, real_git_dir, option_template, GIT_HASH_UNKNOWN, + init_db(git_dir, real_git_dir, template_dir, GIT_HASH_UNKNOWN, ref_storage_format, NULL, do_not_override_repo_unix_permissions, INIT_DB_QUIET | INIT_DB_SKIP_REFDB); @@ -1507,6 +1550,7 @@ int cmd_clone(int argc, const char **argv, const char *prefix) free(dir); free(path); free(repo_to_free); + free(template_dir_dup); junk_mode = JUNK_LEAVE_ALL; transport_ls_refs_options_release(&transport_ls_refs_options); |