mirror of
https://github.com/git/git
synced 2024-11-05 18:59:29 +00:00
Merge branch 'defense-in-depth'
This topic branch adds a couple of measures designed to make it much harder to exploit any bugs in Git's recursive clone machinery that might be found in the future. Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
This commit is contained in:
commit
2b3d38a6b1
21 changed files with 538 additions and 30 deletions
|
@ -157,6 +157,18 @@
|
|||
`nullSha1`::
|
||||
(WARN) Tree contains entries pointing to a null sha1.
|
||||
|
||||
`symlinkPointsToGitDir`::
|
||||
(WARN) Symbolic link points inside a gitdir.
|
||||
|
||||
`symlinkTargetBlob`::
|
||||
(ERROR) A non-blob found instead of a symbolic link's target.
|
||||
|
||||
`symlinkTargetLength`::
|
||||
(WARN) Symbolic link target longer than maximum path length.
|
||||
|
||||
`symlinkTargetMissing`::
|
||||
(ERROR) Unable to read symbolic link target's blob.
|
||||
|
||||
`treeNotSorted`::
|
||||
(ERROR) A tree is not properly sorted.
|
||||
|
||||
|
|
|
@ -908,6 +908,8 @@ int cmd_clone(int argc, const char **argv, const char *prefix)
|
|||
int err = 0, complete_refs_before_fetch = 1;
|
||||
int submodule_progress;
|
||||
int filter_submodules = 0;
|
||||
const char *template_dir;
|
||||
char *template_dir_dup = NULL;
|
||||
|
||||
struct transport_ls_refs_options transport_ls_refs_options =
|
||||
TRANSPORT_LS_REFS_OPTIONS_INIT;
|
||||
|
@ -927,6 +929,13 @@ int cmd_clone(int argc, const char **argv, const char *prefix)
|
|||
usage_msg_opt(_("You must specify a repository to clone."),
|
||||
builtin_clone_usage, builtin_clone_options);
|
||||
|
||||
xsetenv("GIT_CLONE_PROTECTION_ACTIVE", "true", 0 /* allow user override */);
|
||||
template_dir = get_template_dir(option_template);
|
||||
if (*template_dir && !is_absolute_path(template_dir))
|
||||
template_dir = template_dir_dup =
|
||||
absolute_pathdup(template_dir);
|
||||
xsetenv("GIT_CLONE_TEMPLATE_DIR", template_dir, 1);
|
||||
|
||||
if (option_depth || option_since || option_not.nr)
|
||||
deepen = 1;
|
||||
if (option_single_branch == -1)
|
||||
|
@ -1074,7 +1083,7 @@ int cmd_clone(int argc, const char **argv, const char *prefix)
|
|||
}
|
||||
}
|
||||
|
||||
init_db(git_dir, real_git_dir, option_template, GIT_HASH_UNKNOWN, NULL,
|
||||
init_db(git_dir, real_git_dir, template_dir, GIT_HASH_UNKNOWN, NULL,
|
||||
INIT_DB_QUIET);
|
||||
|
||||
if (real_git_dir) {
|
||||
|
@ -1392,6 +1401,7 @@ int cmd_clone(int argc, const char **argv, const char *prefix)
|
|||
free(unborn_head);
|
||||
free(dir);
|
||||
free(path);
|
||||
free(template_dir_dup);
|
||||
UNLEAK(repo);
|
||||
junk_mode = JUNK_LEAVE_ALL;
|
||||
|
||||
|
|
|
@ -11,10 +11,6 @@
|
|||
#include "parse-options.h"
|
||||
#include "worktree.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
|
||||
|
@ -93,8 +89,9 @@ static void copy_templates_1(struct strbuf *path, struct strbuf *template_path,
|
|||
}
|
||||
}
|
||||
|
||||
static void copy_templates(const char *template_dir, const char *init_template_dir)
|
||||
static void copy_templates(const char *option_template)
|
||||
{
|
||||
const char *template_dir = get_template_dir(option_template);
|
||||
struct strbuf path = STRBUF_INIT;
|
||||
struct strbuf template_path = STRBUF_INIT;
|
||||
size_t template_len;
|
||||
|
@ -103,16 +100,8 @@ static void copy_templates(const char *template_dir, const char *init_template_d
|
|||
DIR *dir;
|
||||
char *to_free = NULL;
|
||||
|
||||
if (!template_dir)
|
||||
template_dir = getenv(TEMPLATE_DIR_ENVIRONMENT);
|
||||
if (!template_dir)
|
||||
template_dir = init_template_dir;
|
||||
if (!template_dir)
|
||||
template_dir = to_free = system_path(DEFAULT_GIT_TEMPLATE_DIR);
|
||||
if (!template_dir[0]) {
|
||||
free(to_free);
|
||||
if (!template_dir || !*template_dir)
|
||||
return;
|
||||
}
|
||||
|
||||
strbuf_addstr(&template_path, template_dir);
|
||||
strbuf_complete(&template_path, '/');
|
||||
|
@ -200,7 +189,6 @@ static int create_default_files(const char *template_path,
|
|||
int reinit;
|
||||
int filemode;
|
||||
struct strbuf err = STRBUF_INIT;
|
||||
const char *init_template_dir = NULL;
|
||||
const char *work_tree = get_git_work_tree();
|
||||
|
||||
/*
|
||||
|
@ -212,9 +200,7 @@ static int create_default_files(const char *template_path,
|
|||
* values (since we've just potentially changed what's available on
|
||||
* disk).
|
||||
*/
|
||||
git_config_get_pathname("init.templatedir", &init_template_dir);
|
||||
copy_templates(template_path, init_template_dir);
|
||||
free((char *)init_template_dir);
|
||||
copy_templates(template_path);
|
||||
git_config_clear();
|
||||
reset_shared_repository();
|
||||
git_config(git_default_config, NULL);
|
||||
|
|
15
cache.h
15
cache.h
|
@ -656,6 +656,7 @@ int path_inside_repo(const char *prefix, const char *path);
|
|||
#define INIT_DB_QUIET 0x0001
|
||||
#define INIT_DB_EXIST_OK 0x0002
|
||||
|
||||
const char *get_template_dir(const char *option_template);
|
||||
int init_db(const char *git_dir, const char *real_git_dir,
|
||||
const char *template_dir, int hash_algo,
|
||||
const char *initial_branch, unsigned int flags);
|
||||
|
@ -1784,6 +1785,20 @@ 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);
|
||||
|
||||
void write_or_die(int fd, const void *buf, size_t count);
|
||||
void fsync_or_die(int fd, const char *);
|
||||
int fsync_component(enum fsync_component component, int fd);
|
||||
|
|
13
config.c
13
config.c
|
@ -1525,8 +1525,19 @@ static int git_default_core_config(const char *var, const char *value, void *cb)
|
|||
if (!strcmp(var, "core.attributesfile"))
|
||||
return git_config_pathname(&git_attributes_file, var, value);
|
||||
|
||||
if (!strcmp(var, "core.hookspath"))
|
||||
if (!strcmp(var, "core.hookspath")) {
|
||||
if (current_config_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);
|
||||
return git_config_pathname(&git_hooks_path, var, value);
|
||||
}
|
||||
|
||||
if (!strcmp(var, "core.bare")) {
|
||||
is_bare_repository_cfg = git_config_bool(var, value);
|
||||
|
|
58
copy.c
58
copy.c
|
@ -65,3 +65,61 @@ 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;
|
||||
}
|
||||
|
|
12
dir.c
12
dir.c
|
@ -88,6 +88,18 @@ int fspathncmp(const char *a, const char *b, size_t count)
|
|||
return ignore_case ? strncasecmp(a, b, count) : strncmp(a, b, count);
|
||||
}
|
||||
|
||||
int paths_collide(const char *a, const char *b)
|
||||
{
|
||||
size_t len_a = strlen(a), len_b = strlen(b);
|
||||
|
||||
if (len_a == len_b)
|
||||
return fspatheq(a, b);
|
||||
|
||||
if (len_a < len_b)
|
||||
return is_dir_sep(b[len_a]) && !fspathncmp(a, b, len_a);
|
||||
return is_dir_sep(a[len_b]) && !fspathncmp(a, b, len_b);
|
||||
}
|
||||
|
||||
unsigned int fspathhash(const char *str)
|
||||
{
|
||||
return ignore_case ? strihash(str) : strhash(str);
|
||||
|
|
7
dir.h
7
dir.h
|
@ -519,6 +519,13 @@ int fspatheq(const char *a, const char *b);
|
|||
int fspathncmp(const char *a, const char *b, size_t count);
|
||||
unsigned int fspathhash(const char *str);
|
||||
|
||||
/*
|
||||
* Reports whether paths collide. This may be because the paths differ only in
|
||||
* case on a case-sensitive filesystem, or that one path refers to a symlink
|
||||
* that collides with one of the parent directories of the other.
|
||||
*/
|
||||
int paths_collide(const char *a, const char *b);
|
||||
|
||||
/*
|
||||
* The prefix part of pattern must not contains wildcards.
|
||||
*/
|
||||
|
|
16
entry.c
16
entry.c
|
@ -454,7 +454,7 @@ static void mark_colliding_entries(const struct checkout *state,
|
|||
continue;
|
||||
|
||||
if ((trust_ino && !match_stat_data(&dup->ce_stat_data, st)) ||
|
||||
(!trust_ino && !fspathcmp(ce->name, dup->name))) {
|
||||
paths_collide(ce->name, dup->name)) {
|
||||
dup->ce_flags |= CE_MATCHED;
|
||||
break;
|
||||
}
|
||||
|
@ -541,6 +541,20 @@ int checkout_entry_ca(struct cache_entry *ce, struct conv_attrs *ca,
|
|||
/* If it is a gitlink, leave it alone! */
|
||||
if (S_ISGITLINK(ce->ce_mode))
|
||||
return 0;
|
||||
/*
|
||||
* We must avoid replacing submodules' leading
|
||||
* directories with symbolic links, lest recursive
|
||||
* clones can write into arbitrary locations.
|
||||
*
|
||||
* Technically, this logic is not limited
|
||||
* to recursive clones, or for that matter to
|
||||
* submodules' paths colliding with symbolic links'
|
||||
* paths. Yet it strikes a balance in favor of
|
||||
* simplicity, and if paths are colliding, we might
|
||||
* just as well keep the directories during a clone.
|
||||
*/
|
||||
if (state->clone && S_ISLNK(ce->ce_mode))
|
||||
return 0;
|
||||
remove_subtree(&path);
|
||||
} else if (unlink(path.buf))
|
||||
return error_errno("unable to unlink old '%s'", path.buf);
|
||||
|
|
56
fsck.c
56
fsck.c
|
@ -636,6 +636,8 @@ static int fsck_tree(const struct object_id *tree_oid,
|
|||
retval += report(options, tree_oid, OBJ_TREE,
|
||||
FSCK_MSG_MAILMAP_SYMLINK,
|
||||
".mailmap is a symlink");
|
||||
oidset_insert(&options->symlink_targets_found,
|
||||
entry_oid);
|
||||
}
|
||||
|
||||
if ((backslash = strchr(name, '\\'))) {
|
||||
|
@ -1228,6 +1230,56 @@ static int fsck_blob(const struct object_id *oid, const char *buf,
|
|||
}
|
||||
}
|
||||
|
||||
if (oidset_contains(&options->symlink_targets_found, oid)) {
|
||||
const char *ptr = buf;
|
||||
const struct object_id *reported = NULL;
|
||||
|
||||
oidset_insert(&options->symlink_targets_done, oid);
|
||||
|
||||
if (!buf || size > PATH_MAX) {
|
||||
/*
|
||||
* A missing buffer here is a sign that the caller found the
|
||||
* blob too gigantic to load into memory. Let's just consider
|
||||
* that an error.
|
||||
*/
|
||||
return report(options, oid, OBJ_BLOB,
|
||||
FSCK_MSG_SYMLINK_TARGET_LENGTH,
|
||||
"symlink target too long");
|
||||
}
|
||||
|
||||
while (!reported && ptr) {
|
||||
const char *p = ptr;
|
||||
char c, *slash = strchrnul(ptr, '/');
|
||||
char *backslash = memchr(ptr, '\\', slash - ptr);
|
||||
|
||||
c = *slash;
|
||||
*slash = '\0';
|
||||
|
||||
while (!reported && backslash) {
|
||||
*backslash = '\0';
|
||||
if (is_ntfs_dotgit(p))
|
||||
ret |= report(options, reported = oid, OBJ_BLOB,
|
||||
FSCK_MSG_SYMLINK_POINTS_TO_GIT_DIR,
|
||||
"symlink target points to git dir");
|
||||
*backslash = '\\';
|
||||
p = backslash + 1;
|
||||
backslash = memchr(p, '\\', slash - p);
|
||||
}
|
||||
if (!reported && is_ntfs_dotgit(p))
|
||||
ret |= report(options, reported = oid, OBJ_BLOB,
|
||||
FSCK_MSG_SYMLINK_POINTS_TO_GIT_DIR,
|
||||
"symlink target points to git dir");
|
||||
|
||||
if (!reported && is_hfs_dotgit(ptr))
|
||||
ret |= report(options, reported = oid, OBJ_BLOB,
|
||||
FSCK_MSG_SYMLINK_POINTS_TO_GIT_DIR,
|
||||
"symlink target points to git dir");
|
||||
|
||||
*slash = c;
|
||||
ptr = c ? slash + 1 : NULL;
|
||||
}
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
@ -1319,6 +1371,10 @@ int fsck_finish(struct fsck_options *options)
|
|||
FSCK_MSG_GITATTRIBUTES_MISSING, FSCK_MSG_GITATTRIBUTES_BLOB,
|
||||
options, ".gitattributes");
|
||||
|
||||
ret |= fsck_blobs(&options->symlink_targets_found, &options->symlink_targets_done,
|
||||
FSCK_MSG_SYMLINK_TARGET_MISSING, FSCK_MSG_SYMLINK_TARGET_BLOB,
|
||||
options, "<symlink-target>");
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
|
12
fsck.h
12
fsck.h
|
@ -63,6 +63,8 @@ enum fsck_msg_type {
|
|||
FUNC(GITATTRIBUTES_LARGE, ERROR) \
|
||||
FUNC(GITATTRIBUTES_LINE_LENGTH, ERROR) \
|
||||
FUNC(GITATTRIBUTES_BLOB, ERROR) \
|
||||
FUNC(SYMLINK_TARGET_MISSING, ERROR) \
|
||||
FUNC(SYMLINK_TARGET_BLOB, ERROR) \
|
||||
/* warnings */ \
|
||||
FUNC(EMPTY_NAME, WARN) \
|
||||
FUNC(FULL_PATHNAME, WARN) \
|
||||
|
@ -72,6 +74,8 @@ enum fsck_msg_type {
|
|||
FUNC(NULL_SHA1, WARN) \
|
||||
FUNC(ZERO_PADDED_FILEMODE, WARN) \
|
||||
FUNC(NUL_IN_COMMIT, WARN) \
|
||||
FUNC(SYMLINK_TARGET_LENGTH, WARN) \
|
||||
FUNC(SYMLINK_POINTS_TO_GIT_DIR, WARN) \
|
||||
/* infos (reported as warnings, but ignored by default) */ \
|
||||
FUNC(BAD_FILEMODE, INFO) \
|
||||
FUNC(GITMODULES_PARSE, INFO) \
|
||||
|
@ -139,6 +143,8 @@ struct fsck_options {
|
|||
struct oidset gitmodules_done;
|
||||
struct oidset gitattributes_found;
|
||||
struct oidset gitattributes_done;
|
||||
struct oidset symlink_targets_found;
|
||||
struct oidset symlink_targets_done;
|
||||
kh_oid_map_t *object_names;
|
||||
};
|
||||
|
||||
|
@ -148,6 +154,8 @@ struct fsck_options {
|
|||
.gitmodules_done = OIDSET_INIT, \
|
||||
.gitattributes_found = OIDSET_INIT, \
|
||||
.gitattributes_done = OIDSET_INIT, \
|
||||
.symlink_targets_found = OIDSET_INIT, \
|
||||
.symlink_targets_done = OIDSET_INIT, \
|
||||
.error_func = fsck_error_function \
|
||||
}
|
||||
#define FSCK_OPTIONS_STRICT { \
|
||||
|
@ -156,6 +164,8 @@ struct fsck_options {
|
|||
.gitmodules_done = OIDSET_INIT, \
|
||||
.gitattributes_found = OIDSET_INIT, \
|
||||
.gitattributes_done = OIDSET_INIT, \
|
||||
.symlink_targets_found = OIDSET_INIT, \
|
||||
.symlink_targets_done = OIDSET_INIT, \
|
||||
.error_func = fsck_error_function, \
|
||||
}
|
||||
#define FSCK_OPTIONS_MISSING_GITMODULES { \
|
||||
|
@ -164,6 +174,8 @@ struct fsck_options {
|
|||
.gitmodules_done = OIDSET_INIT, \
|
||||
.gitattributes_found = OIDSET_INIT, \
|
||||
.gitattributes_done = OIDSET_INIT, \
|
||||
.symlink_targets_found = OIDSET_INIT, \
|
||||
.symlink_targets_done = OIDSET_INIT, \
|
||||
.error_func = fsck_error_cb_print_missing_gitmodules, \
|
||||
}
|
||||
|
||||
|
|
50
hook.c
50
hook.c
|
@ -3,24 +3,52 @@
|
|||
#include "run-command.h"
|
||||
#include "config.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)
|
||||
{
|
||||
static struct strbuf path = STRBUF_INIT;
|
||||
|
||||
int found_hook;
|
||||
|
||||
strbuf_reset(&path);
|
||||
strbuf_git_path(&path, "hooks/%s", name);
|
||||
if (access(path.buf, X_OK) < 0) {
|
||||
found_hook = access(path.buf, X_OK) >= 0;
|
||||
#ifdef STRIP_EXTENSION
|
||||
if (!found_hook) {
|
||||
int err = errno;
|
||||
|
||||
#ifdef STRIP_EXTENSION
|
||||
strbuf_addstr(&path, STRIP_EXTENSION);
|
||||
if (access(path.buf, X_OK) >= 0)
|
||||
return path.buf;
|
||||
if (errno == EACCES)
|
||||
err = errno;
|
||||
found_hook = access(path.buf, X_OK) >= 0;
|
||||
if (!found_hook)
|
||||
errno = err;
|
||||
}
|
||||
#endif
|
||||
|
||||
if (err == EACCES && advice_enabled(ADVICE_IGNORED_HOOK)) {
|
||||
if (!found_hook) {
|
||||
if (errno == EACCES && advice_enabled(ADVICE_IGNORED_HOOK)) {
|
||||
static struct string_list advise_given = STRING_LIST_INIT_DUP;
|
||||
|
||||
if (!string_list_lookup(&advise_given, name)) {
|
||||
|
@ -34,6 +62,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))
|
||||
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);
|
||||
return path.buf;
|
||||
}
|
||||
|
||||
|
|
55
setup.c
55
setup.c
|
@ -6,6 +6,7 @@
|
|||
#include "chdir-notify.h"
|
||||
#include "promisor-remote.h"
|
||||
#include "quote.h"
|
||||
#include "exec-cmd.h"
|
||||
|
||||
static int inside_git_dir = -1;
|
||||
static int inside_work_tree = -1;
|
||||
|
@ -1720,3 +1721,57 @@ int daemonize(void)
|
|||
return 0;
|
||||
#endif
|
||||
}
|
||||
|
||||
#ifndef DEFAULT_GIT_TEMPLATE_DIR
|
||||
#define DEFAULT_GIT_TEMPLATE_DIR "/usr/share/git-core/templates"
|
||||
#endif
|
||||
|
||||
struct template_dir_cb_data {
|
||||
char *path;
|
||||
int initialized;
|
||||
};
|
||||
|
||||
static int template_dir_cb(const char *key, const char *value, void *d)
|
||||
{
|
||||
struct template_dir_cb_data *data = d;
|
||||
|
||||
if (strcmp(key, "init.templatedir"))
|
||||
return 0;
|
||||
|
||||
if (!value) {
|
||||
data->path = NULL;
|
||||
} else {
|
||||
char *path = NULL;
|
||||
|
||||
FREE_AND_NULL(data->path);
|
||||
if (!git_config_pathname((const char **)&path, key, value))
|
||||
data->path = path ? path : xstrdup(value);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
const char *get_template_dir(const char *option_template)
|
||||
{
|
||||
const char *template_dir = option_template;
|
||||
|
||||
if (!template_dir)
|
||||
template_dir = getenv(TEMPLATE_DIR_ENVIRONMENT);
|
||||
if (!template_dir) {
|
||||
static struct template_dir_cb_data data;
|
||||
|
||||
if (!data.initialized) {
|
||||
git_protected_config(template_dir_cb, &data);
|
||||
data.initialized = 1;
|
||||
}
|
||||
template_dir = data.path;
|
||||
}
|
||||
if (!template_dir) {
|
||||
static char *dir;
|
||||
|
||||
if (!dir)
|
||||
dir = system_path(DEFAULT_GIT_TEMPLATE_DIR);
|
||||
template_dir = dir;
|
||||
}
|
||||
return template_dir;
|
||||
}
|
||||
|
|
|
@ -495,6 +495,16 @@ 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;
|
||||
|
|
|
@ -560,4 +560,45 @@ 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
|
||||
|
|
|
@ -1023,4 +1023,41 @@ test_expect_success 'fsck error on gitattributes with excessive size' '
|
|||
test_cmp expected 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
|
||||
|
|
|
@ -177,4 +177,19 @@ test_expect_success 'git hook run a hook with a bad shebang' '
|
|||
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 &&
|
||||
grep "Hook ran" err &&
|
||||
test_must_fail env GIT_CLONE_PROTECTION_ACTIVE=true \
|
||||
git hook run test-hook 2>err &&
|
||||
grep "active .core.hooksPath" err &&
|
||||
! grep "Hook ran" err
|
||||
'
|
||||
|
||||
test_done
|
||||
|
|
|
@ -1240,6 +1240,30 @@ EOF
|
|||
test_cmp fatal-expect fatal-actual
|
||||
'
|
||||
|
||||
test_expect_success SYMLINKS 'clone does not get confused by a D/F conflict' '
|
||||
git init df-conflict &&
|
||||
(
|
||||
cd df-conflict &&
|
||||
ln -s .git a &&
|
||||
git add a &&
|
||||
test_tick &&
|
||||
git commit -m symlink &&
|
||||
test_commit a- &&
|
||||
rm a &&
|
||||
mkdir -p a/hooks &&
|
||||
write_script a/hooks/post-checkout <<-EOF &&
|
||||
echo WHOOPSIE >&2
|
||||
echo whoopsie >"$TRASH_DIRECTORY"/whoops
|
||||
EOF
|
||||
git add a/hooks/post-checkout &&
|
||||
test_tick &&
|
||||
git commit -m post-checkout
|
||||
) &&
|
||||
git clone df-conflict clone 2>err &&
|
||||
! grep WHOOPS err &&
|
||||
test_path_is_missing whoops
|
||||
'
|
||||
|
||||
. "$TEST_DIRECTORY"/lib-httpd.sh
|
||||
start_httpd
|
||||
|
||||
|
|
|
@ -633,6 +633,21 @@ test_expect_success CASE_INSENSITIVE_FS 'colliding file detection' '
|
|||
test_i18ngrep "the following paths have collided" icasefs/warning
|
||||
'
|
||||
|
||||
test_expect_success CASE_INSENSITIVE_FS,SYMLINKS \
|
||||
'colliding symlink/directory keeps directory' '
|
||||
git init icasefs-colliding-symlink &&
|
||||
(
|
||||
cd icasefs-colliding-symlink &&
|
||||
a=$(printf a | git hash-object -w --stdin) &&
|
||||
printf "100644 %s 0\tA/dir/b\n120000 %s 0\ta\n" $a $a >idx &&
|
||||
git update-index --index-info <idx &&
|
||||
test_tick &&
|
||||
git commit -m initial
|
||||
) &&
|
||||
git clone icasefs-colliding-symlink icasefs-colliding-symlink-clone &&
|
||||
test_file_not_empty icasefs-colliding-symlink-clone/A/dir/b
|
||||
'
|
||||
|
||||
test_expect_success 'clone with GIT_DEFAULT_HASH' '
|
||||
(
|
||||
sane_unset GIT_DEFAULT_HASH &&
|
||||
|
@ -756,6 +771,57 @@ test_expect_success 'batch missing blob request does not inadvertently try to fe
|
|||
git clone --filter=blob:limit=0 "file://$(pwd)/server" client
|
||||
'
|
||||
|
||||
test_expect_success 'clone with init.templatedir runs hooks' '
|
||||
git init tmpl/hooks &&
|
||||
write_script tmpl/hooks/post-checkout <<-EOF &&
|
||||
echo HOOK-RUN >&2
|
||||
echo I was here >hook.run
|
||||
EOF
|
||||
git -C tmpl/hooks add . &&
|
||||
test_tick &&
|
||||
git -C tmpl/hooks commit -m post-checkout &&
|
||||
|
||||
test_when_finished "git config --global --unset init.templateDir || :" &&
|
||||
test_when_finished "git config --unset init.templateDir || :" &&
|
||||
(
|
||||
sane_unset GIT_TEMPLATE_DIR &&
|
||||
NO_SET_GIT_TEMPLATE_DIR=t &&
|
||||
export NO_SET_GIT_TEMPLATE_DIR &&
|
||||
|
||||
git -c core.hooksPath="$(pwd)/tmpl/hooks" \
|
||||
clone tmpl/hooks hook-run-hookspath 2>err &&
|
||||
! grep "active .* hook found" err &&
|
||||
test_path_is_file hook-run-hookspath/hook.run &&
|
||||
|
||||
git -c init.templateDir="$(pwd)/tmpl" \
|
||||
clone tmpl/hooks hook-run-config 2>err &&
|
||||
! grep "active .* hook found" err &&
|
||||
test_path_is_file hook-run-config/hook.run &&
|
||||
|
||||
git clone --template=tmpl tmpl/hooks hook-run-option 2>err &&
|
||||
! grep "active .* hook found" err &&
|
||||
test_path_is_file hook-run-option/hook.run &&
|
||||
|
||||
git config --global init.templateDir "$(pwd)/tmpl" &&
|
||||
git clone tmpl/hooks hook-run-global-config 2>err &&
|
||||
git config --global --unset init.templateDir &&
|
||||
! grep "active .* hook found" err &&
|
||||
test_path_is_file hook-run-global-config/hook.run &&
|
||||
|
||||
# clone ignores local `init.templateDir`; need to create
|
||||
# a new repository because we deleted `.git/` in the
|
||||
# `setup` test case above
|
||||
git init local-clone &&
|
||||
cd local-clone &&
|
||||
|
||||
git config init.templateDir "$(pwd)/../tmpl" &&
|
||||
git clone ../tmpl/hooks hook-run-local-config 2>err &&
|
||||
git config --unset init.templateDir &&
|
||||
! grep "active .* hook found" err &&
|
||||
test_path_is_missing hook-run-local-config/hook.run
|
||||
)
|
||||
'
|
||||
|
||||
. "$TEST_DIRECTORY"/lib-httpd.sh
|
||||
start_httpd
|
||||
|
||||
|
|
|
@ -1436,4 +1436,35 @@ test_expect_success 'recursive clone respects -q' '
|
|||
test_must_be_empty actual
|
||||
'
|
||||
|
||||
test_expect_success '`submodule init` and `init.templateDir`' '
|
||||
mkdir -p tmpl/hooks &&
|
||||
write_script tmpl/hooks/post-checkout <<-EOF &&
|
||||
echo HOOK-RUN >&2
|
||||
echo I was here >hook.run
|
||||
exit 1
|
||||
EOF
|
||||
|
||||
test_config init.templateDir "$(pwd)/tmpl" &&
|
||||
test_when_finished \
|
||||
"git config --global --unset init.templateDir || true" &&
|
||||
(
|
||||
sane_unset GIT_TEMPLATE_DIR &&
|
||||
NO_SET_GIT_TEMPLATE_DIR=t &&
|
||||
export NO_SET_GIT_TEMPLATE_DIR &&
|
||||
|
||||
git config --global init.templateDir "$(pwd)/tmpl" &&
|
||||
test_must_fail git submodule \
|
||||
add "$submodurl" sub-global 2>err &&
|
||||
git config --global --unset init.templateDir &&
|
||||
grep HOOK-RUN err &&
|
||||
test_path_is_file sub-global/hook.run &&
|
||||
|
||||
git config init.templateDir "$(pwd)/tmpl" &&
|
||||
git submodule add "$submodurl" sub-local 2>err &&
|
||||
git config --unset init.templateDir &&
|
||||
! grep HOOK-RUN err &&
|
||||
test_path_is_missing sub-local/hook.run
|
||||
)
|
||||
'
|
||||
|
||||
test_done
|
||||
|
|
|
@ -1222,8 +1222,8 @@ test_expect_success CASE_INSENSITIVE_FS,SYMLINKS \
|
|||
) &&
|
||||
|
||||
test_path_is_missing "$tell_tale_path" &&
|
||||
test_must_fail git clone --recursive captain hooked 2>err &&
|
||||
grep "directory not empty" err &&
|
||||
git clone --recursive captain hooked 2>err &&
|
||||
! grep HOOK-RUN err &&
|
||||
test_path_is_missing "$tell_tale_path"
|
||||
'
|
||||
|
||||
|
|
Loading…
Reference in a new issue