Skip to content

Commit eedd594

Browse files
committed
Merge branch 'jk/submodule-name-verify-fix' into jk/submodule-name-verify-fsck
* jk/submodule-name-verify-fix: verify_path: disallow symlinks in .gitmodules update-index: stat updated files earlier verify_path: drop clever fallthrough skip_prefix: add icase-insensitive variant is_{hfs,ntfs}_dotgitmodules: add tests path: match NTFS short names for more .git files is_hfs_dotgit: match other .git files is_ntfs_dotgit: use a size_t for traversing string submodule-config: verify submodule names as paths Note that this includes two bits of evil-merge: - there's a new call to verify_path() that doesn't actually have a mode available. It should be OK to pass "0" here, since we're just manipulating the untracked cache, not an actual index entry. - the lstat() in builtin/update-index.c:update_one() needs to be updated to handle the fsmonitor case (without this it still behaves correctly, but does an unnecessary lstat).
2 parents 468165c + 10ecfa7 commit eedd594

16 files changed

+474
-42
lines changed

apply.c

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

3863-
if (old_name && !verify_path(old_name))
3863+
if (old_name && !verify_path(old_name, patch->old_mode))
38643864
return error(_("invalid path '%s'"), old_name);
3865-
if (new_name && !verify_path(new_name))
3865+
if (new_name && !verify_path(new_name, patch->new_mode))
38663866
return error(_("invalid path '%s'"), new_name);
38673867
return 0;
38683868
}

builtin/submodule--helper.c

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1817,6 +1817,29 @@ static int is_active(int argc, const char **argv, const char *prefix)
18171817
return !is_submodule_active(the_repository, argv[1]);
18181818
}
18191819

1820+
/*
1821+
* Exit non-zero if any of the submodule names given on the command line is
1822+
* invalid. If no names are given, filter stdin to print only valid names
1823+
* (which is primarily intended for testing).
1824+
*/
1825+
static int check_name(int argc, const char **argv, const char *prefix)
1826+
{
1827+
if (argc > 1) {
1828+
while (*++argv) {
1829+
if (check_submodule_name(*argv) < 0)
1830+
return 1;
1831+
}
1832+
} else {
1833+
struct strbuf buf = STRBUF_INIT;
1834+
while (strbuf_getline(&buf, stdin) != EOF) {
1835+
if (!check_submodule_name(buf.buf))
1836+
printf("%s\n", buf.buf);
1837+
}
1838+
strbuf_release(&buf);
1839+
}
1840+
return 0;
1841+
}
1842+
18201843
#define SUPPORT_SUPER_PREFIX (1<<0)
18211844

18221845
struct cmd_struct {
@@ -1842,6 +1865,7 @@ static struct cmd_struct commands[] = {
18421865
{"push-check", push_check, 0},
18431866
{"absorb-git-dirs", absorb_git_dirs, SUPPORT_SUPER_PREFIX},
18441867
{"is-active", is_active, 0},
1868+
{"check-name", check_name, 0},
18451869
};
18461870

18471871
int cmd_submodule__helper(int argc, const char **argv, const char *prefix)

builtin/update-index.c

Lines changed: 21 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -364,10 +364,9 @@ static int process_directory(const char *path, int len, struct stat *st)
364364
return error("%s: is a directory - add files inside instead", path);
365365
}
366366

367-
static int process_path(const char *path)
367+
static int process_path(const char *path, struct stat *st, int stat_errno)
368368
{
369369
int pos, len;
370-
struct stat st;
371370
const struct cache_entry *ce;
372371

373372
len = strlen(path);
@@ -391,13 +390,13 @@ static int process_path(const char *path)
391390
* First things first: get the stat information, to decide
392391
* what to do about the pathname!
393392
*/
394-
if (lstat(path, &st) < 0)
395-
return process_lstat_error(path, errno);
393+
if (stat_errno)
394+
return process_lstat_error(path, stat_errno);
396395

397-
if (S_ISDIR(st.st_mode))
398-
return process_directory(path, len, &st);
396+
if (S_ISDIR(st->st_mode))
397+
return process_directory(path, len, st);
399398

400-
return add_one_path(ce, path, len, &st);
399+
return add_one_path(ce, path, len, st);
401400
}
402401

403402
static int add_cacheinfo(unsigned int mode, const struct object_id *oid,
@@ -406,7 +405,7 @@ static int add_cacheinfo(unsigned int mode, const struct object_id *oid,
406405
int size, len, option;
407406
struct cache_entry *ce;
408407

409-
if (!verify_path(path))
408+
if (!verify_path(path, mode))
410409
return error("Invalid path '%s'", path);
411410

412411
len = strlen(path);
@@ -449,7 +448,18 @@ static void chmod_path(char flip, const char *path)
449448

450449
static void update_one(const char *path)
451450
{
452-
if (!verify_path(path)) {
451+
int stat_errno = 0;
452+
struct stat st;
453+
454+
if (mark_valid_only || mark_skip_worktree_only || force_remove ||
455+
mark_fsmonitor_only)
456+
st.st_mode = 0;
457+
else if (lstat(path, &st) < 0) {
458+
st.st_mode = 0;
459+
stat_errno = errno;
460+
} /* else stat is valid */
461+
462+
if (!verify_path(path, st.st_mode)) {
453463
fprintf(stderr, "Ignoring path %s\n", path);
454464
return;
455465
}
@@ -475,7 +485,7 @@ static void update_one(const char *path)
475485
report("remove '%s'", path);
476486
return;
477487
}
478-
if (process_path(path))
488+
if (process_path(path, &st, stat_errno))
479489
die("Unable to process path %s", path);
480490
report("add '%s'", path);
481491
}
@@ -545,7 +555,7 @@ static void read_index_info(int nul_term_line)
545555
path_name = uq.buf;
546556
}
547557

548-
if (!verify_path(path_name)) {
558+
if (!verify_path(path_name, mode)) {
549559
fprintf(stderr, "Ignoring path %s\n", path_name);
550560
continue;
551561
}

cache.h

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -634,7 +634,7 @@ extern int unmerged_index(const struct index_state *);
634634
*/
635635
extern int index_has_changes(struct strbuf *sb);
636636

637-
extern int verify_path(const char *path);
637+
extern int verify_path(const char *path, unsigned mode);
638638
extern int strcmp_offset(const char *s1, const char *s2, size_t *first_change);
639639
extern int index_dir_exists(struct index_state *istate, const char *name, int namelen);
640640
extern void adjust_dirname_case(struct index_state *istate, char *name);
@@ -1165,7 +1165,15 @@ int normalize_path_copy(char *dst, const char *src);
11651165
int longest_ancestor_length(const char *path, struct string_list *prefixes);
11661166
char *strip_path_suffix(const char *path, const char *suffix);
11671167
int daemon_avoid_alias(const char *path);
1168-
extern int is_ntfs_dotgit(const char *name);
1168+
1169+
/*
1170+
* These functions match their is_hfs_dotgit() counterparts; see utf8.h for
1171+
* details.
1172+
*/
1173+
int is_ntfs_dotgit(const char *name);
1174+
int is_ntfs_dotgitmodules(const char *name);
1175+
int is_ntfs_dotgitignore(const char *name);
1176+
int is_ntfs_dotgitattributes(const char *name);
11691177

11701178
/*
11711179
* Returns true iff "str" could be confused as a command-line option when

dir.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2992,7 +2992,7 @@ void untracked_cache_invalidate_path(struct index_state *istate,
29922992
{
29932993
if (!istate->untracked || !istate->untracked->root)
29942994
return;
2995-
if (!safe_path && !verify_path(path))
2995+
if (!safe_path && !verify_path(path, 0))
29962996
return;
29972997
invalidate_one_component(istate->untracked, istate->untracked->root,
29982998
path, strlen(path));

git-compat-util.h

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1001,6 +1001,23 @@ static inline int sane_iscase(int x, int is_lower)
10011001
return (x & 0x20) == 0;
10021002
}
10031003

1004+
/*
1005+
* Like skip_prefix, but compare case-insensitively. Note that the comparison
1006+
* is done via tolower(), so it is strictly ASCII (no multi-byte characters or
1007+
* locale-specific conversions).
1008+
*/
1009+
static inline int skip_iprefix(const char *str, const char *prefix,
1010+
const char **out)
1011+
{
1012+
do {
1013+
if (!*prefix) {
1014+
*out = str;
1015+
return 1;
1016+
}
1017+
} while (tolower(*str++) == tolower(*prefix++));
1018+
return 0;
1019+
}
1020+
10041021
static inline int strtoul_ui(char const *s, int base, unsigned int *result)
10051022
{
10061023
unsigned long ul;

git-submodule.sh

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -229,6 +229,11 @@ Use -f if you really want to add it." >&2
229229
sm_name="$sm_path"
230230
fi
231231

232+
if ! git submodule--helper check-name "$sm_name"
233+
then
234+
die "$(eval_gettext "'$sm_name' is not a valid submodule name")"
235+
fi
236+
232237
# perhaps the path exists and is already a git repo, else clone it
233238
if test -e "$sm_path"
234239
then

path.c

Lines changed: 85 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1305,7 +1305,7 @@ static int only_spaces_and_periods(const char *path, size_t len, size_t skip)
13051305

13061306
int is_ntfs_dotgit(const char *name)
13071307
{
1308-
int len;
1308+
size_t len;
13091309

13101310
for (len = 0; ; len++)
13111311
if (!name[len] || name[len] == '\\' || is_dir_sep(name[len])) {
@@ -1322,6 +1322,90 @@ int is_ntfs_dotgit(const char *name)
13221322
}
13231323
}
13241324

1325+
static int is_ntfs_dot_generic(const char *name,
1326+
const char *dotgit_name,
1327+
size_t len,
1328+
const char *dotgit_ntfs_shortname_prefix)
1329+
{
1330+
int saw_tilde;
1331+
size_t i;
1332+
1333+
if ((name[0] == '.' && !strncasecmp(name + 1, dotgit_name, len))) {
1334+
i = len + 1;
1335+
only_spaces_and_periods:
1336+
for (;;) {
1337+
char c = name[i++];
1338+
if (!c)
1339+
return 1;
1340+
if (c != ' ' && c != '.')
1341+
return 0;
1342+
}
1343+
}
1344+
1345+
/*
1346+
* Is it a regular NTFS short name, i.e. shortened to 6 characters,
1347+
* followed by ~1, ... ~4?
1348+
*/
1349+
if (!strncasecmp(name, dotgit_name, 6) && name[6] == '~' &&
1350+
name[7] >= '1' && name[7] <= '4') {
1351+
i = 8;
1352+
goto only_spaces_and_periods;
1353+
}
1354+
1355+
/*
1356+
* Is it a fall-back NTFS short name (for details, see
1357+
* https://en.wikipedia.org/wiki/8.3_filename?
1358+
*/
1359+
for (i = 0, saw_tilde = 0; i < 8; i++)
1360+
if (name[i] == '\0')
1361+
return 0;
1362+
else if (saw_tilde) {
1363+
if (name[i] < '0' || name[i] > '9')
1364+
return 0;
1365+
} else if (name[i] == '~') {
1366+
if (name[++i] < '1' || name[i] > '9')
1367+
return 0;
1368+
saw_tilde = 1;
1369+
} else if (i >= 6)
1370+
return 0;
1371+
else if (name[i] < 0) {
1372+
/*
1373+
* We know our needles contain only ASCII, so we clamp
1374+
* here to make the results of tolower() sane.
1375+
*/
1376+
return 0;
1377+
} else if (tolower(name[i]) != dotgit_ntfs_shortname_prefix[i])
1378+
return 0;
1379+
1380+
goto only_spaces_and_periods;
1381+
}
1382+
1383+
/*
1384+
* Inline helper to make sure compiler resolves strlen() on literals at
1385+
* compile time.
1386+
*/
1387+
static inline int is_ntfs_dot_str(const char *name, const char *dotgit_name,
1388+
const char *dotgit_ntfs_shortname_prefix)
1389+
{
1390+
return is_ntfs_dot_generic(name, dotgit_name, strlen(dotgit_name),
1391+
dotgit_ntfs_shortname_prefix);
1392+
}
1393+
1394+
int is_ntfs_dotgitmodules(const char *name)
1395+
{
1396+
return is_ntfs_dot_str(name, "gitmodules", "gi7eba");
1397+
}
1398+
1399+
int is_ntfs_dotgitignore(const char *name)
1400+
{
1401+
return is_ntfs_dot_str(name, "gitignore", "gi250a");
1402+
}
1403+
1404+
int is_ntfs_dotgitattributes(const char *name)
1405+
{
1406+
return is_ntfs_dot_str(name, "gitattributes", "gi7d29");
1407+
}
1408+
13251409
int looks_like_command_line_option(const char *str)
13261410
{
13271411
return str && str[0] == '-';

read-cache.c

Lines changed: 38 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -752,7 +752,7 @@ struct cache_entry *make_cache_entry(unsigned int mode,
752752
int size, len;
753753
struct cache_entry *ce, *ret;
754754

755-
if (!verify_path(path)) {
755+
if (!verify_path(path, mode)) {
756756
error("Invalid path '%s'", path);
757757
return NULL;
758758
}
@@ -817,7 +817,7 @@ int ce_same_name(const struct cache_entry *a, const struct cache_entry *b)
817817
* Also, we don't want double slashes or slashes at the
818818
* end that can make pathnames ambiguous.
819819
*/
820-
static int verify_dotfile(const char *rest)
820+
static int verify_dotfile(const char *rest, unsigned mode)
821821
{
822822
/*
823823
* The first character was '.', but that
@@ -831,25 +831,37 @@ static int verify_dotfile(const char *rest)
831831

832832
switch (*rest) {
833833
/*
834-
* ".git" followed by NUL or slash is bad. This
835-
* shares the path end test with the ".." case.
834+
* ".git" followed by NUL or slash is bad. Note that we match
835+
* case-insensitively here, even if ignore_case is not set.
836+
* This outlaws ".GIT" everywhere out of an abundance of caution,
837+
* since there's really no good reason to allow it.
838+
*
839+
* Once we've seen ".git", we can also find ".gitmodules", etc (also
840+
* case-insensitively).
836841
*/
837842
case 'g':
838843
case 'G':
839844
if (rest[1] != 'i' && rest[1] != 'I')
840845
break;
841846
if (rest[2] != 't' && rest[2] != 'T')
842847
break;
843-
rest += 2;
844-
/* fallthrough */
848+
if (rest[3] == '\0' || is_dir_sep(rest[3]))
849+
return 0;
850+
if (S_ISLNK(mode)) {
851+
rest += 3;
852+
if (skip_iprefix(rest, "modules", &rest) &&
853+
(*rest == '\0' || is_dir_sep(*rest)))
854+
return 0;
855+
}
856+
break;
845857
case '.':
846858
if (rest[1] == '\0' || is_dir_sep(rest[1]))
847859
return 0;
848860
}
849861
return 1;
850862
}
851863

852-
int verify_path(const char *path)
864+
int verify_path(const char *path, unsigned mode)
853865
{
854866
char c;
855867

@@ -862,12 +874,25 @@ int verify_path(const char *path)
862874
return 1;
863875
if (is_dir_sep(c)) {
864876
inside:
865-
if (protect_hfs && is_hfs_dotgit(path))
866-
return 0;
867-
if (protect_ntfs && is_ntfs_dotgit(path))
868-
return 0;
877+
if (protect_hfs) {
878+
if (is_hfs_dotgit(path))
879+
return 0;
880+
if (S_ISLNK(mode)) {
881+
if (is_hfs_dotgitmodules(path))
882+
return 0;
883+
}
884+
}
885+
if (protect_ntfs) {
886+
if (is_ntfs_dotgit(path))
887+
return 0;
888+
if (S_ISLNK(mode)) {
889+
if (is_ntfs_dotgitmodules(path))
890+
return 0;
891+
}
892+
}
893+
869894
c = *path++;
870-
if ((c == '.' && !verify_dotfile(path)) ||
895+
if ((c == '.' && !verify_dotfile(path, mode)) ||
871896
is_dir_sep(c) || c == '\0')
872897
return 0;
873898
}
@@ -1184,7 +1209,7 @@ static int add_index_entry_with_check(struct index_state *istate, struct cache_e
11841209

11851210
if (!ok_to_add)
11861211
return -1;
1187-
if (!verify_path(ce->name))
1212+
if (!verify_path(ce->name, ce->ce_mode))
11881213
return error("Invalid path '%s'", ce->name);
11891214

11901215
if (!skip_df_check &&

0 commit comments

Comments
 (0)