summaryrefslogtreecommitdiffstats
path: root/debian/patches/0004-hook-clone-protections-add-escape-hatch.diff
diff options
context:
space:
mode:
Diffstat (limited to 'debian/patches/0004-hook-clone-protections-add-escape-hatch.diff')
-rw-r--r--debian/patches/0004-hook-clone-protections-add-escape-hatch.diff182
1 files changed, 182 insertions, 0 deletions
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 <johannes.schindelin@gmx.de>
+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 <johannes.schindelin@gmx.de>
+Signed-off-by: Junio C Hamano <gitster@pobox.com>
+Signed-off-by: Jonathan Nieder <jrnieder@gmail.com>
+---
+ 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