Merge branch 'jk/symlinked-dotgitx-cleanup'
Various test and documentation updates about .gitsomething paths that are symlinks. * jk/symlinked-dotgitx-cleanup: docs: document symlink restrictions for dot-files fsck: warn about symlinked dotfiles we'll open with O_NOFOLLOW t0060: test ntfs/hfs-obscured dotfiles t7450: test .gitmodules symlink matching against obscured names t7450: test verify_path() handling of gitmodules t7415: rename to expand scope fsck_tree(): wrap some long lines fsck_tree(): fix shadowed variable t7415: remove out-dated comment about translation
This commit is contained in:
commit
416449eaba
@ -1247,6 +1247,12 @@ to:
|
||||
[attr]binary -diff -merge -text
|
||||
------------
|
||||
|
||||
NOTES
|
||||
-----
|
||||
|
||||
Git does not follow symbolic links when accessing a `.gitattributes`
|
||||
file in the working tree. This keeps behavior consistent when the file
|
||||
is accessed from the index or a tree versus from the filesystem.
|
||||
|
||||
EXAMPLES
|
||||
--------
|
||||
|
@ -149,6 +149,10 @@ not tracked by Git remain untracked.
|
||||
To stop tracking a file that is currently tracked, use
|
||||
'git rm --cached'.
|
||||
|
||||
Git does not follow symbolic links when accessing a `.gitignore` file in
|
||||
the working tree. This keeps behavior consistent when the file is
|
||||
accessed from the index or a tree versus from the filesystem.
|
||||
|
||||
EXAMPLES
|
||||
--------
|
||||
|
||||
|
@ -55,6 +55,13 @@ this would also match the 'Commit Name <commit@email.xx>' above:
|
||||
Proper Name <proper@email.xx> CoMmIt NaMe <CoMmIt@EmAiL.xX>
|
||||
--
|
||||
|
||||
NOTES
|
||||
-----
|
||||
|
||||
Git does not follow symbolic links when accessing a `.mailmap` file in
|
||||
the working tree. This keeps behavior consistent when the file is
|
||||
accessed from the index or a tree versus from the filesystem.
|
||||
|
||||
EXAMPLES
|
||||
--------
|
||||
|
||||
|
@ -98,6 +98,14 @@ submodule.<name>.shallow::
|
||||
shallow clone (with a history depth of 1) unless the user explicitly
|
||||
asks for a non-shallow clone.
|
||||
|
||||
NOTES
|
||||
-----
|
||||
|
||||
Git does not allow the `.gitmodules` file within a working tree to be a
|
||||
symbolic link, and will refuse to check out such a tree entry. This
|
||||
keeps behavior consistent when the file is accessed from the index or a
|
||||
tree versus from the filesystem, and helps Git reliably enforce security
|
||||
checks of the file contents.
|
||||
|
||||
EXAMPLES
|
||||
--------
|
||||
|
1
cache.h
1
cache.h
@ -1272,6 +1272,7 @@ int is_ntfs_dotgit(const char *name);
|
||||
int is_ntfs_dotgitmodules(const char *name);
|
||||
int is_ntfs_dotgitignore(const char *name);
|
||||
int is_ntfs_dotgitattributes(const char *name);
|
||||
int is_ntfs_dotmailmap(const char *name);
|
||||
|
||||
/*
|
||||
* Returns true iff "str" could be confused as a command-line option when
|
||||
|
84
fsck.c
84
fsck.c
@ -558,7 +558,7 @@ static int verify_ordered(unsigned mode1, const char *name1,
|
||||
return c1 < c2 ? 0 : TREE_UNORDERED;
|
||||
}
|
||||
|
||||
static int fsck_tree(const struct object_id *oid,
|
||||
static int fsck_tree(const struct object_id *tree_oid,
|
||||
const char *buffer, unsigned long size,
|
||||
struct fsck_options *options)
|
||||
{
|
||||
@ -579,7 +579,9 @@ static int fsck_tree(const struct object_id *oid,
|
||||
struct name_stack df_dup_candidates = { NULL };
|
||||
|
||||
if (init_tree_desc_gently(&desc, buffer, size)) {
|
||||
retval += report(options, oid, OBJ_TREE, FSCK_MSG_BAD_TREE, "cannot be parsed as a tree");
|
||||
retval += report(options, tree_oid, OBJ_TREE,
|
||||
FSCK_MSG_BAD_TREE,
|
||||
"cannot be parsed as a tree");
|
||||
return retval;
|
||||
}
|
||||
|
||||
@ -589,11 +591,11 @@ static int fsck_tree(const struct object_id *oid,
|
||||
while (desc.size) {
|
||||
unsigned short mode;
|
||||
const char *name, *backslash;
|
||||
const struct object_id *oid;
|
||||
const struct object_id *entry_oid;
|
||||
|
||||
oid = tree_entry_extract(&desc, &name, &mode);
|
||||
entry_oid = tree_entry_extract(&desc, &name, &mode);
|
||||
|
||||
has_null_sha1 |= is_null_oid(oid);
|
||||
has_null_sha1 |= is_null_oid(entry_oid);
|
||||
has_full_path |= !!strchr(name, '/');
|
||||
has_empty_name |= !*name;
|
||||
has_dot |= !strcmp(name, ".");
|
||||
@ -603,23 +605,43 @@ static int fsck_tree(const struct object_id *oid,
|
||||
|
||||
if (is_hfs_dotgitmodules(name) || is_ntfs_dotgitmodules(name)) {
|
||||
if (!S_ISLNK(mode))
|
||||
oidset_insert(&options->gitmodules_found, oid);
|
||||
oidset_insert(&options->gitmodules_found,
|
||||
entry_oid);
|
||||
else
|
||||
retval += report(options,
|
||||
oid, OBJ_TREE,
|
||||
tree_oid, OBJ_TREE,
|
||||
FSCK_MSG_GITMODULES_SYMLINK,
|
||||
".gitmodules is a symbolic link");
|
||||
}
|
||||
|
||||
if (S_ISLNK(mode)) {
|
||||
if (is_hfs_dotgitignore(name) ||
|
||||
is_ntfs_dotgitignore(name))
|
||||
retval += report(options, tree_oid, OBJ_TREE,
|
||||
FSCK_MSG_GITIGNORE_SYMLINK,
|
||||
".gitignore is a symlink");
|
||||
if (is_hfs_dotgitattributes(name) ||
|
||||
is_ntfs_dotgitattributes(name))
|
||||
retval += report(options, tree_oid, OBJ_TREE,
|
||||
FSCK_MSG_GITATTRIBUTES_SYMLINK,
|
||||
".gitattributes is a symlink");
|
||||
if (is_hfs_dotmailmap(name) ||
|
||||
is_ntfs_dotmailmap(name))
|
||||
retval += report(options, tree_oid, OBJ_TREE,
|
||||
FSCK_MSG_MAILMAP_SYMLINK,
|
||||
".mailmap is a symlink");
|
||||
}
|
||||
|
||||
if ((backslash = strchr(name, '\\'))) {
|
||||
while (backslash) {
|
||||
backslash++;
|
||||
has_dotgit |= is_ntfs_dotgit(backslash);
|
||||
if (is_ntfs_dotgitmodules(backslash)) {
|
||||
if (!S_ISLNK(mode))
|
||||
oidset_insert(&options->gitmodules_found, oid);
|
||||
oidset_insert(&options->gitmodules_found,
|
||||
entry_oid);
|
||||
else
|
||||
retval += report(options, oid, OBJ_TREE,
|
||||
retval += report(options, tree_oid, OBJ_TREE,
|
||||
FSCK_MSG_GITMODULES_SYMLINK,
|
||||
".gitmodules is a symbolic link");
|
||||
}
|
||||
@ -628,7 +650,9 @@ static int fsck_tree(const struct object_id *oid,
|
||||
}
|
||||
|
||||
if (update_tree_entry_gently(&desc)) {
|
||||
retval += report(options, oid, OBJ_TREE, FSCK_MSG_BAD_TREE, "cannot be parsed as a tree");
|
||||
retval += report(options, tree_oid, OBJ_TREE,
|
||||
FSCK_MSG_BAD_TREE,
|
||||
"cannot be parsed as a tree");
|
||||
break;
|
||||
}
|
||||
|
||||
@ -676,25 +700,45 @@ static int fsck_tree(const struct object_id *oid,
|
||||
name_stack_clear(&df_dup_candidates);
|
||||
|
||||
if (has_null_sha1)
|
||||
retval += report(options, oid, OBJ_TREE, FSCK_MSG_NULL_SHA1, "contains entries pointing to null sha1");
|
||||
retval += report(options, tree_oid, OBJ_TREE,
|
||||
FSCK_MSG_NULL_SHA1,
|
||||
"contains entries pointing to null sha1");
|
||||
if (has_full_path)
|
||||
retval += report(options, oid, OBJ_TREE, FSCK_MSG_FULL_PATHNAME, "contains full pathnames");
|
||||
retval += report(options, tree_oid, OBJ_TREE,
|
||||
FSCK_MSG_FULL_PATHNAME,
|
||||
"contains full pathnames");
|
||||
if (has_empty_name)
|
||||
retval += report(options, oid, OBJ_TREE, FSCK_MSG_EMPTY_NAME, "contains empty pathname");
|
||||
retval += report(options, tree_oid, OBJ_TREE,
|
||||
FSCK_MSG_EMPTY_NAME,
|
||||
"contains empty pathname");
|
||||
if (has_dot)
|
||||
retval += report(options, oid, OBJ_TREE, FSCK_MSG_HAS_DOT, "contains '.'");
|
||||
retval += report(options, tree_oid, OBJ_TREE,
|
||||
FSCK_MSG_HAS_DOT,
|
||||
"contains '.'");
|
||||
if (has_dotdot)
|
||||
retval += report(options, oid, OBJ_TREE, FSCK_MSG_HAS_DOTDOT, "contains '..'");
|
||||
retval += report(options, tree_oid, OBJ_TREE,
|
||||
FSCK_MSG_HAS_DOTDOT,
|
||||
"contains '..'");
|
||||
if (has_dotgit)
|
||||
retval += report(options, oid, OBJ_TREE, FSCK_MSG_HAS_DOTGIT, "contains '.git'");
|
||||
retval += report(options, tree_oid, OBJ_TREE,
|
||||
FSCK_MSG_HAS_DOTGIT,
|
||||
"contains '.git'");
|
||||
if (has_zero_pad)
|
||||
retval += report(options, oid, OBJ_TREE, FSCK_MSG_ZERO_PADDED_FILEMODE, "contains zero-padded file modes");
|
||||
retval += report(options, tree_oid, OBJ_TREE,
|
||||
FSCK_MSG_ZERO_PADDED_FILEMODE,
|
||||
"contains zero-padded file modes");
|
||||
if (has_bad_modes)
|
||||
retval += report(options, oid, OBJ_TREE, FSCK_MSG_BAD_FILEMODE, "contains bad file modes");
|
||||
retval += report(options, tree_oid, OBJ_TREE,
|
||||
FSCK_MSG_BAD_FILEMODE,
|
||||
"contains bad file modes");
|
||||
if (has_dup_entries)
|
||||
retval += report(options, oid, OBJ_TREE, FSCK_MSG_DUPLICATE_ENTRIES, "contains duplicate file entries");
|
||||
retval += report(options, tree_oid, OBJ_TREE,
|
||||
FSCK_MSG_DUPLICATE_ENTRIES,
|
||||
"contains duplicate file entries");
|
||||
if (not_properly_sorted)
|
||||
retval += report(options, oid, OBJ_TREE, FSCK_MSG_TREE_NOT_SORTED, "not properly sorted");
|
||||
retval += report(options, tree_oid, OBJ_TREE,
|
||||
FSCK_MSG_TREE_NOT_SORTED,
|
||||
"not properly sorted");
|
||||
return retval;
|
||||
}
|
||||
|
||||
|
3
fsck.h
3
fsck.h
@ -67,6 +67,9 @@ enum fsck_msg_type {
|
||||
FUNC(NUL_IN_COMMIT, WARN) \
|
||||
/* infos (reported as warnings, but ignored by default) */ \
|
||||
FUNC(GITMODULES_PARSE, INFO) \
|
||||
FUNC(GITIGNORE_SYMLINK, INFO) \
|
||||
FUNC(GITATTRIBUTES_SYMLINK, INFO) \
|
||||
FUNC(MAILMAP_SYMLINK, INFO) \
|
||||
FUNC(BAD_TAG_NAME, INFO) \
|
||||
FUNC(MISSING_TAGGER_ENTRY, INFO) \
|
||||
/* ignored (elevated when requested) */ \
|
||||
|
5
path.c
5
path.c
@ -1493,6 +1493,11 @@ int is_ntfs_dotgitattributes(const char *name)
|
||||
return is_ntfs_dot_str(name, "gitattributes", "gi7d29");
|
||||
}
|
||||
|
||||
int is_ntfs_dotmailmap(const char *name)
|
||||
{
|
||||
return is_ntfs_dot_str(name, "mailmap", "maba30");
|
||||
}
|
||||
|
||||
int looks_like_command_line_option(const char *str)
|
||||
{
|
||||
return str && str[0] == '-';
|
||||
|
@ -172,9 +172,22 @@ static struct test_data dirname_data[] = {
|
||||
{ NULL, NULL }
|
||||
};
|
||||
|
||||
static int is_dotgitmodules(const char *path)
|
||||
static int check_dotfile(const char *x, const char **argv,
|
||||
int (*is_hfs)(const char *),
|
||||
int (*is_ntfs)(const char *))
|
||||
{
|
||||
return is_hfs_dotgitmodules(path) || is_ntfs_dotgitmodules(path);
|
||||
int res = 0, expect = 1;
|
||||
for (; *argv; argv++) {
|
||||
if (!strcmp("--not", *argv))
|
||||
expect = !expect;
|
||||
else if (expect != (is_hfs(*argv) || is_ntfs(*argv)))
|
||||
res = error("'%s' is %s.git%s", *argv,
|
||||
expect ? "not " : "", x);
|
||||
else
|
||||
fprintf(stderr, "ok: '%s' is %s.git%s\n",
|
||||
*argv, expect ? "" : "not ", x);
|
||||
}
|
||||
return !!res;
|
||||
}
|
||||
|
||||
static int cmp_by_st_size(const void *a, const void *b)
|
||||
@ -382,17 +395,24 @@ int cmd__path_utils(int argc, const char **argv)
|
||||
return test_function(dirname_data, posix_dirname, argv[1]);
|
||||
|
||||
if (argc > 2 && !strcmp(argv[1], "is_dotgitmodules")) {
|
||||
int res = 0, expect = 1, i;
|
||||
for (i = 2; i < argc; i++)
|
||||
if (!strcmp("--not", argv[i]))
|
||||
expect = !expect;
|
||||
else if (expect != is_dotgitmodules(argv[i]))
|
||||
res = error("'%s' is %s.gitmodules", argv[i],
|
||||
expect ? "not " : "");
|
||||
else
|
||||
fprintf(stderr, "ok: '%s' is %s.gitmodules\n",
|
||||
argv[i], expect ? "" : "not ");
|
||||
return !!res;
|
||||
return check_dotfile("modules", argv + 2,
|
||||
is_hfs_dotgitmodules,
|
||||
is_ntfs_dotgitmodules);
|
||||
}
|
||||
if (argc > 2 && !strcmp(argv[1], "is_dotgitignore")) {
|
||||
return check_dotfile("ignore", argv + 2,
|
||||
is_hfs_dotgitignore,
|
||||
is_ntfs_dotgitignore);
|
||||
}
|
||||
if (argc > 2 && !strcmp(argv[1], "is_dotgitattributes")) {
|
||||
return check_dotfile("attributes", argv + 2,
|
||||
is_hfs_dotgitattributes,
|
||||
is_ntfs_dotgitattributes);
|
||||
}
|
||||
if (argc > 2 && !strcmp(argv[1], "is_dotmailmap")) {
|
||||
return check_dotfile("mailmap", argv + 2,
|
||||
is_hfs_dotmailmap,
|
||||
is_ntfs_dotmailmap);
|
||||
}
|
||||
|
||||
if (argc > 2 && !strcmp(argv[1], "file-size")) {
|
||||
|
@ -468,6 +468,36 @@ test_expect_success 'match .gitmodules' '
|
||||
.gitmodules,:\$DATA
|
||||
'
|
||||
|
||||
test_expect_success 'match .gitattributes' '
|
||||
test-tool path-utils is_dotgitattributes \
|
||||
.gitattributes \
|
||||
.git${u200c}attributes \
|
||||
.Gitattributes \
|
||||
.gitattributeS \
|
||||
GITATT~1 \
|
||||
GI7D29~1
|
||||
'
|
||||
|
||||
test_expect_success 'match .gitignore' '
|
||||
test-tool path-utils is_dotgitignore \
|
||||
.gitignore \
|
||||
.git${u200c}ignore \
|
||||
.Gitignore \
|
||||
.gitignorE \
|
||||
GITIGN~1 \
|
||||
GI250A~1
|
||||
'
|
||||
|
||||
test_expect_success 'match .mailmap' '
|
||||
test-tool path-utils is_dotmailmap \
|
||||
.mailmap \
|
||||
.mail${u200c}map \
|
||||
.Mailmap \
|
||||
.mailmaP \
|
||||
MAILMA~1 \
|
||||
MABA30~1
|
||||
'
|
||||
|
||||
test_expect_success MINGW 'is_valid_path() on Windows' '
|
||||
test-tool path-utils is_valid_path \
|
||||
win32 \
|
||||
|
@ -1,9 +1,16 @@
|
||||
#!/bin/sh
|
||||
|
||||
test_description='check handling of .. in submodule names
|
||||
test_description='check broken or malicious patterns in .git* files
|
||||
|
||||
Exercise the name-checking function on a variety of names, and then give a
|
||||
real-world setup that confirms we catch this in practice.
|
||||
Such as:
|
||||
|
||||
- presence of .. in submodule names;
|
||||
Exercise the name-checking function on a variety of names, and then give a
|
||||
real-world setup that confirms we catch this in practice.
|
||||
|
||||
- nested submodule names
|
||||
|
||||
- symlinked .gitmodules, etc
|
||||
'
|
||||
. ./test-lib.sh
|
||||
. "$TEST_DIRECTORY"/lib-pack.sh
|
||||
@ -132,31 +139,84 @@ test_expect_success 'index-pack --strict works for non-repo pack' '
|
||||
grep gitmodulesName output
|
||||
'
|
||||
|
||||
test_expect_success 'fsck detects symlinked .gitmodules file' '
|
||||
git init symlink &&
|
||||
(
|
||||
cd symlink &&
|
||||
check_dotx_symlink () {
|
||||
fsck_must_fail=test_must_fail
|
||||
fsck_prefix=error
|
||||
refuse_index=t
|
||||
case "$1" in
|
||||
--warning)
|
||||
fsck_must_fail=
|
||||
fsck_prefix=warning
|
||||
refuse_index=
|
||||
shift
|
||||
;;
|
||||
esac
|
||||
|
||||
# Make the tree directly to avoid index restrictions.
|
||||
#
|
||||
# Because symlinks store the target as a blob, choose
|
||||
# a pathname that could be parsed as a .gitmodules file
|
||||
# to trick naive non-symlink-aware checking.
|
||||
tricky="[foo]bar=true" &&
|
||||
content=$(git hash-object -w ../.gitmodules) &&
|
||||
target=$(printf "$tricky" | git hash-object -w --stdin) &&
|
||||
{
|
||||
printf "100644 blob $content\t$tricky\n" &&
|
||||
printf "120000 blob $target\t.gitmodules\n"
|
||||
} | git mktree &&
|
||||
name=$1
|
||||
type=$2
|
||||
path=$3
|
||||
dir=symlink-$name-$type
|
||||
|
||||
# Check not only that we fail, but that it is due to the
|
||||
# symlink detector; this grep string comes from the config
|
||||
# variable name and will not be translated.
|
||||
test_must_fail git fsck 2>output &&
|
||||
test_i18ngrep gitmodulesSymlink output
|
||||
)
|
||||
'
|
||||
test_expect_success "set up repo with symlinked $name ($type)" '
|
||||
git init $dir &&
|
||||
(
|
||||
cd $dir &&
|
||||
|
||||
# Make the tree directly to avoid index restrictions.
|
||||
#
|
||||
# Because symlinks store the target as a blob, choose
|
||||
# a pathname that could be parsed as a .gitmodules file
|
||||
# to trick naive non-symlink-aware checking.
|
||||
tricky="[foo]bar=true" &&
|
||||
content=$(git hash-object -w ../.gitmodules) &&
|
||||
target=$(printf "$tricky" | git hash-object -w --stdin) &&
|
||||
{
|
||||
printf "100644 blob $content\t$tricky\n" &&
|
||||
printf "120000 blob $target\t$path\n"
|
||||
} >bad-tree
|
||||
) &&
|
||||
tree=$(git -C $dir mktree <$dir/bad-tree)
|
||||
'
|
||||
|
||||
test_expect_success "fsck detects symlinked $name ($type)" '
|
||||
(
|
||||
cd $dir &&
|
||||
|
||||
# Check not only that we fail, but that it is due to the
|
||||
# symlink detector
|
||||
$fsck_must_fail git fsck 2>output &&
|
||||
grep "$fsck_prefix.*tree $tree: ${name}Symlink" output
|
||||
)
|
||||
'
|
||||
|
||||
test -n "$refuse_index" &&
|
||||
test_expect_success "refuse to load symlinked $name into index ($type)" '
|
||||
test_must_fail \
|
||||
git -C $dir \
|
||||
-c core.protectntfs \
|
||||
-c core.protecthfs \
|
||||
read-tree $tree 2>err &&
|
||||
grep "invalid path.*$name" err &&
|
||||
git -C $dir ls-files -s >out &&
|
||||
test_must_be_empty out
|
||||
'
|
||||
}
|
||||
|
||||
check_dotx_symlink gitmodules vanilla .gitmodules
|
||||
check_dotx_symlink gitmodules ntfs ".gitmodules ."
|
||||
check_dotx_symlink gitmodules hfs ".${u200c}gitmodules"
|
||||
|
||||
check_dotx_symlink --warning gitattributes vanilla .gitattributes
|
||||
check_dotx_symlink --warning gitattributes ntfs ".gitattributes ."
|
||||
check_dotx_symlink --warning gitattributes hfs ".${u200c}gitattributes"
|
||||
|
||||
check_dotx_symlink --warning gitignore vanilla .gitignore
|
||||
check_dotx_symlink --warning gitignore ntfs ".gitignore ."
|
||||
check_dotx_symlink --warning gitignore hfs ".${u200c}gitignore"
|
||||
|
||||
check_dotx_symlink --warning mailmap vanilla .mailmap
|
||||
check_dotx_symlink --warning mailmap ntfs ".mailmap ."
|
||||
check_dotx_symlink --warning mailmap hfs ".${u200c}mailmap"
|
||||
|
||||
test_expect_success 'fsck detects non-blob .gitmodules' '
|
||||
git init non-blob &&
|
5
utf8.c
5
utf8.c
@ -777,6 +777,11 @@ int is_hfs_dotgitattributes(const char *path)
|
||||
return is_hfs_dot_str(path, "gitattributes");
|
||||
}
|
||||
|
||||
int is_hfs_dotmailmap(const char *path)
|
||||
{
|
||||
return is_hfs_dot_str(path, "mailmap");
|
||||
}
|
||||
|
||||
const char utf8_bom[] = "\357\273\277";
|
||||
|
||||
int skip_utf8_bom(char **text, size_t len)
|
||||
|
Loading…
Reference in New Issue
Block a user