Skip to content

Commit 10ecfa7

Browse files
committed
verify_path: disallow symlinks in .gitmodules
There are a few reasons it's not a good idea to make .gitmodules a symlink, including: 1. It won't be portable to systems without symlinks. 2. It may behave inconsistently, since Git may look at this file in the index or a tree without bothering to resolve any symbolic links. We don't do this _yet_, but the config infrastructure is there and it's planned for the future. With some clever code, we could make (2) work. And some people may not care about (1) if they only work on one platform. But there are a few security reasons to simply disallow it: a. A symlinked .gitmodules file may circumvent any fsck checks of the content. b. Git may read and write from the on-disk file without sanity checking the symlink target. So for example, if you link ".gitmodules" to "../oops" and run "git submodule add", we'll write to the file "oops" outside the repository. Again, both of those are problems that _could_ be solved with sufficient code, but given the complications in (1) and (2), we're better off just outlawing it explicitly. Note the slightly tricky call to verify_path() in update-index's update_one(). There we may not have a mode if we're not updating from the filesystem (e.g., we might just be removing the file). Passing "0" as the mode there works fine; since it's not a symlink, we'll just skip the extra checks. Signed-off-by: Jeff King <[email protected]>
1 parent eb12dd0 commit 10ecfa7

File tree

4 files changed

+37
-15
lines changed

4 files changed

+37
-15
lines changed

apply.c

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3867,9 +3867,9 @@ static int check_unsafe_path(struct patch *patch)
38673867
if (!patch->is_delete)
38683868
new_name = patch->new_name;
38693869

3870-
if (old_name && !verify_path(old_name))
3870+
if (old_name && !verify_path(old_name, patch->old_mode))
38713871
return error(_("invalid path '%s'"), old_name);
3872-
if (new_name && !verify_path(new_name))
3872+
if (new_name && !verify_path(new_name, patch->new_mode))
38733873
return error(_("invalid path '%s'"), new_name);
38743874
return 0;
38753875
}

builtin/update-index.c

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -399,7 +399,7 @@ static int add_cacheinfo(unsigned int mode, const struct object_id *oid,
399399
int size, len, option;
400400
struct cache_entry *ce;
401401

402-
if (!verify_path(path))
402+
if (!verify_path(path, mode))
403403
return error("Invalid path '%s'", path);
404404

405405
len = strlen(path);
@@ -452,7 +452,7 @@ static void update_one(const char *path)
452452
stat_errno = errno;
453453
} /* else stat is valid */
454454

455-
if (!verify_path(path)) {
455+
if (!verify_path(path, st.st_mode)) {
456456
fprintf(stderr, "Ignoring path %s\n", path);
457457
return;
458458
}
@@ -543,7 +543,7 @@ static void read_index_info(int nul_term_line)
543543
path_name = uq.buf;
544544
}
545545

546-
if (!verify_path(path_name)) {
546+
if (!verify_path(path_name, mode)) {
547547
fprintf(stderr, "Ignoring path %s\n", path_name);
548548
continue;
549549
}

cache.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -598,7 +598,7 @@ extern int read_index_unmerged(struct index_state *);
598598
extern int write_locked_index(struct index_state *, struct lock_file *lock, unsigned flags);
599599
extern int discard_index(struct index_state *);
600600
extern int unmerged_index(const struct index_state *);
601-
extern int verify_path(const char *path);
601+
extern int verify_path(const char *path, unsigned mode);
602602
extern int strcmp_offset(const char *s1, const char *s2, size_t *first_change);
603603
extern int index_dir_exists(struct index_state *istate, const char *name, int namelen);
604604
extern void adjust_dirname_case(struct index_state *istate, char *name);

read-cache.c

Lines changed: 31 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -732,7 +732,7 @@ struct cache_entry *make_cache_entry(unsigned int mode,
732732
int size, len;
733733
struct cache_entry *ce, *ret;
734734

735-
if (!verify_path(path)) {
735+
if (!verify_path(path, mode)) {
736736
error("Invalid path '%s'", path);
737737
return NULL;
738738
}
@@ -796,7 +796,7 @@ int ce_same_name(const struct cache_entry *a, const struct cache_entry *b)
796796
* Also, we don't want double slashes or slashes at the
797797
* end that can make pathnames ambiguous.
798798
*/
799-
static int verify_dotfile(const char *rest)
799+
static int verify_dotfile(const char *rest, unsigned mode)
800800
{
801801
/*
802802
* The first character was '.', but that
@@ -814,6 +814,9 @@ static int verify_dotfile(const char *rest)
814814
* case-insensitively here, even if ignore_case is not set.
815815
* This outlaws ".GIT" everywhere out of an abundance of caution,
816816
* since there's really no good reason to allow it.
817+
*
818+
* Once we've seen ".git", we can also find ".gitmodules", etc (also
819+
* case-insensitively).
817820
*/
818821
case 'g':
819822
case 'G':
@@ -823,6 +826,12 @@ static int verify_dotfile(const char *rest)
823826
break;
824827
if (rest[3] == '\0' || is_dir_sep(rest[3]))
825828
return 0;
829+
if (S_ISLNK(mode)) {
830+
rest += 3;
831+
if (skip_iprefix(rest, "modules", &rest) &&
832+
(*rest == '\0' || is_dir_sep(*rest)))
833+
return 0;
834+
}
826835
break;
827836
case '.':
828837
if (rest[1] == '\0' || is_dir_sep(rest[1]))
@@ -831,7 +840,7 @@ static int verify_dotfile(const char *rest)
831840
return 1;
832841
}
833842

834-
int verify_path(const char *path)
843+
int verify_path(const char *path, unsigned mode)
835844
{
836845
char c;
837846

@@ -844,12 +853,25 @@ int verify_path(const char *path)
844853
return 1;
845854
if (is_dir_sep(c)) {
846855
inside:
847-
if (protect_hfs && is_hfs_dotgit(path))
848-
return 0;
849-
if (protect_ntfs && is_ntfs_dotgit(path))
850-
return 0;
856+
if (protect_hfs) {
857+
if (is_hfs_dotgit(path))
858+
return 0;
859+
if (S_ISLNK(mode)) {
860+
if (is_hfs_dotgitmodules(path))
861+
return 0;
862+
}
863+
}
864+
if (protect_ntfs) {
865+
if (is_ntfs_dotgit(path))
866+
return 0;
867+
if (S_ISLNK(mode)) {
868+
if (is_ntfs_dotgitmodules(path))
869+
return 0;
870+
}
871+
}
872+
851873
c = *path++;
852-
if ((c == '.' && !verify_dotfile(path)) ||
874+
if ((c == '.' && !verify_dotfile(path, mode)) ||
853875
is_dir_sep(c) || c == '\0')
854876
return 0;
855877
}
@@ -1166,7 +1188,7 @@ static int add_index_entry_with_check(struct index_state *istate, struct cache_e
11661188

11671189
if (!ok_to_add)
11681190
return -1;
1169-
if (!verify_path(ce->name))
1191+
if (!verify_path(ce->name, ce->ce_mode))
11701192
return error("Invalid path '%s'", ce->name);
11711193

11721194
if (!skip_df_check &&

0 commit comments

Comments
 (0)