From e55c3bf4c0ec4c7def54d18a5e951daa9bb25c6c Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Mon, 20 May 2024 07:14:39 +0200 Subject: Adding debian version 1:2.45.1-1. Signed-off-by: Daniel Baumann --- .../patches/0001-hook-plug-a-new-memory-leak.diff | 34 ++++ ....hooksPath-add-some-protection-while-cloni.diff | 82 +++++++++ ...y-that-clone-c-core.hooksPath-dev-null-wor.diff | 48 +++++ ...04-hook-clone-protections-add-escape-hatch.diff | 182 +++++++++++++++++++ ...-protections-special-case-current-Git-LFS-.diff | 82 +++++++++ ...-protections-simplify-templates-hooks-vali.diff | 198 +++++++++++++++++++++ ...a-helper-function-to-compare-file-contents.diff | 185 +++++++++++++++++++ debian/patches/series | 7 + 8 files changed, 818 insertions(+) create mode 100644 debian/patches/0001-hook-plug-a-new-memory-leak.diff create mode 100644 debian/patches/0002-Revert-core.hooksPath-add-some-protection-while-cloni.diff create mode 100644 debian/patches/0003-tests-verify-that-clone-c-core.hooksPath-dev-null-wor.diff create mode 100644 debian/patches/0004-hook-clone-protections-add-escape-hatch.diff create mode 100644 debian/patches/0005-hooks-clone-protections-special-case-current-Git-LFS-.diff create mode 100644 debian/patches/0006-hooks-clone-protections-simplify-templates-hooks-vali.diff create mode 100644 debian/patches/0007-Revert-Add-a-helper-function-to-compare-file-contents.diff create mode 100644 debian/patches/series (limited to 'debian/patches') diff --git a/debian/patches/0001-hook-plug-a-new-memory-leak.diff b/debian/patches/0001-hook-plug-a-new-memory-leak.diff new file mode 100644 index 0000000..ab74831 --- /dev/null +++ b/debian/patches/0001-hook-plug-a-new-memory-leak.diff @@ -0,0 +1,34 @@ +From 94f95a123b10f3837e181ad93b81f1a4f53bb8fc Mon Sep 17 00:00:00 2001 +From: Johannes Schindelin +Date: Sat, 18 May 2024 10:32:39 +0000 +Subject: hook: plug a new memory leak + +commit 2811ce3a79dc8a0105a6defb59718b35f5b397aa upstream. + +In 8db1e8743c0 (clone: prevent hooks from running during a clone, +2024-03-28), I introduced an inadvertent memory leak that was +unfortunately not caught before v2.45.1 was released. Here is a fix. + +Signed-off-by: Johannes Schindelin +Signed-off-by: Junio C Hamano +Signed-off-by: Jonathan Nieder +--- + hook.c | 4 +++- + 1 file changed, 3 insertions(+), 1 deletion(-) + +diff --git a/hook.c b/hook.c +index eebc4d44734..8de469b134a 100644 +--- a/hook.c ++++ b/hook.c +@@ -26,8 +26,10 @@ static int identical_to_template_hook(const char *name, const char *path) + found_template_hook = access(template_path.buf, X_OK) >= 0; + } + #endif +- if (!found_template_hook) ++ if (!found_template_hook) { ++ strbuf_release(&template_path); + return 0; ++ } + + ret = do_files_match(template_path.buf, path); + diff --git a/debian/patches/0002-Revert-core.hooksPath-add-some-protection-while-cloni.diff b/debian/patches/0002-Revert-core.hooksPath-add-some-protection-while-cloni.diff new file mode 100644 index 0000000..8e1c975 --- /dev/null +++ b/debian/patches/0002-Revert-core.hooksPath-add-some-protection-while-cloni.diff @@ -0,0 +1,82 @@ +From 7db946419c29e185f1cc6e544cfb47b442019ac7 Mon Sep 17 00:00:00 2001 +From: Johannes Schindelin +Date: Sat, 18 May 2024 10:32:41 +0000 +Subject: Revert "core.hooksPath: add some protection while cloning" + +commit f13e8e2ea56ceef593311b3cff1ba7ba1a493682 upstream. + +This defense-in-depth was intended to protect the clone operation +against future escalations where bugs in `git clone` would allow +attackers to write arbitrary files in the `.git/` directory would allow +for Remote Code Execution attacks via maliciously-placed hooks. + +However, it turns out that the `core.hooksPath` protection has +unintentional side effects so severe that they do not justify the +benefit of the protections. For example, it has been reported in +https://lore.kernel.org/git/FAFA34CB-9732-4A0A-87FB-BDB272E6AEE8@alchemists.io/ +that the following invocation, which is intended to make `git clone` +safer, is itself broken by that protective measure: + + git clone --config core.hooksPath=/dev/null + +Since it turns out that the benefit does not justify the cost, let's revert +20f3588efc6 (core.hooksPath: add some protection while cloning, +2024-03-30). + +Signed-off-by: Johannes Schindelin +Signed-off-by: Junio C Hamano +Signed-off-by: Jonathan Nieder +--- + config.c | 13 +------------ + t/t1800-hook.sh | 15 --------------- + 2 files changed, 1 insertion(+), 27 deletions(-) + +diff --git a/config.c b/config.c +index 77a0fd2d80e..ae3652b08fa 100644 +--- a/config.c ++++ b/config.c +@@ -1416,19 +1416,8 @@ static int git_default_core_config(const char *var, const char *value, + if (!strcmp(var, "core.attributesfile")) + return git_config_pathname(&git_attributes_file, var, value); + +- if (!strcmp(var, "core.hookspath")) { +- if (ctx->kvi && ctx->kvi->scope == CONFIG_SCOPE_LOCAL && +- git_env_bool("GIT_CLONE_PROTECTION_ACTIVE", 0)) +- die(_("active `core.hooksPath` found in the local " +- "repository config:\n\t%s\nFor security " +- "reasons, this is disallowed by default.\nIf " +- "this is intentional and the hook should " +- "actually be run, please\nrun the command " +- "again with " +- "`GIT_CLONE_PROTECTION_ACTIVE=false`"), +- value); ++ if (!strcmp(var, "core.hookspath")) + return git_config_pathname(&git_hooks_path, var, value); +- } + + if (!strcmp(var, "core.bare")) { + is_bare_repository_cfg = git_config_bool(var, value); +diff --git a/t/t1800-hook.sh b/t/t1800-hook.sh +index 1894ebeb0e8..8b0234cf2d5 100755 +--- a/t/t1800-hook.sh ++++ b/t/t1800-hook.sh +@@ -185,19 +185,4 @@ test_expect_success 'stdin to hooks' ' + test_cmp expect actual + ' + +-test_expect_success 'clone protections' ' +- test_config core.hooksPath "$(pwd)/my-hooks" && +- mkdir -p my-hooks && +- write_script my-hooks/test-hook <<-\EOF && +- echo Hook ran $1 +- EOF +- +- git hook run test-hook 2>err && +- test_grep "Hook ran" err && +- test_must_fail env GIT_CLONE_PROTECTION_ACTIVE=true \ +- git hook run test-hook 2>err && +- test_grep "active .core.hooksPath" err && +- test_grep ! "Hook ran" err +-' +- + test_done diff --git a/debian/patches/0003-tests-verify-that-clone-c-core.hooksPath-dev-null-wor.diff b/debian/patches/0003-tests-verify-that-clone-c-core.hooksPath-dev-null-wor.diff new file mode 100644 index 0000000..9a494d9 --- /dev/null +++ b/debian/patches/0003-tests-verify-that-clone-c-core.hooksPath-dev-null-wor.diff @@ -0,0 +1,48 @@ +From ce34e1b7a072db221190446e79cb373c7f6010a5 Mon Sep 17 00:00:00 2001 +From: Johannes Schindelin +Date: Sat, 18 May 2024 10:32:42 +0000 +Subject: tests: verify that `clone -c core.hooksPath=/dev/null` works again + +commit a25a15726f4d1bf1c8362f1b3146096d6a87f965 upstream. + +As part of the protections added in Git v2.45.1 and friends, +repository-local `core.hooksPath` settings are no longer allowed, as a +defense-in-depth mechanism to prevent future Git vulnerabilities to +raise to critical level if those vulnerabilities inadvertently allow the +repository-local config to be written. + +What the added protection did not anticipate is that such a +repository-local `core.hooksPath` can not only be used to point to +maliciously-placed scripts in the current worktree, but also to +_prevent_ hooks from being called altogether. + +We just reverted the `core.hooksPath` protections, based on the Git +maintainer's recommendation in +https://lore.kernel.org/git/xmqq4jaxvm8z.fsf@gitster.g/ to address this +concern as well as related ones. Let's make sure that we won't regress +while trying to protect the clone operation further. + +Reported-by: Brooke Kuhlmann +Signed-off-by: Johannes Schindelin +Signed-off-by: Junio C Hamano +Signed-off-by: Jonathan Nieder +--- + t/t1350-config-hooks-path.sh | 7 +++++++ + 1 file changed, 7 insertions(+) + +diff --git a/t/t1350-config-hooks-path.sh b/t/t1350-config-hooks-path.sh +index f6dc83e2aab..45a04929170 100755 +--- a/t/t1350-config-hooks-path.sh ++++ b/t/t1350-config-hooks-path.sh +@@ -41,4 +41,11 @@ test_expect_success 'git rev-parse --git-path hooks' ' + test .git/custom-hooks/abc = "$(cat actual)" + ' + ++test_expect_success 'core.hooksPath=/dev/null' ' ++ git clone -c core.hooksPath=/dev/null . no-templates && ++ value="$(git -C no-templates config --local core.hooksPath)" && ++ # The Bash used by Git for Windows rewrites `/dev/null` to `nul` ++ { test /dev/null = "$value" || test nul = "$value"; } ++' ++ + test_done diff --git a/debian/patches/0004-hook-clone-protections-add-escape-hatch.diff b/debian/patches/0004-hook-clone-protections-add-escape-hatch.diff new file mode 100644 index 0000000..b2aa135 --- /dev/null +++ b/debian/patches/0004-hook-clone-protections-add-escape-hatch.diff @@ -0,0 +1,182 @@ +From 1f34eea689413fa10a664f4c154b097be7796b0a Mon Sep 17 00:00:00 2001 +From: Johannes Schindelin +Date: Sat, 18 May 2024 10:32:43 +0000 +Subject: hook(clone protections): add escape hatch + +commit 85811d32aca9f0ba324a04bd8709c315d472efbe upstream. + +As defense-in-depth measures, v2.39.4 and friends leading up to v2.45.1 +introduced code that detects when hooks have been installed during a +`git clone`, which is indicative of a common attack vector with critical +severity that allows Remote Code Execution. + +There are legitimate use cases for such behavior, though, for example +when those hooks stem from Git's own templates, which system +administrators are at liberty to modify to enforce, say, commit message +conventions. The git clone protections specifically add exceptions to +allow for that. + +Another legitimate use case that has been identified too late to be +handled in these security bug-fix versions is Git LFS: It behaves +somewhat similar to common attack vectors by writing a few hooks while +running the `smudge` filter during a regular clone, which means that Git +has no chance to know that the hooks are benign and e.g. the +`post-checkout` hook can be safely executed as part of the clone +operation. + +To help Git LFS, and other tools behaving similarly (if there are any), +let's add a new, multi-valued `safe.hook.sha256` config setting. Like +the already-existing `safe.*` settings, it is ignored in +repository-local configs, and it is interpreted as a list of SHA-256 +checksums of hooks' contents that are safe to execute during a clone +operation. Future Git LFS versions will need to write those entries at +the same time they install the `smudge`/`clean` filters. + +Signed-off-by: Johannes Schindelin +Signed-off-by: Junio C Hamano +Signed-off-by: Jonathan Nieder +--- + Documentation/config/safe.txt | 6 +++ + hook.c | 69 ++++++++++++++++++++++++++++++++--- + t/t1800-hook.sh | 15 ++++++++ + 3 files changed, 85 insertions(+), 5 deletions(-) + +diff --git a/Documentation/config/safe.txt b/Documentation/config/safe.txt +index 577df40223a..e2eb4992bef 100644 +--- a/Documentation/config/safe.txt ++++ b/Documentation/config/safe.txt +@@ -59,3 +59,9 @@ which id the original user has. + If that is not what you would prefer and want git to only trust + repositories that are owned by root instead, then you can remove + the `SUDO_UID` variable from root's environment before invoking git. ++ ++safe.hook.sha256:: ++ The value is the SHA-256 of hooks that are considered to be safe ++ to run during a clone operation. +++ ++Multiple values can be added via `git config --global --add`. +diff --git a/hook.c b/hook.c +index 8de469b134a..9eca6c0103a 100644 +--- a/hook.c ++++ b/hook.c +@@ -10,6 +10,9 @@ + #include "environment.h" + #include "setup.h" + #include "copy.h" ++#include "strmap.h" ++#include "hash-ll.h" ++#include "hex.h" + + static int identical_to_template_hook(const char *name, const char *path) + { +@@ -37,11 +40,66 @@ static int identical_to_template_hook(const char *name, const char *path) + return ret; + } + ++static struct strset safe_hook_sha256s = STRSET_INIT; ++static int safe_hook_sha256s_initialized; ++ ++static int get_sha256_of_file_contents(const char *path, char *sha256) ++{ ++ struct strbuf sb = STRBUF_INIT; ++ int fd; ++ ssize_t res; ++ ++ git_hash_ctx ctx; ++ const struct git_hash_algo *algo = &hash_algos[GIT_HASH_SHA256]; ++ unsigned char hash[GIT_MAX_RAWSZ]; ++ ++ if ((fd = open(path, O_RDONLY)) < 0) ++ return -1; ++ res = strbuf_read(&sb, fd, 400); ++ close(fd); ++ if (res < 0) ++ return -1; ++ ++ algo->init_fn(&ctx); ++ algo->update_fn(&ctx, sb.buf, sb.len); ++ strbuf_release(&sb); ++ algo->final_fn(hash, &ctx); ++ ++ hash_to_hex_algop_r(sha256, hash, algo); ++ ++ return 0; ++} ++ ++static int safe_hook_cb(const char *key, const char *value, ++ const struct config_context *ctx UNUSED, void *d) ++{ ++ struct strset *set = d; ++ ++ if (value && !strcmp(key, "safe.hook.sha256")) ++ strset_add(set, value); ++ ++ return 0; ++} ++ ++static int is_hook_safe_during_clone(const char *name, const char *path, char *sha256) ++{ ++ if (get_sha256_of_file_contents(path, sha256) < 0) ++ return 0; ++ ++ if (!safe_hook_sha256s_initialized) { ++ safe_hook_sha256s_initialized = 1; ++ git_protected_config(safe_hook_cb, &safe_hook_sha256s); ++ } ++ ++ return strset_contains(&safe_hook_sha256s, sha256); ++} ++ + const char *find_hook(const char *name) + { + static struct strbuf path = STRBUF_INIT; + + int found_hook; ++ char sha256[GIT_SHA256_HEXSZ + 1] = { '\0' }; + + strbuf_reset(&path); + strbuf_git_path(&path, "hooks/%s", name); +@@ -73,13 +131,14 @@ const char *find_hook(const char *name) + return NULL; + } + if (!git_hooks_path && git_env_bool("GIT_CLONE_PROTECTION_ACTIVE", 0) && +- !identical_to_template_hook(name, path.buf)) ++ !identical_to_template_hook(name, path.buf) && ++ !is_hook_safe_during_clone(name, path.buf, sha256)) + die(_("active `%s` hook found during `git clone`:\n\t%s\n" + "For security reasons, this is disallowed by default.\n" +- "If this is intentional and the hook should actually " +- "be run, please\nrun the command again with " +- "`GIT_CLONE_PROTECTION_ACTIVE=false`"), +- name, path.buf); ++ "If this is intentional and the hook is safe to run, " ++ "please run the following command and try again:\n\n" ++ " git config --global --add safe.hook.sha256 %s"), ++ name, path.buf, sha256); + return path.buf; + } + +diff --git a/t/t1800-hook.sh b/t/t1800-hook.sh +index 8b0234cf2d5..cbdf60c451a 100755 +--- a/t/t1800-hook.sh ++++ b/t/t1800-hook.sh +@@ -185,4 +185,19 @@ test_expect_success 'stdin to hooks' ' + test_cmp expect actual + ' + ++test_expect_success '`safe.hook.sha256` and clone protections' ' ++ git init safe-hook && ++ write_script safe-hook/.git/hooks/pre-push <<-\EOF && ++ echo "called hook" >safe-hook.log ++ EOF ++ ++ test_must_fail env GIT_CLONE_PROTECTION_ACTIVE=true \ ++ git -C safe-hook hook run pre-push 2>err && ++ cmd="$(grep "git config --global --add safe.hook.sha256 [0-9a-f]" err)" && ++ eval "$cmd" && ++ GIT_CLONE_PROTECTION_ACTIVE=true \ ++ git -C safe-hook hook run pre-push && ++ test "called hook" = "$(cat safe-hook/safe-hook.log)" ++' ++ + test_done diff --git a/debian/patches/0005-hooks-clone-protections-special-case-current-Git-LFS-.diff b/debian/patches/0005-hooks-clone-protections-special-case-current-Git-LFS-.diff new file mode 100644 index 0000000..bad67cd --- /dev/null +++ b/debian/patches/0005-hooks-clone-protections-special-case-current-Git-LFS-.diff @@ -0,0 +1,82 @@ +From 09595d6984b41cbb6f653643f826fe009c56b493 Mon Sep 17 00:00:00 2001 +From: Johannes Schindelin +Date: Sat, 18 May 2024 10:32:44 +0000 +Subject: hooks(clone protections): special-case current Git LFS hooks + +commit c65d0f9ee6894cdf7feeb51639870bfaf826c905 upstream. + +A notable regression in v2.45.1 and friends (all the way down to +v2.39.4) has been that Git LFS-enabled clones error out with a message +indicating that the `post-checkout` hook has been tampered with while +cloning, and as a safety measure it is not executed. + +A generic fix for benign third-party applications wishing to write hooks +during clone operations has been implemented in the parent of this +commit: said applications are expected to add `safe.hook.sha256` values +to a protected config. + +However, the current version of Git LFS, v3.5.1, cannot be adapted +retroactively; Therefore, let's just hard-code the SHA-256 values for +this version. That way, Git LFS usage will no longer be broken, and the +next Git LFS version can be taught to add those `safe.hook.sha256` +entries. + +Signed-off-by: Johannes Schindelin +Signed-off-by: Junio C Hamano +Signed-off-by: Jonathan Nieder +--- + hook.c | 11 +++++++++++ + t/t1800-hook.sh | 20 ++++++++++++++++++++ + 2 files changed, 31 insertions(+) + +diff --git a/hook.c b/hook.c +index 9eca6c0103a..fc0548edb66 100644 +--- a/hook.c ++++ b/hook.c +@@ -88,6 +88,17 @@ static int is_hook_safe_during_clone(const char *name, const char *path, char *s + + if (!safe_hook_sha256s_initialized) { + safe_hook_sha256s_initialized = 1; ++ ++ /* Hard-code known-safe values for Git LFS v3.4.0..v3.5.1 */ ++ /* pre-push */ ++ strset_add(&safe_hook_sha256s, "df5417b2daa3aa144c19681d1e997df7ebfe144fb7e3e05138bd80ae998008e4"); ++ /* post-checkout */ ++ strset_add(&safe_hook_sha256s, "791471b4ff472aab844a4fceaa48bbb0a12193616f971e8e940625498b4938a6"); ++ /* post-commit */ ++ strset_add(&safe_hook_sha256s, "21e961572bb3f43a5f2fbafc1cc764d86046cc2e5f0bbecebfe9684a0b73b664"); ++ /* post-merge */ ++ strset_add(&safe_hook_sha256s, "75da0da66a803b4b030ad50801ba57062c6196105eb1d2251590d100edb9390b"); ++ + git_protected_config(safe_hook_cb, &safe_hook_sha256s); + } + +diff --git a/t/t1800-hook.sh b/t/t1800-hook.sh +index cbdf60c451a..c51be5f7a06 100755 +--- a/t/t1800-hook.sh ++++ b/t/t1800-hook.sh +@@ -200,4 +200,24 @@ test_expect_success '`safe.hook.sha256` and clone protections' ' + test "called hook" = "$(cat safe-hook/safe-hook.log)" + ' + ++write_lfs_pre_push_hook () { ++ write_script "$1" <<-\EOF ++ command -v git-lfs >/dev/null 2>&1 || { echo >&2 "\nThis repository is configured for Git LFS but 'git-lfs' was not found on your path. If you no longer wish to use Git LFS, remove this hook by deleting the 'pre-push' file in the hooks directory (set by 'core.hookspath'; usually '.git/hooks').\n"; exit 2; } ++ git lfs pre-push "$@" ++ EOF ++} ++ ++test_expect_success 'Git LFS special-handling in clone protections' ' ++ git init lfs-hooks && ++ write_lfs_pre_push_hook lfs-hooks/.git/hooks/pre-push && ++ write_script git-lfs <<-\EOF && ++ echo "called $*" >fake-git-lfs.log ++ EOF ++ ++ PATH="$PWD:$PATH" GIT_CLONE_PROTECTION_ACTIVE=true \ ++ git -C lfs-hooks hook run pre-push && ++ test_write_lines "called pre-push" >expect && ++ test_cmp lfs-hooks/fake-git-lfs.log expect ++' ++ + test_done diff --git a/debian/patches/0006-hooks-clone-protections-simplify-templates-hooks-vali.diff b/debian/patches/0006-hooks-clone-protections-simplify-templates-hooks-vali.diff new file mode 100644 index 0000000..a0642e3 --- /dev/null +++ b/debian/patches/0006-hooks-clone-protections-simplify-templates-hooks-vali.diff @@ -0,0 +1,198 @@ +From 8813bb5f4109991b88c98584a4abbb2d06cfbc28 Mon Sep 17 00:00:00 2001 +From: Johannes Schindelin +Date: Sat, 18 May 2024 10:32:45 +0000 +Subject: hooks(clone protections): simplify templates hooks validation + +commit eff37e9b1dec25a3e1297eb89a36d8e68fe01b40 upstream. + +When an active hook is encountered during a clone operation, to protect +against Remote Code Execution attack vectors, Git checks whether the +hook was copied over from the templates directory. + +When that logic was introduced, there was no other way to check this +than to add a function to compare files. + +In the meantime, we've added code to compute the SHA-256 checksum of a +given hook and compare that checksum against a list of known-safe ones. + +Let's simplify the logic by adding to said list when copying the +templates' hooks. + +We need to be careful to support multi-process operations such as +recursive submodule clones: In such a scenario, the list of SHA-256 +checksums that is kept in memory is not enough, we also have to pass the +information down to child processes via `GIT_CONFIG_PARAMETERS`. + +Extend the regression test in t5601 to ensure that recursive clones are +handled as expected. + +Note: Technically there is no way that the checksums computed while +initializing the submodules' gitdirs can be passed to the process that +performs the checkout: For historical reasons, these operations are +performed in processes spawned in separate loops from the +super-project's `git clone` process. But since the templates from which +the submodules are initialized are the very same as the ones from which +the super-project is initialized, we can get away with using the list of +SHA-256 checksums that is computed when initializing the super-project +and passing that down to the `submodule--helper` processes that perform +the recursive checkout. + +Signed-off-by: Johannes Schindelin +Signed-off-by: Junio C Hamano +Signed-off-by: Jonathan Nieder +--- + hook.c | 43 ++++++++++++++++--------------------------- + hook.h | 10 ++++++++++ + setup.c | 7 +++++++ + t/t5601-clone.sh | 19 +++++++++++++++++++ + 4 files changed, 52 insertions(+), 27 deletions(-) + +diff --git a/hook.c b/hook.c +index fc0548edb66..8ac51c9912b 100644 +--- a/hook.c ++++ b/hook.c +@@ -14,32 +14,6 @@ + #include "hash-ll.h" + #include "hex.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) { +- strbuf_release(&template_path); +- return 0; +- } +- +- ret = do_files_match(template_path.buf, path); +- +- strbuf_release(&template_path); +- return ret; +-} +- + static struct strset safe_hook_sha256s = STRSET_INIT; + static int safe_hook_sha256s_initialized; + +@@ -70,6 +44,22 @@ static int get_sha256_of_file_contents(const char *path, char *sha256) + return 0; + } + ++void add_safe_hook(const char *path) ++{ ++ char sha256[GIT_SHA256_HEXSZ + 1] = { '\0' }; ++ ++ if (!get_sha256_of_file_contents(path, sha256)) { ++ char *p; ++ ++ strset_add(&safe_hook_sha256s, sha256); ++ ++ /* support multi-process operations e.g. recursive clones */ ++ p = xstrfmt("safe.hook.sha256=%s", sha256); ++ git_config_push_parameter(p); ++ free(p); ++ } ++} ++ + static int safe_hook_cb(const char *key, const char *value, + const struct config_context *ctx UNUSED, void *d) + { +@@ -142,7 +132,6 @@ const char *find_hook(const char *name) + return NULL; + } + if (!git_hooks_path && git_env_bool("GIT_CLONE_PROTECTION_ACTIVE", 0) && +- !identical_to_template_hook(name, path.buf) && + !is_hook_safe_during_clone(name, path.buf, sha256)) + die(_("active `%s` hook found during `git clone`:\n\t%s\n" + "For security reasons, this is disallowed by default.\n" +diff --git a/hook.h b/hook.h +index 19ab9a5806e..b4770d9bd88 100644 +--- a/hook.h ++++ b/hook.h +@@ -87,4 +87,14 @@ int run_hooks(const char *hook_name); + * hook. This function behaves like the old run_hook_le() API. + */ + int run_hooks_l(const char *hook_name, ...); ++ ++/** ++ * Mark the contents of the provided path as safe to run during a clone ++ * operation. ++ * ++ * This function is mainly used when copying templates to mark the ++ * just-copied hooks as benign. ++ */ ++void add_safe_hook(const char *path); ++ + #endif +diff --git a/setup.c b/setup.c +index 30f243fc32d..25828a85ec3 100644 +--- a/setup.c ++++ b/setup.c +@@ -17,6 +17,8 @@ + #include "trace2.h" + #include "worktree.h" + #include "exec-cmd.h" ++#include "run-command.h" ++#include "hook.h" + + static int inside_git_dir = -1; + static int inside_work_tree = -1; +@@ -1868,6 +1870,7 @@ static void copy_templates_1(struct strbuf *path, struct strbuf *template_path, + size_t path_baselen = path->len; + size_t template_baselen = template_path->len; + struct dirent *de; ++ int is_hooks_dir = ends_with(template_path->buf, "/hooks/"); + + /* Note: if ".git/hooks" file exists in the repository being + * re-initialized, /etc/core-git/templates/hooks/update would +@@ -1920,6 +1923,10 @@ static void copy_templates_1(struct strbuf *path, struct strbuf *template_path, + strbuf_release(&lnk); + } + else if (S_ISREG(st_template.st_mode)) { ++ if (is_hooks_dir && ++ is_executable(template_path->buf)) ++ add_safe_hook(template_path->buf); ++ + if (copy_file(path->buf, template_path->buf, st_template.st_mode)) + die_errno(_("cannot copy '%s' to '%s'"), + template_path->buf, path->buf); +diff --git a/t/t5601-clone.sh b/t/t5601-clone.sh +index deb1c282c71..ca3a8d1ebed 100755 +--- a/t/t5601-clone.sh ++++ b/t/t5601-clone.sh +@@ -836,6 +836,25 @@ test_expect_success 'clone with init.templatedir runs hooks' ' + git config --unset init.templateDir && + test_grep ! "active .* hook found" err && + test_path_is_missing hook-run-local-config/hook.run ++ ) && ++ ++ test_config_global protocol.file.allow always && ++ git -C tmpl/hooks submodule add "$(pwd)/tmpl/hooks" sub && ++ test_tick && ++ git -C tmpl/hooks add .gitmodules sub && ++ git -C tmpl/hooks commit -m submodule && ++ ++ ( ++ sane_unset GIT_TEMPLATE_DIR && ++ NO_SET_GIT_TEMPLATE_DIR=t && ++ export NO_SET_GIT_TEMPLATE_DIR && ++ ++ git -c init.templateDir="$(pwd)/tmpl" \ ++ clone --recurse-submodules \ ++ tmpl/hooks hook-run-submodule 2>err && ++ test_grep ! "active .* hook found" err && ++ test_path_is_file hook-run-submodule/hook.run && ++ test_path_is_file hook-run-submodule/sub/hook.run + ) + ' + diff --git a/debian/patches/0007-Revert-Add-a-helper-function-to-compare-file-contents.diff b/debian/patches/0007-Revert-Add-a-helper-function-to-compare-file-contents.diff new file mode 100644 index 0000000..6cf2874 --- /dev/null +++ b/debian/patches/0007-Revert-Add-a-helper-function-to-compare-file-contents.diff @@ -0,0 +1,185 @@ +From 13b17dea6c851b21ceb9ce163cdd7338f1ec4ecf Mon Sep 17 00:00:00 2001 +From: Johannes Schindelin +Date: Sat, 18 May 2024 10:32:46 +0000 +Subject: Revert "Add a helper function to compare file contents" + +commit 851218a8af645b0abd64882d2b88bc984aa762e9 upstream. + +Now that during a `git clone`, the hooks' contents are no longer +compared to the templates' files', the caller for which the +`do_files_match()` function was introduced is gone, and therefore this +function can be retired, too. + +This reverts commit 584de0b4c23 (Add a helper function to compare file +contents, 2024-03-30). + +Signed-off-by: Johannes Schindelin +Signed-off-by: Junio C Hamano +Signed-off-by: Jonathan Nieder +--- + copy.c | 58 -------------------------------------- + copy.h | 14 --------- + t/helper/test-path-utils.c | 10 ------- + t/t0060-path-utils.sh | 41 --------------------------- + 4 files changed, 123 deletions(-) + +diff --git a/copy.c b/copy.c +index 3df156f6cea..d9d20920126 100644 +--- a/copy.c ++++ b/copy.c +@@ -70,61 +70,3 @@ int copy_file_with_time(const char *dst, const char *src, int mode) + return copy_times(dst, src); + return status; + } +- +-static int do_symlinks_match(const char *path1, const char *path2) +-{ +- struct strbuf buf1 = STRBUF_INIT, buf2 = STRBUF_INIT; +- int ret = 0; +- +- if (!strbuf_readlink(&buf1, path1, 0) && +- !strbuf_readlink(&buf2, path2, 0)) +- ret = !strcmp(buf1.buf, buf2.buf); +- +- strbuf_release(&buf1); +- strbuf_release(&buf2); +- return ret; +-} +- +-int do_files_match(const char *path1, const char *path2) +-{ +- struct stat st1, st2; +- int fd1 = -1, fd2 = -1, ret = 1; +- char buf1[8192], buf2[8192]; +- +- if ((fd1 = open_nofollow(path1, O_RDONLY)) < 0 || +- fstat(fd1, &st1) || !S_ISREG(st1.st_mode)) { +- if (fd1 < 0 && errno == ELOOP) +- /* maybe this is a symbolic link? */ +- return do_symlinks_match(path1, path2); +- ret = 0; +- } else if ((fd2 = open_nofollow(path2, O_RDONLY)) < 0 || +- fstat(fd2, &st2) || !S_ISREG(st2.st_mode)) { +- ret = 0; +- } +- +- if (ret) +- /* to match, neither must be executable, or both */ +- ret = !(st1.st_mode & 0111) == !(st2.st_mode & 0111); +- +- if (ret) +- ret = st1.st_size == st2.st_size; +- +- while (ret) { +- ssize_t len1 = read_in_full(fd1, buf1, sizeof(buf1)); +- ssize_t len2 = read_in_full(fd2, buf2, sizeof(buf2)); +- +- if (len1 < 0 || len2 < 0 || len1 != len2) +- ret = 0; /* read error or different file size */ +- else if (!len1) /* len2 is also 0; hit EOF on both */ +- break; /* ret is still true */ +- else +- ret = !memcmp(buf1, buf2, len1); +- } +- +- if (fd1 >= 0) +- close(fd1); +- if (fd2 >= 0) +- close(fd2); +- +- return ret; +-} +diff --git a/copy.h b/copy.h +index 057259a3a7a..2af77cba864 100644 +--- a/copy.h ++++ b/copy.h +@@ -7,18 +7,4 @@ 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 --git a/t/helper/test-path-utils.c b/t/helper/test-path-utils.c +index 023ed2e1a78..bf0e23ed505 100644 +--- a/t/helper/test-path-utils.c ++++ b/t/helper/test-path-utils.c +@@ -501,16 +501,6 @@ int cmd__path_utils(int argc, const char **argv) + return !!res; + } + +- if (argc == 4 && !strcmp(argv[1], "do_files_match")) { +- int ret = do_files_match(argv[2], argv[3]); +- +- if (ret) +- printf("equal\n"); +- else +- printf("different\n"); +- return !ret; +- } +- + fprintf(stderr, "%s: unknown function name: %s\n", argv[0], + argv[1] ? argv[1] : "(there was none)"); + return 1; +diff --git a/t/t0060-path-utils.sh b/t/t0060-path-utils.sh +index 85686ee15da..0afa3d0d312 100755 +--- a/t/t0060-path-utils.sh ++++ b/t/t0060-path-utils.sh +@@ -610,45 +610,4 @@ test_expect_success !VALGRIND,RUNTIME_PREFIX,CAN_EXEC_IN_PWD '%(prefix)/ works' + test_cmp expect actual + ' + +-test_expect_success 'do_files_match()' ' +- test_seq 0 10 >0-10.txt && +- test_seq -1 10 >-1-10.txt && +- test_seq 1 10 >1-10.txt && +- test_seq 1 9 >1-9.txt && +- test_seq 0 8 >0-8.txt && +- +- test-tool path-utils do_files_match 0-10.txt 0-10.txt >out && +- +- assert_fails() { +- test_must_fail \ +- test-tool path-utils do_files_match "$1" "$2" >out && +- grep different out +- } && +- +- assert_fails 0-8.txt 1-9.txt && +- assert_fails -1-10.txt 0-10.txt && +- assert_fails 1-10.txt 1-9.txt && +- assert_fails 1-10.txt .git && +- assert_fails does-not-exist 1-10.txt && +- +- if test_have_prereq FILEMODE +- then +- cp 0-10.txt 0-10.x && +- chmod a+x 0-10.x && +- assert_fails 0-10.txt 0-10.x +- fi && +- +- if test_have_prereq SYMLINKS +- then +- ln -sf 0-10.txt symlink && +- ln -s 0-10.txt another-symlink && +- ln -s over-the-ocean yet-another-symlink && +- ln -s "$PWD/0-10.txt" absolute-symlink && +- assert_fails 0-10.txt symlink && +- test-tool path-utils do_files_match symlink another-symlink && +- assert_fails symlink yet-another-symlink && +- assert_fails symlink absolute-symlink +- fi +-' +- + test_done diff --git a/debian/patches/series b/debian/patches/series new file mode 100644 index 0000000..7ff1f37 --- /dev/null +++ b/debian/patches/series @@ -0,0 +1,7 @@ +0001-hook-plug-a-new-memory-leak.diff +0002-Revert-core.hooksPath-add-some-protection-while-cloni.diff +0003-tests-verify-that-clone-c-core.hooksPath-dev-null-wor.diff +0004-hook-clone-protections-add-escape-hatch.diff +0005-hooks-clone-protections-special-case-current-Git-LFS-.diff +0006-hooks-clone-protections-simplify-templates-hooks-vali.diff +0007-Revert-Add-a-helper-function-to-compare-file-contents.diff -- cgit v1.2.3