git.vger.kernel.org archive mirror
 help / color / mirror / Atom feed
From: "Johannes Schindelin via GitGitGadget" <gitgitgadget@gmail.com>
To: git@vger.kernel.org
Cc: Johannes Schindelin <johannes.schindelin@gmx.de>,
	Johannes Schindelin <johannes.schindelin@gmx.de>
Subject: [PATCH 7/8] hooks(clone protections): simplify templates hooks validation
Date: Fri, 17 May 2024 23:15:55 +0000	[thread overview]
Message-ID: <c487bd06be813f7442494fb124ccb2c48d327dcd.1715987756.git.gitgitgadget@gmail.com> (raw)
In-Reply-To: <pull.1732.git.1715987756.gitgitgadget@gmail.com>

From: Johannes Schindelin <johannes.schindelin@gmx.de>

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 <johannes.schindelin@gmx.de>
---
 builtin/init-db.c |  7 +++++++
 hook.c            | 43 ++++++++++++++++---------------------------
 hook.h            | 10 ++++++++++
 setup.c           |  1 +
 t/t5601-clone.sh  | 19 +++++++++++++++++++
 5 files changed, 53 insertions(+), 27 deletions(-)

diff --git a/builtin/init-db.c b/builtin/init-db.c
index a101e7f94c1..64357fdada4 100644
--- a/builtin/init-db.c
+++ b/builtin/init-db.c
@@ -10,6 +10,8 @@
 #include "exec-cmd.h"
 #include "parse-options.h"
 #include "worktree.h"
+#include "run-command.h"
+#include "hook.h"
 
 #ifdef NO_TRUSTABLE_FILEMODE
 #define TEST_FILEMODE 0
@@ -28,6 +30,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
@@ -80,6 +83,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/hook.c b/hook.c
index f810ee133be..b69cc691bdf 100644
--- a/hook.c
+++ b/hook.c
@@ -4,32 +4,6 @@
 #include "config.h"
 #include "strmap.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;
 
@@ -60,6 +34,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, void *d)
 {
 	struct strset *set = d;
@@ -131,7 +121,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 4258b13da0d..e2034ee8b23 100644
--- a/hook.h
+++ b/hook.h
@@ -82,4 +82,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 c3301f5ab82..7f7538c9bf7 100644
--- a/setup.c
+++ b/setup.c
@@ -7,6 +7,7 @@
 #include "promisor-remote.h"
 #include "quote.h"
 #include "exec-cmd.h"
+#include "hook.h"
 
 static int inside_git_dir = -1;
 static int inside_work_tree = -1;
diff --git a/t/t5601-clone.sh b/t/t5601-clone.sh
index 20deca0231b..71eaa3d1e14 100755
--- a/t/t5601-clone.sh
+++ b/t/t5601-clone.sh
@@ -819,6 +819,25 @@ test_expect_success 'clone with init.templatedir runs hooks' '
 		git config --unset init.templateDir &&
 		! 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 &&
+		! grep "active .* hook found" err &&
+		test_path_is_file hook-run-submodule/hook.run &&
+		test_path_is_file hook-run-submodule/sub/hook.run
 	)
 '
 
-- 
gitgitgadget


  parent reply	other threads:[~2024-05-17 23:16 UTC|newest]

Thread overview: 54+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2024-05-17 23:15 [PATCH 0/8] Various fixes for v2.45.1 and friends Johannes Schindelin via GitGitGadget
2024-05-17 23:15 ` [PATCH 1/8] hook: plug a new memory leak Johannes Schindelin via GitGitGadget
2024-05-17 23:15 ` [PATCH 2/8] init: use the correct path of the templates directory again Johannes Schindelin via GitGitGadget
2024-05-17 23:15 ` [PATCH 3/8] Revert "core.hooksPath: add some protection while cloning" Johannes Schindelin via GitGitGadget
2024-05-17 23:15 ` [PATCH 4/8] tests: verify that `clone -c core.hooksPath=/dev/null` works again Johannes Schindelin via GitGitGadget
2024-05-18  0:10   ` Junio C Hamano
2024-05-18 18:58     ` Johannes Schindelin
2024-05-17 23:15 ` [PATCH 5/8] hook(clone protections): add escape hatch Johannes Schindelin via GitGitGadget
2024-05-18  0:21   ` Junio C Hamano
2024-05-17 23:15 ` [PATCH 6/8] hooks(clone protections): special-case current Git LFS hooks Johannes Schindelin via GitGitGadget
2024-05-18  0:20   ` Junio C Hamano
2024-05-17 23:15 ` Johannes Schindelin via GitGitGadget [this message]
2024-05-17 23:15 ` [PATCH 8/8] Revert "Add a helper function to compare file contents" Johannes Schindelin via GitGitGadget
2024-05-17 23:52 ` [PATCH 0/8] Various fixes for v2.45.1 and friends Junio C Hamano
2024-05-18  0:02   ` Johannes Schindelin
2024-05-18 10:32 ` [PATCH v2 " Johannes Schindelin via GitGitGadget
2024-05-18 10:32   ` [PATCH v2 1/8] hook: plug a new memory leak Johannes Schindelin via GitGitGadget
2024-05-18 10:32   ` [PATCH v2 2/8] init: use the correct path of the templates directory again Johannes Schindelin via GitGitGadget
2024-05-18 10:32   ` [PATCH v2 3/8] Revert "core.hooksPath: add some protection while cloning" Johannes Schindelin via GitGitGadget
2024-05-18 10:32   ` [PATCH v2 4/8] tests: verify that `clone -c core.hooksPath=/dev/null` works again Johannes Schindelin via GitGitGadget
2024-05-18 10:32   ` [PATCH v2 5/8] hook(clone protections): add escape hatch Johannes Schindelin via GitGitGadget
2024-05-18 18:14     ` Jeff King
2024-05-18 18:54       ` Junio C Hamano
2024-05-18 19:35         ` Jeff King
2024-05-18 19:37         ` Johannes Schindelin
2024-05-18 19:32       ` Johannes Schindelin
2024-05-18 19:47         ` Jeff King
2024-05-18 20:06           ` Johannes Schindelin
2024-05-18 21:12             ` Jeff King
2024-05-19  1:15               ` Junio C Hamano
2024-05-20 16:05                 ` Johannes Schindelin
2024-05-20 18:18                   ` Junio C Hamano
2024-05-20 19:38                     ` Johannes Schindelin
2024-05-20 20:07                       ` Junio C Hamano
2024-05-20 21:03                       ` Johannes Schindelin
2024-05-18 10:32   ` [PATCH v2 6/8] hooks(clone protections): special-case current Git LFS hooks Johannes Schindelin via GitGitGadget
2024-05-18 10:32   ` [PATCH v2 7/8] hooks(clone protections): simplify templates hooks validation Johannes Schindelin via GitGitGadget
2024-05-18 10:32   ` [PATCH v2 8/8] Revert "Add a helper function to compare file contents" Johannes Schindelin via GitGitGadget
2024-05-18 17:07   ` [PATCH v2 0/8] Various fixes for v2.45.1 and friends Junio C Hamano
2024-05-18 19:22     ` Johannes Schindelin
2024-05-18 20:13       ` Johannes Schindelin
2024-05-20 20:21   ` [PATCH v3 0/6] " Johannes Schindelin via GitGitGadget
2024-05-20 20:22     ` [PATCH v3 1/6] hook: plug a new memory leak Johannes Schindelin via GitGitGadget
2024-05-20 20:22     ` [PATCH v3 2/6] init: use the correct path of the templates directory again Johannes Schindelin via GitGitGadget
2024-05-20 20:22     ` [PATCH v3 3/6] Revert "core.hooksPath: add some protection while cloning" Johannes Schindelin via GitGitGadget
2024-05-20 20:22     ` [PATCH v3 4/6] tests: verify that `clone -c core.hooksPath=/dev/null` works again Johannes Schindelin via GitGitGadget
2024-05-20 20:22     ` [PATCH v3 5/6] clone: drop the protections where hooks aren't run Johannes Schindelin via GitGitGadget
2024-05-20 20:22     ` [PATCH v3 6/6] Revert "Add a helper function to compare file contents" Johannes Schindelin via GitGitGadget
2024-05-20 23:56     ` [PATCH v3 0/6] Various fixes for v2.45.1 and friends Junio C Hamano
2024-05-21  5:33       ` Junio C Hamano
2024-05-21 18:14         ` Junio C Hamano
2024-05-21 22:33     ` brian m. carlson
2024-05-21 22:40       ` Junio C Hamano
2024-05-21 23:04       ` Junio C Hamano

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=c487bd06be813f7442494fb124ccb2c48d327dcd.1715987756.git.gitgitgadget@gmail.com \
    --to=gitgitgadget@gmail.com \
    --cc=git@vger.kernel.org \
    --cc=johannes.schindelin@gmx.de \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).