]> git.ipfire.org Git - thirdparty/git.git/blobdiff - builtin/clone.c
The fifth batch
[thirdparty/git.git] / builtin / clone.c
index 74ec14542e811d10f0dc88c9bfe935fc41af1491..554b29768c351eda6635936a2d29a96f8e921b72 100644 (file)
@@ -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_create_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_create_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_create_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);