Skip to content

Commit 412dfe2

Browse files
committed
Sync with 2.41.2
* maint-2.41: Git 2.41.2 Git 2.40.3 Git 2.39.5 Revert "Add a helper function to compare file contents" hooks(clone protections): simplify templates hooks validation hooks(clone protections): special-case current Git LFS hooks hook(clone protections): add escape hatch tests: verify that `clone -c core.hooksPath=/dev/null` works again Revert "core.hooksPath: add some protection while cloning" init: use the correct path of the templates directory again hook: plug a new memory leak ci: stop installing "gcc-13" for osx-gcc ci: avoid bare "gcc" for osx-gcc job ci: drop mention of BREW_INSTALL_PACKAGES variable
2 parents babb4e5 + e5be6e1 commit 412dfe2

File tree

17 files changed

+201
-171
lines changed

17 files changed

+201
-171
lines changed

.github/workflows/main.yml

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -278,8 +278,7 @@ jobs:
278278
cc: clang
279279
pool: macos-13
280280
- jobname: osx-gcc
281-
cc: gcc
282-
cc_package: gcc-13
281+
cc: gcc-13
283282
pool: macos-13
284283
- jobname: linux-gcc-default
285284
cc: gcc

Documentation/RelNotes/2.39.5.txt

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
Git 2.39.5 Release Notes
2+
========================
3+
4+
Relative to Git 2.39.5, this release has fixes for regressions that
5+
were introduced in 2.39.4, most notably the error message shown when
6+
cloning Git LFS-enabled repositories. It also contains a fix for the
7+
`osx-gcc` CI job.
8+
9+
Fixes since Git 2.39.4
10+
----------------------
11+
12+
* Hooks that are intentionally installed _during_ a clone operation
13+
(like Git LFS does in its `smudge` filter) can now be marked as
14+
intentional by appending `safe.hook.sha256` values to a protected
15+
config.
16+
17+
The hooks installed by the current Git LFS version (3.5.1) are
18+
hard-coded as being safe.
19+
20+
* The `core.hooksPath` setting is allowed in repository-local
21+
configs again; The benefits of making it protected were
22+
outweighed by the cost.
23+
24+
* Fix a memory leak.
25+
26+
* CI fix.

Documentation/RelNotes/2.40.3.txt

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
Git v2.40.3 Release Notes
2+
=========================
3+
4+
This release merges up the regression bug fixes in v2.39.5,
5+
most notably the bug where cloning Git LFS-enabled repositories
6+
failed; see the release notes for that version for details.

Documentation/RelNotes/2.41.2.txt

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
Git v2.41.2 Release Notes
2+
=========================
3+
4+
This release merges up the regression bug fixes in v2.39.5 and
5+
v2.40.3, most notably the bug where cloning Git LFS-enabled
6+
repositories failed; see the release notes for these versions
7+
for details.

Documentation/config/safe.txt

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,3 +59,9 @@ which id the original user has.
5959
If that is not what you would prefer and want git to only trust
6060
repositories that are owned by root instead, then you can remove
6161
the `SUDO_UID` variable from root's environment before invoking git.
62+
63+
safe.hook.sha256::
64+
The value is the SHA-256 of hooks that are considered to be safe
65+
to run during a clone operation.
66+
+
67+
Multiple values can be added via `git config --global --add`.

ci/install-dependencies.sh

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -34,8 +34,6 @@ macos-*)
3434
export HOMEBREW_NO_AUTO_UPDATE=1 HOMEBREW_NO_INSTALL_CLEANUP=1
3535
# Uncomment this if you want to run perf tests:
3636
# brew install gnu-time
37-
test -z "$BREW_INSTALL_PACKAGES" ||
38-
brew install $BREW_INSTALL_PACKAGES
3937
brew link --force gettext
4038
mkdir -p $HOME/bin
4139
(

config.c

Lines changed: 1 addition & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1558,19 +1558,8 @@ static int git_default_core_config(const char *var, const char *value,
15581558
if (!strcmp(var, "core.attributesfile"))
15591559
return git_config_pathname(&git_attributes_file, var, value);
15601560

1561-
if (!strcmp(var, "core.hookspath")) {
1562-
if (ctx->kvi && ctx->kvi->scope == CONFIG_SCOPE_LOCAL &&
1563-
git_env_bool("GIT_CLONE_PROTECTION_ACTIVE", 0))
1564-
die(_("active `core.hooksPath` found in the local "
1565-
"repository config:\n\t%s\nFor security "
1566-
"reasons, this is disallowed by default.\nIf "
1567-
"this is intentional and the hook should "
1568-
"actually be run, please\nrun the command "
1569-
"again with "
1570-
"`GIT_CLONE_PROTECTION_ACTIVE=false`"),
1571-
value);
1561+
if (!strcmp(var, "core.hookspath"))
15721562
return git_config_pathname(&git_hooks_path, var, value);
1573-
}
15741563

15751564
if (!strcmp(var, "core.bare")) {
15761565
is_bare_repository_cfg = git_config_bool(var, value);

copy.c

Lines changed: 0 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -70,61 +70,3 @@ int copy_file_with_time(const char *dst, const char *src, int mode)
7070
return copy_times(dst, src);
7171
return status;
7272
}
73-
74-
static int do_symlinks_match(const char *path1, const char *path2)
75-
{
76-
struct strbuf buf1 = STRBUF_INIT, buf2 = STRBUF_INIT;
77-
int ret = 0;
78-
79-
if (!strbuf_readlink(&buf1, path1, 0) &&
80-
!strbuf_readlink(&buf2, path2, 0))
81-
ret = !strcmp(buf1.buf, buf2.buf);
82-
83-
strbuf_release(&buf1);
84-
strbuf_release(&buf2);
85-
return ret;
86-
}
87-
88-
int do_files_match(const char *path1, const char *path2)
89-
{
90-
struct stat st1, st2;
91-
int fd1 = -1, fd2 = -1, ret = 1;
92-
char buf1[8192], buf2[8192];
93-
94-
if ((fd1 = open_nofollow(path1, O_RDONLY)) < 0 ||
95-
fstat(fd1, &st1) || !S_ISREG(st1.st_mode)) {
96-
if (fd1 < 0 && errno == ELOOP)
97-
/* maybe this is a symbolic link? */
98-
return do_symlinks_match(path1, path2);
99-
ret = 0;
100-
} else if ((fd2 = open_nofollow(path2, O_RDONLY)) < 0 ||
101-
fstat(fd2, &st2) || !S_ISREG(st2.st_mode)) {
102-
ret = 0;
103-
}
104-
105-
if (ret)
106-
/* to match, neither must be executable, or both */
107-
ret = !(st1.st_mode & 0111) == !(st2.st_mode & 0111);
108-
109-
if (ret)
110-
ret = st1.st_size == st2.st_size;
111-
112-
while (ret) {
113-
ssize_t len1 = read_in_full(fd1, buf1, sizeof(buf1));
114-
ssize_t len2 = read_in_full(fd2, buf2, sizeof(buf2));
115-
116-
if (len1 < 0 || len2 < 0 || len1 != len2)
117-
ret = 0; /* read error or different file size */
118-
else if (!len1) /* len2 is also 0; hit EOF on both */
119-
break; /* ret is still true */
120-
else
121-
ret = !memcmp(buf1, buf2, len1);
122-
}
123-
124-
if (fd1 >= 0)
125-
close(fd1);
126-
if (fd2 >= 0)
127-
close(fd2);
128-
129-
return ret;
130-
}

copy.h

Lines changed: 0 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -7,18 +7,4 @@ int copy_fd(int ifd, int ofd);
77
int copy_file(const char *dst, const char *src, int mode);
88
int copy_file_with_time(const char *dst, const char *src, int mode);
99

10-
/*
11-
* Compare the file mode and contents of two given files.
12-
*
13-
* If both files are actually symbolic links, the function returns 1 if the link
14-
* targets are identical or 0 if they are not.
15-
*
16-
* If any of the two files cannot be accessed or in case of read failures, this
17-
* function returns 0.
18-
*
19-
* If the file modes and contents are identical, the function returns 1,
20-
* otherwise it returns 0.
21-
*/
22-
int do_files_match(const char *path1, const char *path2);
23-
2410
#endif /* COPY_H */

hook.c

Lines changed: 81 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -9,37 +9,96 @@
99
#include "strbuf.h"
1010
#include "environment.h"
1111
#include "setup.h"
12-
#include "copy.h"
12+
#include "hex.h"
13+
#include "strmap.h"
1314

14-
static int identical_to_template_hook(const char *name, const char *path)
15+
static struct strset safe_hook_sha256s = STRSET_INIT;
16+
static int safe_hook_sha256s_initialized;
17+
18+
static int get_sha256_of_file_contents(const char *path, char *sha256)
1519
{
16-
const char *env = getenv("GIT_CLONE_TEMPLATE_DIR");
17-
const char *template_dir = get_template_dir(env && *env ? env : NULL);
18-
struct strbuf template_path = STRBUF_INIT;
19-
int found_template_hook, ret;
20+
struct strbuf sb = STRBUF_INIT;
21+
int fd;
22+
ssize_t res;
2023

21-
strbuf_addf(&template_path, "%s/hooks/%s", template_dir, name);
22-
found_template_hook = access(template_path.buf, X_OK) >= 0;
23-
#ifdef STRIP_EXTENSION
24-
if (!found_template_hook) {
25-
strbuf_addstr(&template_path, STRIP_EXTENSION);
26-
found_template_hook = access(template_path.buf, X_OK) >= 0;
24+
git_hash_ctx ctx;
25+
const struct git_hash_algo *algo = &hash_algos[GIT_HASH_SHA256];
26+
unsigned char hash[GIT_MAX_RAWSZ];
27+
28+
if ((fd = open(path, O_RDONLY)) < 0)
29+
return -1;
30+
res = strbuf_read(&sb, fd, 400);
31+
close(fd);
32+
if (res < 0)
33+
return -1;
34+
35+
algo->init_fn(&ctx);
36+
algo->update_fn(&ctx, sb.buf, sb.len);
37+
strbuf_release(&sb);
38+
algo->final_fn(hash, &ctx);
39+
40+
hash_to_hex_algop_r(sha256, hash, algo);
41+
42+
return 0;
43+
}
44+
45+
void add_safe_hook(const char *path)
46+
{
47+
char sha256[GIT_SHA256_HEXSZ + 1] = { '\0' };
48+
49+
if (!get_sha256_of_file_contents(path, sha256)) {
50+
char *p;
51+
52+
strset_add(&safe_hook_sha256s, sha256);
53+
54+
/* support multi-process operations e.g. recursive clones */
55+
p = xstrfmt("safe.hook.sha256=%s", sha256);
56+
git_config_push_parameter(p);
57+
free(p);
2758
}
28-
#endif
29-
if (!found_template_hook)
59+
}
60+
61+
static int safe_hook_cb(const char *key, const char *value,
62+
const struct config_context *ctx UNUSED, void *d)
63+
{
64+
struct strset *set = d;
65+
66+
if (value && !strcmp(key, "safe.hook.sha256"))
67+
strset_add(set, value);
68+
69+
return 0;
70+
}
71+
72+
static int is_hook_safe_during_clone(const char *name, const char *path, char *sha256)
73+
{
74+
if (get_sha256_of_file_contents(path, sha256) < 0)
3075
return 0;
3176

32-
ret = do_files_match(template_path.buf, path);
77+
if (!safe_hook_sha256s_initialized) {
78+
safe_hook_sha256s_initialized = 1;
3379

34-
strbuf_release(&template_path);
35-
return ret;
80+
/* Hard-code known-safe values for Git LFS v3.4.0..v3.5.1 */
81+
/* pre-push */
82+
strset_add(&safe_hook_sha256s, "df5417b2daa3aa144c19681d1e997df7ebfe144fb7e3e05138bd80ae998008e4");
83+
/* post-checkout */
84+
strset_add(&safe_hook_sha256s, "791471b4ff472aab844a4fceaa48bbb0a12193616f971e8e940625498b4938a6");
85+
/* post-commit */
86+
strset_add(&safe_hook_sha256s, "21e961572bb3f43a5f2fbafc1cc764d86046cc2e5f0bbecebfe9684a0b73b664");
87+
/* post-merge */
88+
strset_add(&safe_hook_sha256s, "75da0da66a803b4b030ad50801ba57062c6196105eb1d2251590d100edb9390b");
89+
90+
git_protected_config(safe_hook_cb, &safe_hook_sha256s);
91+
}
92+
93+
return strset_contains(&safe_hook_sha256s, sha256);
3694
}
3795

3896
const char *find_hook(const char *name)
3997
{
4098
static struct strbuf path = STRBUF_INIT;
4199

42100
int found_hook;
101+
char sha256[GIT_SHA256_HEXSZ + 1] = { '\0' };
43102

44103
strbuf_reset(&path);
45104
strbuf_git_path(&path, "hooks/%s", name);
@@ -71,13 +130,13 @@ const char *find_hook(const char *name)
71130
return NULL;
72131
}
73132
if (!git_hooks_path && git_env_bool("GIT_CLONE_PROTECTION_ACTIVE", 0) &&
74-
!identical_to_template_hook(name, path.buf))
133+
!is_hook_safe_during_clone(name, path.buf, sha256))
75134
die(_("active `%s` hook found during `git clone`:\n\t%s\n"
76135
"For security reasons, this is disallowed by default.\n"
77-
"If this is intentional and the hook should actually "
78-
"be run, please\nrun the command again with "
79-
"`GIT_CLONE_PROTECTION_ACTIVE=false`"),
80-
name, path.buf);
136+
"If this is intentional and the hook is safe to run, "
137+
"please run the following command and try again:\n\n"
138+
" git config --global --add safe.hook.sha256 %s"),
139+
name, path.buf, sha256);
81140
return path.buf;
82141
}
83142

hook.h

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,4 +87,14 @@ int run_hooks(const char *hook_name);
8787
* hook. This function behaves like the old run_hook_le() API.
8888
*/
8989
int run_hooks_l(const char *hook_name, ...);
90+
91+
/**
92+
* Mark the contents of the provided path as safe to run during a clone
93+
* operation.
94+
*
95+
* This function is mainly used when copying templates to mark the
96+
* just-copied hooks as benign.
97+
*/
98+
void add_safe_hook(const char *path);
99+
90100
#endif

setup.c

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@
1818
#include "trace2.h"
1919
#include "worktree.h"
2020
#include "exec-cmd.h"
21+
#include "run-command.h"
22+
#include "hook.h"
2123

2224
static int inside_git_dir = -1;
2325
static int inside_work_tree = -1;
@@ -1793,6 +1795,7 @@ static void copy_templates_1(struct strbuf *path, struct strbuf *template_path,
17931795
size_t path_baselen = path->len;
17941796
size_t template_baselen = template_path->len;
17951797
struct dirent *de;
1798+
int is_hooks_dir = ends_with(template_path->buf, "/hooks/");
17961799

17971800
/* Note: if ".git/hooks" file exists in the repository being
17981801
* re-initialized, /etc/core-git/templates/hooks/update would
@@ -1845,6 +1848,10 @@ static void copy_templates_1(struct strbuf *path, struct strbuf *template_path,
18451848
strbuf_release(&lnk);
18461849
}
18471850
else if (S_ISREG(st_template.st_mode)) {
1851+
if (is_hooks_dir &&
1852+
is_executable(template_path->buf))
1853+
add_safe_hook(template_path->buf);
1854+
18481855
if (copy_file(path->buf, template_path->buf, st_template.st_mode))
18491856
die_errno(_("cannot copy '%s' to '%s'"),
18501857
template_path->buf, path->buf);

t/helper/test-path-utils.c

Lines changed: 0 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -501,16 +501,6 @@ int cmd__path_utils(int argc, const char **argv)
501501
return !!res;
502502
}
503503

504-
if (argc == 4 && !strcmp(argv[1], "do_files_match")) {
505-
int ret = do_files_match(argv[2], argv[3]);
506-
507-
if (ret)
508-
printf("equal\n");
509-
else
510-
printf("different\n");
511-
return !ret;
512-
}
513-
514504
fprintf(stderr, "%s: unknown function name: %s\n", argv[0],
515505
argv[1] ? argv[1] : "(there was none)");
516506
return 1;

0 commit comments

Comments
 (0)