From: Johannes Schindelin Date: Wed, 17 Apr 2024 09:38:18 +0000 (+0200) Subject: Sync with 2.40.2 X-Git-Tag: v2.41.1~1 X-Git-Url: http://git.ipfire.org/?a=commitdiff_plain;h=f5b2af06f55c0f21ae0199be5fe120f2cccd698b;p=thirdparty%2Fgit.git Sync with 2.40.2 * maint-2.40: (39 commits) 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 < '/' docs: document security issues around untrusted .git dirs upload-pack: disable lazy-fetching by default ... --- f5b2af06f55c0f21ae0199be5fe120f2cccd698b diff --cc builtin/clone.c index 15f9912b4c,5fa2901400..d6545d036f --- a/builtin/clone.c +++ b/builtin/clone.c @@@ -929,7 -938,8 +958,9 @@@ int cmd_clone(int argc, const char **ar int err = 0, complete_refs_before_fetch = 1; int submodule_progress; int filter_submodules = 0; + int hash_algo; + const char *template_dir; + char *template_dir_dup = NULL; struct transport_ls_refs_options transport_ls_refs_options = TRANSPORT_LS_REFS_OPTIONS_INIT; diff --cc builtin/init-db.c index aef4036105,a101e7f94c..848f7ec70f --- a/builtin/init-db.c +++ b/builtin/init-db.c @@@ -12,17 -8,9 +12,13 @@@ #include "refs.h" #include "builtin.h" #include "exec-cmd.h" +#include "object-file.h" #include "parse-options.h" +#include "path.h" +#include "setup.h" #include "worktree.h" +#include "wrapper.h" - #ifndef DEFAULT_GIT_TEMPLATE_DIR - #define DEFAULT_GIT_TEMPLATE_DIR "/usr/share/git-core/templates" - #endif - #ifdef NO_TRUSTABLE_FILEMODE #define TEST_FILEMODE 0 #else diff --cc copy.c index 882c79cffb,8492f6fc83..1b4069cfea --- a/copy.c +++ b/copy.c @@@ -1,7 -1,4 +1,10 @@@ -#include "cache.h" +#include "git-compat-util.h" +#include "copy.h" +#include "path.h" +#include "wrapper.h" ++#include "gettext.h" ++#include "strbuf.h" ++#include "abspath.h" int copy_fd(int ifd, int ofd) { diff --cc copy.h index 2af77cba86,0000000000..057259a3a7 mode 100644,000000..100644 --- a/copy.h +++ b/copy.h @@@ -1,10 -1,0 +1,24 @@@ +#ifndef COPY_H +#define COPY_H + +#define COPY_READ_ERROR (-2) +#define COPY_WRITE_ERROR (-3) +int copy_fd(int ifd, int ofd); +int copy_file(const char *dst, const char *src, int mode); +int copy_file_with_time(const char *dst, const char *src, int mode); + ++/* ++ * Compare the file mode and contents of two given files. ++ * ++ * If both files are actually symbolic links, the function returns 1 if the link ++ * targets are identical or 0 if they are not. ++ * ++ * If any of the two files cannot be accessed or in case of read failures, this ++ * function returns 0. ++ * ++ * If the file modes and contents are identical, the function returns 1, ++ * otherwise it returns 0. ++ */ ++int do_files_match(const char *path1, const char *path2); ++ +#endif /* COPY_H */ diff --cc hook.c index 3ca5e60895,655e2fe44e..22a976c6d3 --- a/hook.c +++ b/hook.c @@@ -1,11 -1,31 +1,38 @@@ -#include "cache.h" +#include "git-compat-util.h" +#include "advice.h" +#include "gettext.h" #include "hook.h" +#include "path.h" #include "run-command.h" #include "config.h" +#include "strbuf.h" ++#include "environment.h" ++#include "setup.h" ++#include "copy.h" + + static int identical_to_template_hook(const char *name, const char *path) + { + const char *env = getenv("GIT_CLONE_TEMPLATE_DIR"); + const char *template_dir = get_template_dir(env && *env ? env : NULL); + struct strbuf template_path = STRBUF_INIT; + int found_template_hook, ret; + + strbuf_addf(&template_path, "%s/hooks/%s", template_dir, name); + found_template_hook = access(template_path.buf, X_OK) >= 0; + #ifdef STRIP_EXTENSION + if (!found_template_hook) { + strbuf_addstr(&template_path, STRIP_EXTENSION); + found_template_hook = access(template_path.buf, X_OK) >= 0; + } + #endif + if (!found_template_hook) + return 0; + + ret = do_files_match(template_path.buf, path); + + strbuf_release(&template_path); + return ret; + } const char *find_hook(const char *name) { diff --cc remote-curl.c index acf7b2bb40,322cd0f9f4..a0777e3f7a --- a/remote-curl.c +++ b/remote-curl.c @@@ -1,9 -1,6 +1,10 @@@ -#include "cache.h" +#include "git-compat-util.h" + #include "git-curl-compat.h" +#include "alloc.h" #include "config.h" +#include "environment.h" +#include "gettext.h" +#include "hex.h" #include "remote.h" #include "connect.h" #include "strbuf.h" diff --cc setup.c index 458582207e,c3301f5ab8..84324e35c6 --- a/setup.c +++ b/setup.c @@@ -11,8 -6,7 +11,9 @@@ #include "chdir-notify.h" #include "promisor-remote.h" #include "quote.h" +#include "trace2.h" +#include "wrapper.h" + #include "exec-cmd.h" static int inside_git_dir = -1; static int inside_work_tree = -1; diff --cc setup.h index 4c1ca9d0c9,0000000000..e7708f5739 mode 100644,000000..100644 --- a/setup.h +++ b/setup.h @@@ -1,168 -1,0 +1,182 @@@ +#ifndef SETUP_H +#define SETUP_H + +#include "string-list.h" + +int is_inside_git_dir(void); +int is_inside_work_tree(void); +int get_common_dir_noenv(struct strbuf *sb, const char *gitdir); +int get_common_dir(struct strbuf *sb, const char *gitdir); + +/* + * Return true if the given path is a git directory; note that this _just_ + * looks at the directory itself. If you want to know whether "foo/.git" + * is a repository, you must feed that path, not just "foo". + */ +int is_git_directory(const char *path); + +/* + * Return 1 if the given path is the root of a git repository or + * submodule, else 0. Will not return 1 for bare repositories with the + * exception of creating a bare repository in "foo/.git" and calling + * is_git_repository("foo"). + * + * If we run into read errors, we err on the side of saying "yes, it is", + * as we usually consider sub-repos precious, and would prefer to err on the + * side of not disrupting or deleting them. + */ +int is_nonbare_repository_dir(struct strbuf *path); + +#define READ_GITFILE_ERR_STAT_FAILED 1 +#define READ_GITFILE_ERR_NOT_A_FILE 2 +#define READ_GITFILE_ERR_OPEN_FAILED 3 +#define READ_GITFILE_ERR_READ_FAILED 4 +#define READ_GITFILE_ERR_INVALID_FORMAT 5 +#define READ_GITFILE_ERR_NO_PATH 6 +#define READ_GITFILE_ERR_NOT_A_REPO 7 +#define READ_GITFILE_ERR_TOO_LARGE 8 +void read_gitfile_error_die(int error_code, const char *path, const char *dir); +const char *read_gitfile_gently(const char *path, int *return_error_code); +#define read_gitfile(path) read_gitfile_gently((path), NULL) +const char *resolve_gitdir_gently(const char *suspect, int *return_error_code); +#define resolve_gitdir(path) resolve_gitdir_gently((path), NULL) + ++/* ++ * Check if a repository is safe and die if it is not, by verifying the ++ * ownership of the worktree (if any), the git directory, and the gitfile (if ++ * any). ++ * ++ * Exemptions for known-safe repositories can be added via `safe.directory` ++ * config settings; for non-bare repositories, their worktree needs to be ++ * added, for bare ones their git directory. ++ */ ++void die_upon_dubious_ownership(const char *gitfile, const char *worktree, ++ const char *gitdir); ++ +void setup_work_tree(void); +/* + * Find the commondir and gitdir of the repository that contains the current + * working directory, without changing the working directory or other global + * state. The result is appended to commondir and gitdir. If the discovered + * gitdir does not correspond to a worktree, then 'commondir' and 'gitdir' will + * both have the same result appended to the buffer. The return value is + * either 0 upon success and non-zero if no repository was found. + */ +int discover_git_directory(struct strbuf *commondir, + struct strbuf *gitdir); +const char *setup_git_directory_gently(int *); +const char *setup_git_directory(void); +char *prefix_path(const char *prefix, int len, const char *path); +char *prefix_path_gently(const char *prefix, int len, int *remaining, const char *path); + +int check_filename(const char *prefix, const char *name); +void verify_filename(const char *prefix, + const char *name, + int diagnose_misspelt_rev); +void verify_non_filename(const char *prefix, const char *name); +int path_inside_repo(const char *prefix, const char *path); + +void sanitize_stdfds(void); +int daemonize(void); + +/* + * GIT_REPO_VERSION is the version we write by default. The + * _READ variant is the highest number we know how to + * handle. + */ +#define GIT_REPO_VERSION 0 +#define GIT_REPO_VERSION_READ 1 + +/* + * You _have_ to initialize a `struct repository_format` using + * `= REPOSITORY_FORMAT_INIT` before calling `read_repository_format()`. + */ +struct repository_format { + int version; + int precious_objects; + char *partial_clone; /* value of extensions.partialclone */ + int worktree_config; + int is_bare; + int hash_algo; + int sparse_index; + char *work_tree; + struct string_list unknown_extensions; + struct string_list v1_only_extensions; +}; + +/* + * Always use this to initialize a `struct repository_format` + * to a well-defined, default state before calling + * `read_repository()`. + */ +#define REPOSITORY_FORMAT_INIT \ +{ \ + .version = -1, \ + .is_bare = -1, \ + .hash_algo = GIT_HASH_SHA1, \ + .unknown_extensions = STRING_LIST_INIT_DUP, \ + .v1_only_extensions = STRING_LIST_INIT_DUP, \ +} + +/* + * Read the repository format characteristics from the config file "path" into + * "format" struct. Returns the numeric version. On error, or if no version is + * found in the configuration, -1 is returned, format->version is set to -1, + * and all other fields in the struct are set to the default configuration + * (REPOSITORY_FORMAT_INIT). Always initialize the struct using + * REPOSITORY_FORMAT_INIT before calling this function. + */ +int read_repository_format(struct repository_format *format, const char *path); + +/* + * Free the memory held onto by `format`, but not the struct itself. + * (No need to use this after `read_repository_format()` fails.) + */ +void clear_repository_format(struct repository_format *format); + +/* + * Verify that the repository described by repository_format is something we + * can read. If it is, return 0. Otherwise, return -1, and "err" will describe + * any errors encountered. + */ +int verify_repository_format(const struct repository_format *format, + struct strbuf *err); + +/* + * Check the repository format version in the path found in get_git_dir(), + * and die if it is a version we don't understand. Generally one would + * set_git_dir() before calling this, and use it only for "are we in a valid + * repo?". + * + * If successful and fmt is not NULL, fill fmt with data. + */ +void check_repository_format(struct repository_format *fmt); + ++const char *get_template_dir(const char *option_template); ++ +/* + * NOTE NOTE NOTE!! + * + * PERM_UMASK, OLD_PERM_GROUP and OLD_PERM_EVERYBODY enumerations must + * not be changed. Old repositories have core.sharedrepository written in + * numeric format, and therefore these values are preserved for compatibility + * reasons. + */ +enum sharedrepo { + PERM_UMASK = 0, + OLD_PERM_GROUP = 1, + OLD_PERM_EVERYBODY = 2, + PERM_GROUP = 0660, + PERM_EVERYBODY = 0664 +}; +int git_config_perm(const char *var, const char *value); + +struct startup_info { + int have_repository; + const char *prefix; + const char *original_cwd; +}; +extern struct startup_info *startup_info; +extern const char *tmp_original_cwd; + +#endif /* SETUP_H */ diff --cc t/helper/test-path-utils.c index 2ef53d5f7a,0e0de21807..164b6a6832 --- a/t/helper/test-path-utils.c +++ b/t/helper/test-path-utils.c @@@ -1,12 -1,7 +1,13 @@@ #include "test-tool.h" #include "cache.h" +#include "abspath.h" +#include "environment.h" +#include "path.h" +#include "setup.h" #include "string-list.h" +#include "trace.h" #include "utf8.h" ++#include "copy.h" /* * A "string_list_each_func_t" function that normalizes an entry from diff --cc t/t1450-fsck.sh index 8c442adb1a,6277ce6b20..21d65b207b --- a/t/t1450-fsck.sh +++ b/t/t1450-fsck.sh @@@ -1020,34 -1023,41 +1020,71 @@@ test_expect_success 'fsck error on gita test_cmp expected actual ' +test_expect_success 'fsck detects problems in worktree index' ' + test_when_finished "git worktree remove -f wt" && + git worktree add wt && + + echo "this will be removed to break the worktree index" >wt/file && + git -C wt add file && + blob=$(git -C wt rev-parse :file) && + remove_object $blob && + + test_must_fail git fsck --name-objects >actual 2>&1 && + cat >expect <<-EOF && + missing blob $blob (.git/worktrees/wt/index:file) + EOF + test_cmp expect actual +' + +test_expect_success 'fsck reports problems in main index without filename' ' + test_when_finished "rm -f .git/index && git read-tree HEAD" && + echo "this object will be removed to break the main index" >file && + git add file && + blob=$(git rev-parse :file) && + remove_object $blob && + + test_must_fail git fsck --name-objects >actual 2>&1 && + cat >expect <<-EOF && + missing blob $blob (:file) + EOF + test_cmp expect actual +' + + test_expect_success 'fsck warning on symlink target with excessive length' ' + symlink_target=$(printf "pattern %032769d" 1 | git hash-object -w --stdin) && + test_when_finished "remove_object $symlink_target" && + tree=$(printf "120000 blob %s\t%s\n" $symlink_target symlink | git mktree) && + test_when_finished "remove_object $tree" && + cat >expected <<-EOF && + warning in blob $symlink_target: symlinkTargetLength: symlink target too long + EOF + git fsck --no-dangling >actual 2>&1 && + test_cmp expected actual + ' + + test_expect_success 'fsck warning on symlink target pointing inside git dir' ' + gitdir=$(printf ".git" | git hash-object -w --stdin) && + ntfs_gitdir=$(printf "GIT~1" | git hash-object -w --stdin) && + hfs_gitdir=$(printf ".${u200c}git" | git hash-object -w --stdin) && + inside_gitdir=$(printf "nested/.git/config" | git hash-object -w --stdin) && + benign_target=$(printf "legit/config" | git hash-object -w --stdin) && + tree=$(printf "120000 blob %s\t%s\n" \ + $benign_target benign_target \ + $gitdir gitdir \ + $hfs_gitdir hfs_gitdir \ + $inside_gitdir inside_gitdir \ + $ntfs_gitdir ntfs_gitdir | + git mktree) && + for o in $gitdir $ntfs_gitdir $hfs_gitdir $inside_gitdir $benign_target $tree + do + test_when_finished "remove_object $o" || return 1 + done && + printf "warning in blob %s: symlinkPointsToGitDir: symlink target points to git dir\n" \ + $gitdir $hfs_gitdir $inside_gitdir $ntfs_gitdir | + sort >expected && + git fsck --no-dangling >actual 2>&1 && + sort actual >actual.sorted && + test_cmp expected actual.sorted + ' + test_done